loadStoreProducts method

Future<void> loadStoreProducts()

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.

A store build shows the store's own localised price and not the catalogue's figure: the store decides currency, tax and rounding, and App Review rejects a screen whose price disagrees with the purchase sheet. One request for every key rather than one per card, because the answer is needed by the whole grid at once.

Skipped on a build with no store rail, and when the catalogue names no product. Deliberate degradation on failure, and not a silent one: storeOffers keeps what it held, the card falls back to saying the price is shown in the store, and the reason goes to the log.

Implementation

Future<void> loadStoreProducts() async {
  final int session = _sessionGeneration;
  final int readToken = _latestRead.begin(_storeProductsKey);

  try {
    // Inside the try for the reason [loadStoreFundedTeam] gives: resolving a
    // rail can itself throw.
    final StoreBillingService? store = storeRail;
    if (store == null) return;

    final ManageVia thisStore = store.store;
    final List<String> keys = <String>[
      for (final MagicStarterPlan plan in _plans)
        for (final MagicStarterProduct product in plan.storeProducts(
          thisStore,
        ))
          product.key,
    ];
    if (keys.isEmpty) return;

    final Map<String, StoreProductOffer> offers = await store.products(keys);
    if (session != _sessionGeneration) return;
    if (!_latestRead.isCurrent(readToken, _storeProductsKey)) return;

    _storeOffers = offers;
    refreshUI();
  } catch (error) {
    if (session != _sessionGeneration) return;
    if (!_latestRead.isCurrent(readToken, _storeProductsKey)) return;
    _reportDegradation('storeProducts', error);
  }
}