MagicStarterBillingView class

The plan and billing screen.

One page over the six independent reads MagicStarterBillingController publishes: the tier the team holds, the catalogue it can move to, what it has spent this cycle, its billing history, the card on file, and where the subscription is managed.

It resolves its controller, it does not construct one

MagicStatefulViewState resolves the controller through Magic.find, and the registry builder below is zero-argument, so nothing on the render path can supply a collaborator. The consumer therefore registers the controller itself before this route is reached:

Magic.put(
  MagicStarterBillingController(
    usageCopy: withUsageCopy,
    formatNumber: formatCount,
    storeFundedTeamReader: readStoreFundedTeam,
    isOwnerReader: readTeamOwnership,
  ),
);

That is also why every consumer-supplied thing this screen renders through (the usage copy, the number format) is a REQUIRED parameter on the controller rather than on this widget: a view parameter would need a default at the registration site, and both of the defaults available there are the wrong answer shipped silently.

Page chrome comes from MSPageScaffold

Never hand-rolled. The scaffold routes the page through the host's one MSPageContainer geometry, so this screen lines up with every other page in the app rather than centring at its own width.

The four axes that gate every affordance

The rail, never the running platform. Which management surface this screen offers comes from MagicStarterBillingController.manageVia, which the producer computes from the rail that sold the subscription. The two are independent: a subscription bought on an iPhone is still managed in the App Store when its owner opens the web app, so a branch on the RUNNING platform (a kIsWeb check, a dart:io host test, the framework's target-platform value) would offer the wrong surface to a real customer.

The owner, for anything that spends money. The billing write routes are the account owner's server-side, so a member sees the plan grid read-only with an owner-only notice instead of a call to action it would take a 403 to discover. The gate is tri-state (see MagicStarterBillingController.isOwner): only a KNOWN non-owner loses the call to action, since an unresolved membership must not stand between an owner and paying.

The rail this BUILD can serve, for anything it has to call. The purchase-affecting calls live on WebBillingService and StoreBillingService, each of which resolves to null where the build cannot serve it, and no build serves both. That absence gates the checkout call to action, all three portal affordances and the whole store purchase surface, and it is a different question from manage_via: one asks where the customer's subscription is managed, the other whether this binary has an implementation to invoke.

A configured web origin, for the checkout redirects. A hosted checkout session needs ABSOLUTE success and cancel urls, and MagicStarterConfig.billingWebOrigin deliberately carries no default. An unset origin therefore hides the checkout call to action rather than building a relative url the rail refuses, because that refusal arrives as a BillingException whose message goes to the log (see _MagicStarterBillingViewState._reportBillingFailure) and the adopter would never learn which config key they forgot.

One store account funds exactly one team

Store tiers share a subscription group so that upgrade and downgrade work, and a store account holds at most one active subscription per group. So a second purchase from the same account does not open a second subscription: it TRANSFERS the one that exists and silently stops funding the team that had it. Before offering to buy, this screen asks the consumer's cross-team check and refuses by NAME when another team is already funded, then asks AGAIN at the tap (see _MagicStarterBillingViewState._purchaseInStore).

Inheritance

Constructors

MagicStarterBillingView({Key? key})
Creates the billing view.
const

Properties

hashCode → int
The hash code for this object.
no setterinherited
key → Key?
Controls how one widget replaces another widget in the tree.
finalinherited
runtimeType → Type
A representation of the runtime type of the object.
no setterinherited

Methods

createElement() → StatefulElement
Creates a StatefulElement to manage this widget's location in the tree.
inherited
createState() → State<MagicStarterBillingView>
Creates the mutable state for this widget at a given location in the tree.
override
debugDescribeChildren() → List<DiagnosticsNode>
Returns a list of DiagnosticsNode objects describing this node's children.
inherited
debugFillProperties(DiagnosticPropertiesBuilder properties) → void
Add additional properties associated with the node.
inherited
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
toDiagnosticsNode({String? name, DiagnosticsTreeStyle? style}) → DiagnosticsNode
Returns a debug representation of the object that is used by debugging tools and by DiagnosticsNode.toStringDeep.
inherited
toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) → String
A string representation of this object.
inherited
toStringDeep({String prefixLineOne = '', String? prefixOtherLines, DiagnosticLevel minLevel = DiagnosticLevel.debug, int wrapWidth = 65}) → String
Returns a string representation of this node and its descendants.
inherited
toStringShallow({String joiner = ', ', DiagnosticLevel minLevel = DiagnosticLevel.debug}) → String
Returns a one-line detailed description of the object.
inherited
toStringShort() → String
A short, textual description of this widget.
inherited

Operators

operator ==(Object other) → bool
The equality operator.
inherited