MagicStarterBillingController class
Backs the billing screen: six independent reads of what a customer is entitled to, what they have spent, and where they manage it.
Why this controller does NOT use MagicStateMixin
Every sibling controller in this package mixes in MagicStateMixin, and
this one deliberately does not. That mixin holds ONE T? _state slot and one
RxStatus, and both of its transitions null the state:
setLoading() calls setState(null, ...)
(magic/lib/src/http/magic_controller.dart:177-179) and so does setError()
(:195-197). This screen runs six independent reads whose answers have
nothing to do with each other, so routing them through one slot would mean
any single failing read wipes the other five: an invoices timeout would blank
the plan the customer is paying for. Each read therefore publishes its own
field and this controller notifies through refreshUI instead.
Every read degrades, none of them throws
A read that fails leaves its own field at last-known state and logs. The screen is a set of independent cards, and a card with no data is recoverable where an exception out of a read takes the whole screen with it. The payment method additionally keeps its OWN pmLoading and pmError, because it is the one read that dials a payment rail live, so a slow or broken rail must gate that card and nothing else.
What the consumer has to supply
usageCopy is REQUIRED, and that is a decision rather than an oversight.
Both of the defaults an optional parameter could carry are defects: a
pass-through would put the raw wire key requests_this_month on a customer's
screen, and a drop would silently remove the usage surface from every app
that forgot to pass one. Neither may be reachable by forgetting.
The other two collaborators are optional, and leaving one out is answered in OPPOSITE directions, which is a decision rather than an inconsistency. No MagicStarterTeamOwnershipReader leaves ownership unresolved and every gate permissive, because hiding a purchase button from a real owner stands between them and paying while the server refuses a non-owner regardless. No MagicStarterStoreFundedTeamReader REFUSES the store purchase, because nothing in that build can promise a second purchase will not transfer another team's subscription away, and a customer cannot undo that.
Because it takes required collaborators, this controller is registered with
Magic.put(...) rather than resolved through the Magic.findOrPut singleton
getter the sibling controllers expose; there is no zero-argument constructor
for findOrPut to call.
Example
Magic.put(
MagicStarterBillingController(
usageCopy: withUsageCopy,
storeFundedTeamReader: readStoreFundedTeam,
isOwnerReader: readTeamOwnership,
),
);
- Inheritance
-
- Object
- ChangeNotifier
- MagicStarterBillingController
Constructors
- MagicStarterBillingController({required MagicStarterUsageCopy usageCopy, required MagicStarterNumberFormat formatNumber, MagicStarterStoreFundedTeamReader? storeFundedTeamReader, MagicStarterTeamOwnershipReader? isOwnerReader, @visibleForTesting BillingService? billingService})
- Creates the billing controller.
Properties
- awaitingProductKey → String?
-
The catalogue key of a store purchase the backend has not confirmed yet,
or
nullwhen nothing is waiting.no setter - billing → BillingService
-
The five entitlement READS: the injected contract when there is one, else
the rail this build resolved through
Payments.billing.latefinal - canPurchase → bool
-
Whether this screen may offer to start or change a paid plan on ANY rail.
no setter
- canPurchaseViaStore → bool
-
Whether this screen may offer to buy through the STORE rail.
no setter
- canPurchaseViaWeb → bool
-
Whether this screen may offer to start or change a paid plan through the
WEB rail.
no setter
- currentPlanId → String?
-
The active plan id, or
nullwhile it is genuinely unknown: before loadEntitlement resolves, and permanently after a failed read.no setter - cycle → BillingCycle?
-
How often the customer is charged, or
nullwhen nothing has said.no setter - entitlementLoaded → bool
-
Whether the live entitlement read has resolved a plan, so currentPlanId
is the customer's real tier rather than an unanswered read.
no setter
- entitlementSnapshot → MagicStarterEntitlementSnapshot
-
The entitlement as the last read answered it. See
MagicStarterEntitlementSnapshot for why it is read as a whole.
no setter
- formatNumber → MagicStarterNumberFormat
-
Renders every integer this screen shows, in the consumer's locale.
final
- hashCode → int
-
The hash code for this object.
no setterinherited
- hasListeners → bool
-
Whether any listeners are currently registered.
no setterinherited
- initialized → bool
-
Whether the controller has been initialized.
no setterinherited
-
invoicePages
→ MagicPaginator<
Invoice> ? -
The paginator behind invoices, or null before the first load.
no setter
-
invoices
→ List<
Invoice> -
The customer's billing history, most recent first. Empty until
loadInvoices resolves; stays empty on a failed read.
no setter
- isDisposed → bool
-
Whether the controller has been disposed.
no setterinherited
- isOwner → bool?
-
Whether the signed-in user owns the team, or
nullwhen that is genuinely unresolved.no setter - isOwnerReader → MagicStarterTeamOwnershipReader?
-
The consumer's ownership check, or
nullwhen the consumer registered none.final - manageUrl → String?
-
The store-management destination the server passed through from the rail,
or
nullwhen the rail reported none (which is always, on Stripe).no setter - manageVia → ManageVia?
-
The management surface the server computed from the rail, or
nullwhile no entitlement read has resolved one.no setter - paymentMethod → PaymentMethod?
-
The card on file and the next renewal date, or
nulluntil loadPaymentMethod resolves.no setter -
plans
→ List<
MagicStarterPlan> -
The plan catalogue, in the order the backend served it (cheapest first).
no setter
- planStatus → PlanStatus
-
Where the paid plan stands in its lifecycle, or
PlanStatus.nonebefore any entitlement read has answered.no setter - pmError → bool
-
Whether loadPaymentMethod failed at the transport level (a network
error, a non-2xx, or a payload this build cannot read).
no setter
- pmLoading → bool
-
Whether loadPaymentMethod is still in flight.
no setter
- portalAvailable → bool
-
Whether the web billing portal is a surface this caller can reach.
no setter
- purchaseContext → PurchaseContext
-
The catalogue facts a store purchase needs to tell an upgrade from a
downgrade: the tiers in the catalogue's own order (cheapest first), the
tier of every product key the rows list, and the tier of every store
product id they name.
no setter
- renews → bool?
-
Whether the subscription will renew at the end of its paid period, or
nullwhile no entitlement read has answered.no setter - runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
- storeCheckRegistered → bool
-
Whether the consumer registered a storeFundedTeamReader at all.
no setter
- storeFundedTeam → String?
-
The NAME of another of the caller's teams that a store account already
funds, or
nullwhen none does and while the read has not resolved.no setter - storeFundedTeamReader → MagicStarterStoreFundedTeamReader?
-
The consumer's cross-team store check, or
nullwhen the consumer registered none.final - storeManaged → bool
-
Whether a store owns this subscription, so the store owns its management
too and nothing here may offer a competing purchase or a web billing
surface.
no setter
-
storeOffers
→ Map<
String, StoreProductOffer> -
What the store charges for each catalogue product, keyed by catalogue key.
no setter
- storeRail → StoreBillingService?
-
The STORE rail, or
nullin a build that cannot serve one.latefinal -
usage
→ List<
UsageStat> -
The current cycle's usage stats, with the consumer's copy already paired
on by usageCopy.
no setter
- usageCopy → MagicStarterUsageCopy
-
Pairs the consumer's display copy onto every
UsageStatthe producer reported. Required; see the class docblock for why it has no default.final - webRail → WebBillingService?
-
The WEB rail, or
nullin a build that cannot serve one.latefinal
Methods
-
addListener(
VoidCallback listener) → void -
Register a closure to be called when the object changes.
inherited
-
Authorize an action against the current
Auth.uservia theGate.inherited -
cancelWait(
) → void - Stops a running wait and releases whoever is awaiting it with MagicStarterStorePurchaseOutcome.abandoned.
-
dispose(
) → void - Runs onClose if it has not run yet, then releases the notifier.
-
load(
) → Future< void> - Dispatches all six reads AT ONCE and resolves when the last one settles.
-
loadEntitlement(
) → Future< void> - Reads the customer's current entitlement and republishes currentPlanId, manageVia, manageUrl and renews.
-
loadInvoices(
) → Future< void> - Reads the first page of the customer's billing history and republishes invoices.
-
loadPaymentMethod(
) → Future< void> - Reads the card on file and republishes paymentMethod.
-
loadPlans(
) → Future< void> - Reads the plan catalogue, decodes each row into a MagicStarterPlan and republishes plans.
-
loadStoreFundedTeam(
) → Future< void> - Asks the consumer's storeFundedTeamReader whether a store account already funds another of the caller's teams, and republishes storeFundedTeam with its name when one does.
-
loadStoreProducts(
) → Future< void> - Asks the store what it charges for every SELLABLE product in plans that has an id in THIS store and republishes storeOffers. A grandfathered product is never offered, and neither is one this store carries no id for, so neither price is asked for.
-
loadUsage(
) → Future< void> - Reads the current cycle's usage, pairs the consumer's copy onto it through usageCopy and republishes usage.
-
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
notifyListeners(
) → void -
Call all the registered listeners.
inherited
-
onClose(
) → void -
Called when the controller is about to be disposed.
inherited
-
onInit(
) → void - Called when the controller is first created.
-
purchaseInStore(
MagicStarterProduct product) → Future< bool> -
Buys
productthrough the STORE rail with this catalogue's purchaseContext, and answers whether the store reported a completed transaction. -
purchaseInStoreAndWait(
MagicStarterProduct product) → Future< MagicStarterStorePurchaseOutcome> -
Buys
productand then waits for the backend to reflect it, which is the whole of what a customer needs from a purchase: a sheet that closed ontrueis the STORE's word, and the plan they see is the backend's. -
refreshUI(
) → void -
Refresh the UI by notifying listeners.
inherited
-
removeListener(
VoidCallback listener) → void -
Remove a previously registered closure from the list of closures that are
notified when the object changes.
inherited
-
resetForSession(
) → Future< void> - Drops every field the six reads populate, publishes the cleared state, then refetches for the identity that is now authenticated.
-
toString(
) → String -
A string representation of this object.
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited