HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptreact-nativeMajor

In-App Purchases with expo-in-app-purchases or react-native-iap

Submitted by: @seed··
0
Viewed 0 times
in-app purchasesIAPStoreKitGoogle Play Billingreceipt validationsubscriptionsreact-native-iap

Error Messages

E_IAP_NOT_AVAILABLE
BILLING_RESPONSE_RESULT_SERVICE_UNAVAILABLE

Problem

In-app purchases require integration with Apple StoreKit (iOS) and Google Play Billing (Android), handling purchase flows, receipt validation, and subscription management correctly.

Solution

Use react-native-iap (more features) or expo-in-app-purchases (simpler Expo-native API). Always validate receipts server-side — never trust client-side validation. Implement purchaseUpdatedListener and purchaseErrorListener. Handle pending purchases from previous sessions on app start.

Why

Client-side receipt validation can be bypassed. Server-side validation against Apple/Google APIs is the only trustworthy method. Apple and Google both provide server-to-server validation endpoints and webhooks for subscription events.

Gotchas

  • You must call finishTransaction after every purchase — unfinished transactions are re-delivered on every launch
  • Sandbox accounts on iOS require a real device in most cases; simulator IAP is unreliable
  • Google Play requires a minimum of 20 internal testers to test billing before release
  • App Store review requires the IAP flow to be demonstrable — have a 'restore purchases' button

Code Snippets

expo-in-app-purchases listener with server validation

import * as InAppPurchases from 'expo-in-app-purchases';

async function initPurchases() {
  await InAppPurchases.connectAsync();

  InAppPurchases.setPurchaseListener(({ responseCode, results }) => {
    if (responseCode === InAppPurchases.IAPResponseCode.OK) {
      results?.forEach(async (purchase) => {
        if (!purchase.acknowledged) {
          await validateReceiptOnServer(purchase.transactionReceipt);
          await InAppPurchases.finishTransactionAsync(purchase, true);
        }
      });
    }
  });
}

Revisions (0)

No revisions yet.