Payments Manager
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.
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:
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:
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.
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.
Payments.extend(PaymentsManager.storeRole, () => MyStoreRail());
- An unknown role throws a
BillingExceptionat 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 aBillingExceptionnaming the role. forgetDrivers()clears every override and every resolved instance. Call it between tests: the manager is astatic finalthat outlives a container reset.
Facade Delegation#
Payments carries no logic of its own; every member forwards to the manager:
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.
}
Related Documentation#
- 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.