Skip to content

Payments Manager

View raw .md
On this page

Table of Contents#


Singleton Lifecycle#

PaymentsManager is a process-level singleton, the same shape as every other manager in this ecosystem (NotificationManager, SocialAuthManager): 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, so state set through one is visible through the other.


The Five Reads#

The manager holds the BillingService this build resolved (through the conditional-import factory, see Drivers) and answers the five reads on it. Payments forwards each one as an explicit static method of the same name:

dart
final entitlement = await Payments.currentEntitlement();
final plans = await Payments.getPlans();
final usage = await Payments.getUsage();
final invoices = await Payments.getInvoices();
final method = await Payments.getPaymentMethod();

None of the five ever throws for want of a platform: the manager always has SOME BillingService, because every arm of the factory returns a real one (never null) for the reads.


Rail Resolution: web and store#

The manager also exposes the two RAILS, reading them 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 web = Payments.web;
if (web != null) {
  await web.checkout(
    productKey: 'pro_annual',
    successUrl: '...',
    cancelUrl: '...',
  );
}

There is no second way to ask the same question anywhere in the package: no isWeb getter, no kIsWeb check inside the manager. A second way to ask would eventually disagree with the first, and the contract's own absence is already the answer. See Rails for what each rail's contract promises and, for the store rail, what it does not.


extend(): Overriding a Driver#

The manager resolves drivers through an explicit registry plus a factory map, because there is no reflection here to autowire one, and exposes an extend(name, factory) hook that registers a custom driver and clears any cached instance under that name so the next resolution builds the new one. This mirrors SocialAuthManager.extend in magic_social_auth, the sibling to read for the exact pattern.

dart
Payments.manager.extend('sandbox', (config) => SandboxBillingDriver(config));

This is the seam a test uses to stand in for a driver's behaviour without mocking a third-party SDK, and the one a future store implementation would let a consumer swap.


Facade Delegation#

Payments carries no logic of its own; every member is a forwarder to the manager, the same convention Notify and SocialAuth follow:

dart
class Payments {
  Payments._();

  static PaymentsManager get manager => PaymentsManager();

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

  // ... the remaining reads, `web`, `store` and `extend` follow the same shape.
}

Reaching for Payments.manager directly is how a caller gets at anything the facade has not (yet) forwarded.


Related

esc

Search index did not load.

No match for "".

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