Skip to content

In‑app purchases (IAP)

FRVR.iap abstracts over Google Play, Facebook Instant, Apple iAP, and other channel billing backends. You describe a catalog once; each channel maps your product IDs onto its own store.

FRVR.config.iap = {
  catalog: {
    COINS_100:  { label: '100 coins',  storeId: 'com.mygame.coins_100' },
    COINS_500:  { label: '500 coins',  storeId: 'com.mygame.coins_500' },
    REMOVE_ADS: { label: 'Remove ads', storeId: 'com.mygame.remove_ads' },
  },
};
  • The key (COINS_100) is what your code uses.
  • storeId is what the current channel’s store calls the product.
  • label is a human name you can show in UI if you don’t already have one.

After FRVR.init() and the first bootstrap tick, the catalog is ready:

const catalog = FRVR.iap.getCatalog();
// { COINS_100: { label, storeId, price, currency, … }, … }

const item = FRVR.iap.getProductById('COINS_100');
// { label, storeId, price, currency, description, … }

Price/currency come from the underlying store. Use them — don’t hard‑code prices.

FRVR.iap.purchase('COINS_100', { meta: 'optional_game_context' })
  .then(purchase => grantCoins(100, purchase))
  .catch(err => {
    if (err instanceof FRVR.iap.errors.IAPPurchaseErrorCancelledByUser) return;
    if (err instanceof FRVR.iap.errors.IAPPurchaseErrorAlreadyOwned)    return ownedFlow();
    console.error('purchase failed', err);
  });

Error types:

ErrorMeaning
IAPPurchaseErrorCancelledByUserUser closed the sheet. Not an error you show.
IAPPurchaseErrorAlreadyOwnedDurable already owned by this account.
IAPPurchaseErrorUnknownProductWrong product ID.
IAPErrorGeneric fallback. Inspect .code.

Consumables (coins, gems) are bought over and over. After you grant the content, consume the purchase so the store frees the user to buy again:

FRVR.iap.purchase('COINS_100')
  .then(purchase => {
    grantCoins(100);
    return FRVR.iap.consumePurchase(purchase);   // returns the purchaseId
  });

Durables (ad removal, character skins) are never consumed. To re‑grant them on a new device:

FRVR.iap.restorePurchases().then(purchases => {
  for (const p of purchases) applyDurable(p.productId);
});

If your app crashed between purchase and content delivery, the store keeps the receipt in a pending state. On every startup:

FRVR.iap.getUnconsumedPurchases().then(purchases => {
  purchases.forEach(p => {
    grantCoins(lookupAmount(p.productId));
    FRVR.iap.consumePurchase(p);
  });
});

This closes the loop even if the user force‑killed the app mid‑purchase.

dev channel ships a mock catalog and a fake purchase flow. Try buy → consume → restore.

IAP: catalog, purchase, consume, restore

loading SDK…
 
MethodPurpose
isReady() / onReady(cb)Provider readiness.
getCatalog()All products keyed by your catalog IDs.
getProductById(id)One product, including store price.
purchase(id, meta?)Start checkout. Resolves with a purchase receipt.
consumePurchase(purchase)Mark a consumable as delivered.
restorePurchases()Re‑emit durables owned by the user.
getUnconsumedPurchases()Unfinished consumables — call at startup to recover.
errors.*Error classes to instanceof‑check.