purchaseInStoreAndWait method
Buys product and then waits for the backend to reflect it, which is the
whole of what a customer needs from a purchase: a sheet that closed on
true is the STORE's word, and the plan they see is the backend's.
- The entitlement is snapshotted BEFORE the sheet opens: afterwards the answer may already have moved.
- A dismissed sheet ends it. A thrown
BillingExceptionpropagates exactly as purchaseInStore throws it, and a PENDING one still polls in the background, so the gate reopens the moment the backend reflects the purchase rather than only when the window runs out. - When the rail says the change applies at renewal
(
StoreBillingService.lastChangeTiming), the entitlement is NOT expected to move and polling would only run out the clock. The wait flag is released and the screen reports the pre-sheet period end. The rail's answer is the only one asked: it knows what the store will do, and a guess from the catalogue's tier order told a customer their plan changed later when the store changed it now. - Anything else, an unknown timing included, polls on
_pollBackoff(60 s in all) until the snapshot differs.
Implementation
Future<MagicStarterStorePurchaseOutcome> purchaseInStoreAndWait(
MagicStarterProduct product,
) async {
// 1. What the backend said BEFORE the sheet. A purchase already waiting is
// refused by [purchaseInStore] with the same pending code a store
// reports, and that refusal must not start a second poll.
final MagicStarterEntitlementSnapshot before = _entitlementSnapshot;
final bool alreadyWaiting = _awaitingProductKey != null;
// 2. The sheet.
final bool bought;
try {
bought = await purchaseInStore(product);
} catch (error) {
if (!alreadyWaiting && _isPending(error)) {
unawaited(_awaitEntitlementChange(before));
}
rethrow;
}
if (!bought) return MagicStarterStorePurchaseOutcome.dismissed;
// 3. Nothing to poll for.
if (storeRail?.lastChangeTiming == StoreChangeTiming.atRenewal) {
_clearAwaiting();
return MagicStarterStorePurchaseOutcome.deferred;
}
// 4. Poll.
return _awaitEntitlementChange(before);
}