Skip to content

Payments Manager

View raw .md

PaymentsManager is the singleton behind the Payments facade: it resolves the three roles (billing, web, store) from the build's factory or from an override registered with extend.

On this page

Quick Reference#

Member Answers When it is null
billing BillingService, the five reads Never
web WebBillingService?, the hosted checkout and portal A build that cannot serve the web rail
store StoreBillingService?, the store purchase A build that cannot serve a store
extend(role, factory) Registers an override for a role n/a
forgetDrivers() Drops every override and every resolved instance n/a

The roles are PaymentsManager.billingRole, PaymentsManager.webRole and PaymentsManager.storeRole; PaymentsManager.roles is the closed set.

Singleton Lifecycle#

PaymentsManager is a process-level singleton: a private constructor behind a factory that always returns the same instance.

dart
class PaymentsManager {
  static final PaymentsManager _instance = PaymentsManager._internal();

  factory PaymentsManager() => _instance;

  PaymentsManager._internal();
}

Payments, the facade, resolves the same instance, and so does app.make<PaymentsManager>('payments'), so an override registered through one is visible through the others.

The Three Roles#

A role is filled by a consumer's override when there is one, otherwise by the build's conditional-import factory (see Drivers). Each role is resolved the first time it is read and held after that.

billing carries the five reads. Payments forwards each one as an explicit static method of the same name:

dart
import 'package:magic_payments/magic_payments.dart';

final BillingEntitlement entitlement = await Payments.currentEntitlement();
final List<Map<String, dynamic>> plans = await Payments.getPlans();
final List<UsageStat> usage = await Payments.getUsage();
final BillingInvoicesPage invoices = await Payments.getInvoices();
final PaymentMethod method = await Payments.getPaymentMethod();

The manager always has some BillingService, because every arm of the factory returns one. On a platform the package has never seen that service is the stub, and each of its reads throws UnsupportedPlatformException: a refusal a support ticket can read, rather than an empty answer that would render as a customer with no subscription.

Rail Resolution: web and store#

The manager exposes the two rails from the same factory rather than from a platform check:

dart
WebBillingService? get web;
StoreBillingService? get store;

Payments.web and Payments.store forward these directly. Either can be null, and that is the whole availability mechanism: a caller checks the rail before it renders an affordance, instead of rendering one and catching a refusal.

dart
final WebBillingService? web = Payments.web;
if (web != null) {
  await web.checkout(
    productKey: 'pro_annual',
    successUrl: 'https://example.com/billing?checkout=success',
    cancelUrl: 'https://example.com/billing?checkout=cancel',
  );
}

There is no second way to ask the same question: no isWeb getter, no kIsWeb check inside the manager. A rail that is absent is not cached, so asking again costs nothing. See Rails for what each rail's contract promises.

extend(): Overriding a Role#

extend(role, factory) registers a factory for one role and evicts any instance already resolved for it, so the next read builds the override. It is the seam a test uses to stand in for a rail without mocking a vendor SDK, and the way an app supplies a rail this package does not ship.

dart
Payments.extend(PaymentsManager.storeRole, () => MyStoreRail());
  • An unknown role throws a BillingException at once, because an override under a misspelt role is one nothing would ever read.
  • The factory is not called by extend. Its result is type-checked when the role is first read, and a wrong type throws a BillingException naming the role.
  • forgetDrivers() clears every override and every resolved instance. Call it between tests: the manager is a static final that outlives a container reset.

Facade Delegation#

Payments carries no logic of its own; every member forwards to the manager:

dart
class Payments {
  Payments._();

  static PaymentsManager get manager => PaymentsManager();

  static BillingService get billing => manager.billing;
  static WebBillingService? get web => manager.web;
  static StoreBillingService? get store => manager.store;

  static Future<BillingEntitlement> currentEntitlement() =>
      billing.currentEntitlement();

  // getPlans, getUsage, getInvoices, getPaymentMethod, extend and
  // forgetDrivers follow the same shape.
}
  • Service Provider: where the manager is bound and its driver is resolved at boot.
  • Rails: the three contracts the roles carry.
  • Drivers: which build resolves which implementation.
esc

Search index did not load.

No match for "".

Try a class like rounded-lg or a widget like WButton.