Use this page with AI

Copy this into your coding assistant and add your request.

Read https://openiap.dev/docs/setup/react-native and https://openiap.dev/llms.txt. Follow the reading instructions, detailed reference, and linked guides relevant to my task before making changes.
Inspect my existing project and reuse its framework and conventions. Ask me for missing product decisions. Implement the requested behavior and run the applicable checks.
Show the working result, the commands and actual test results, and any remaining limitations. Keep your explanation brief.

My request: [describe what customers should be able to do]
See an example request →

React Native Setup

react-native-iap provides in-app purchase support for React Native apps using Nitro Modules, a high-performance native bridging layer for React Native. It supports StoreKit 2 on iOS and Google Play Billing 9.1.0+ on Android by default, with optional build flavors for Horizon OS (Meta Quest) and Fire OS (Amazon Appstore).

react-native-iap v15+ is for bare React Native CLI projects only and requires React Native 0.79+ (Nitro Modules), iOS 15.0+ (StoreKit 2), and Android minSdkVersion 23+ with compileSdkVersion 36+. Expo support ended in v15.0.0 — use expo-iap instead; it provides the same API on Expo Modules.

Installation#

react-native-iap requires react-native-nitro-modules as a peer dependency — install both together.

sh
# Using yarn (recommended)
yarn add react-native-iap react-native-nitro-modules

# Using npm
npm install react-native-iap react-native-nitro-modules

Nitro Modules (v15+)#

react-native-iap v15+ is built on Nitro Modules for high-performance native bridging. Key points:

  • Requires React Native 0.79+ — Nitro relies on the new native module architecture
  • Native modules are automatically linked during your app's build process

If you hit Swift 6 C++ interop errors in Nitro, see Troubleshooting for the Swift 5 language mode workaround.

iOS#

  • Install CocoaPods:
    sh
    cd ios && bundle exec pod install
  • Enable In-App Purchase capability in Xcode: Target > Signing & Capabilities > + Capability > In-App Purchase
  • When linking with the iOS 27 SDK, migrate older React Native host templates from an AppDelegate-owned UIWindow to a UIWindowSceneDelegate. See the Xcode 27 UIScene checklist.

Android#

  • No additional native configuration needed
  • Uses Google Play Billing 9.1.0+ with automatic service reconnection
  • Store-specific Android targets — Horizon OS (Meta Quest) and Fire OS (Amazon Appstore) — ship as separate build flavors. Complete the setup on this page first, then follow Store Setup for target-specific Gradle, manifest, and runtime details. Vega OS is not an Android flavor — see Vega OS below.

Vega OS#

Vega OS is Amazon's newer, non-Android operating system; its apps run on the Kepler runtime. react-native-iap supports it through a separate React Native for Vega target — it is not an Android build flavor, and unlike expo-iap there is no config plugin to enable it. Keep the Amazon Kepler packages in that Vega-only target so regular iOS and Android builds are unaffected, and follow Amazon Store Setup for package, manifest, and supported-version details.

Usage#

Under the hood, the typical flow is initConnection → set up purchaseUpdatedListener and purchaseErrorListener fetchProducts requestPurchase finishTransaction, with endConnection only when the app-level owner shuts down or signs out. The useIAP hook manages listener setup and removes its listeners when the component unmounts, while keeping the native store connection available across screens. See the Purchase Guide for the complete flow.

useIAP Hook (Recommended)#

The useIAP hook is the recommended way to use react-native-iap. It manages listener lifecycle, connection state, and error normalization automatically. Unmounting the hook removes its listeners without ending the app-level native store connection.

ts
import React, { useEffect } from 'react';
import { Alert, Button, FlatList } from 'react-native';
import { useIAP, ErrorCode, finishTransaction } from 'react-native-iap';

function Store() {
  const {
    connected,
    products,
    fetchProducts,
    requestPurchase,
  } = useIAP({
    onPurchaseSuccess: (purchase) => {
      // 1. Validate receipt with your backend or IAPKit
      // 2. Grant entitlement
      // 3. CRITICAL: Finish the transaction
      //    (Android auto-refunds after 3 days if not called!)
      void finishTransaction({
        purchase,
        isConsumable: false, // true for consumables
      }).catch((error) => {
        console.warn('Transaction finalization failed:', error);
      });
    },
    onPurchaseError: (error) => {
      if (error.code === ErrorCode.UserCancelled) return;
      Alert.alert('Purchase Failed', error.message);
    },
  });

  useEffect(() => {
    if (!connected) return;
    void fetchProducts({ skus: ['premium', 'coins_100'] }).catch((error) => {
      console.warn('Product fetch failed:', error);
    });
  }, [connected, fetchProducts]);

  return (
    <FlatList
      data={products}
      keyExtractor={(product) => product.id}
      renderItem={({ item }) => (
        <Button
          title={`${item.title} - ${item.displayPrice}`}
          disabled={!connected}
          onPress={() => {
            void requestPurchase({
              request: {
                apple: { sku: item.id },
                google: { skus: [item.id] },
              },
              type: 'in-app',
            }).catch((error) =>
              console.warn('Purchase request failed:', error),
            );
          }}
        />
      )}
    />
  );
}

Each call here has a full reference — see fetchProducts, requestPurchase, and finishTransaction for parameters and per-store behavior, and ErrorCode for the full error reference.

Most useIAP methods return Promise<void> and update internal state — use the onPurchaseSuccess callback to receive purchase results, not the return value of requestPurchase.

Hook State#

After calling methods, consume state from the hook:

Root API (Advanced)#

For advanced use cases without React state management, you can use the root API directly with event listeners:

ts
import {
  initConnection,
  endConnection,
  fetchProducts,
  requestPurchase,
  finishTransaction,
  purchaseUpdatedListener,
  purchaseErrorListener,
  ErrorCode,
} from 'react-native-iap';

// Initialize
const connected = await initConnection();
if (!connected) throw new Error('Store connection failed');

// Listen for events BEFORE requesting purchases
const purchaseSub = purchaseUpdatedListener((purchase) => {
  // Validate on server, then finish transaction.
  // CRITICAL: Android auto-refunds after 3 days if not called!
  // Use isConsumable: true for consumables.
  void finishTransaction({ purchase, isConsumable: false }).catch((error) => {
    console.warn('Transaction finalization failed:', error);
  });
});

const errorSub = purchaseErrorListener((error) => {
  if (error.code === ErrorCode.UserCancelled) return;
  console.error(error.message);
});

// Fetch and purchase
const products = await fetchProducts({ skus: ['premium'] });
await requestPurchase({
  request: {
    apple: { sku: 'premium' },
    google: { skus: ['premium'] },
  },
  type: 'in-app',
});

// Cleanup on unmount
purchaseSub.remove();
errorSub.remove();
const ended = await endConnection();
if (!ended) console.warn('Store teardown did not complete');

Each call here has a full reference — see initConnection, fetchProducts, requestPurchase, finishTransaction, and endConnection for parameters and per-store behavior, and purchaseUpdatedListener and purchaseErrorListener for the purchase events.

Error Handling#

Errors are automatically normalized to the ErrorCode enum. Use the provided helper functions:

ts
import {
  ErrorCode,
  isUserCancelledError,
  getUserFriendlyErrorMessage,
} from 'react-native-iap';

// In useIAP onPurchaseError callback:
if (isUserCancelledError(error)) return;

const message = getUserFriendlyErrorMessage(error);
Alert.alert('Error', message);

// Or use switch for specific handling:
switch (error.code) {
  case ErrorCode.NetworkError:
    showRetryDialog();
    break;
  case ErrorCode.ItemUnavailable:
    showUnavailableMessage();
    break;
}

Troubleshooting#

Products not found

  • Ensure all agreements are signed in App Store Connect / Google Play Console
  • Verify banking, legal, and tax information is complete and approved
  • Check that bundle ID / package name matches exactly
  • Products must be in "Ready to Submit" status (Apple) or "Active" (Google)
  • Wait 15-30 minutes after creating products before testing

Build errors

  • iOS: Run cd ios && bundle exec pod install
  • Android: Ensure compileSdkVersion 36+ in android/build.gradle
  • Metro bundler issues: yarn start --reset-cache

Folly coroutine error ('folly/coro/Coroutine.h' not found)

If your iOS build fails with 'folly/coro/Coroutine.h' file not found from RCT-Folly/folly/Expected.h, add these defines to your Podfile post_install block:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)']
      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'FOLLY_NO_CONFIG=1'
      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'FOLLY_CFG_NO_COROUTINES=1'
      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'FOLLY_HAS_COROUTINES=0'
    end
  end
end

Swift 6 C++ interop errors (Nitro)

If you see errors in AnyMap.swift related to cppPart.pointee, switch the NitroModules pod to the Swift 5 language mode (SWIFT_VERSION = '5.0') as a temporary workaround:

# ios/Podfile - add inside post_install block
post_install do |installer|
  installer.pods_project.targets.each do |target|
    if target.name == 'NitroModules'
      target.build_configurations.each do |config|
        config.build_settings['SWIFT_VERSION'] = '5.0'
      end
    end
  end
end

Next Steps#