Skip to main content

Implementing In-App Purchases

1. Setting Up In-App Purchase Libraries

Setting up in-app purchase libraries in React Native involves integrating platform-specific solutions for iOS (StoreKit) and Android (Google Play Billing). The most popular cross-platform solution is react-native-iap, which provides a unified API for both platforms.

Library Platform Support Installation Command Notes
react-native-iap Android, iOS npm install react-native-iap Officially supported by the React Native community; requires native setup for iOS and Android
react-native-store Android, iOS npm install react-native-store Lightweight alternative, less actively maintained

Example: Installing react-native-iap

npm install react-native-iap

Example: Linking Native Dependencies (iOS)

cd ios && pod install
Note: For iOS, you must also configure App Store Connect and set up the in-app purchase products in the Apple Developer portal before using this library.

2. Configuring Products

Property Description Values
productId Unique identifier for the product string
type Product type (consumable, non-consumable, subscription) consumable, non-consumable, subscription
title Product title displayed to the user string
description Product description displayed to the user string
price Price of the product in local currency string
currency ISO currency code (e.g., USD, EUR) string

Example: Product Configuration

const products = [
  {
    productId: 'com.example.product1',
    type: 'consumable',
    title: 'Extra Life',
    description: 'Restore one life in the game',
    price: '$1.99',
    currency: 'USD'
  },
  {
    productId: 'com.example.subscription',
    type: 'subscription',
    title: 'Premium Membership',
    description: 'Unlimited access to all features',
    price: '$9.99',
    currency: 'USD'
  }
];
Note: Product IDs must be unique and match exactly what is configured in the app store (Apple App Store or Google Play Console).

3. Fetching Available Products

Example: Fetching Product Details

import { getProducts } from 'react-native-iap';

const fetchProducts = async () => {
  try {
    const products = await getProducts(['product_1', 'product_2']);
    console.log('Fetched products:', products);
  } catch (error) {
    console.error('Error fetching products:', error);
  }
};

fetchProducts();
Property Description Notes
productIdentifier Unique identifier for the product Must match store listing
title Product title as shown in store Localized string
description Product description in store Localized string
price Price in local currency Formatted string
currency ISO currency code E.g., USD, EUR
type Product type (consumable, non-consumable) See product types table
Note: Ensure all product identifiers are registered in the app store before fetching.
Warning: Do not hardcode product IDs; fetch them dynamically to support localization and updates.

Product Types Overview

  • Consumable: Can be purchased multiple times (e.g., in-game currency)
  • Non-Consumable: Purchased once, persists across app reinstalls (e.g., premium features)
  • Subscription: Recurring payment for access (e.g., monthly magazine)

4. Initiating Purchase Flow

Method Description Parameters Notes
purchaseItem() Initiates the purchase flow for a specific item productId, quantity Must be called after getProducts() to ensure item is valid
purchaseItemWithQuantity() Initiates the purchase flow with a specified quantity productId, quantity Supports in-app items with quantity selection

Example: Initiating Purchase Flow

import { purchaseItem } from 'react-native-in-app-purchase';

const handlePurchase = async () => {
  try {
    const result = await purchaseItem('com.example.product1', 1);
    console.log('Purchase successful:', result);
  } catch (error) {
    console.error('Purchase failed:', error);
  }
};
Note: Ensure the user has sufficient funds and the item is available before initiating purchase.

5. Handling Purchase Verification

Property Description Value
transactionId Unique identifier for the transaction string
productId Identifier of the purchased product string
transactionDate Date and time of the transaction string
transactionReceipt Receipt data for verification string

Example: Verify Purchase

import { purchaseUpdated, purchaseError } from 'react-native-iap';

const verifyPurchase = async (transaction) => {
  const receipt = transaction.transactionReceipt;
  const response = await fetch('https://your-server.com/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ receipt })
  });
  const verified = await response.json();
  return verified;
};

purchaseUpdated(async (transaction) => {
  const isVerified = await verifyPurchase(transaction);
  if (isVerified.valid) {
    // Grant product access
  } else {
    // Handle invalid purchase
  }
});
Note: Always verify purchases on your server to prevent fraud.
Warning: Do not rely solely on client-side verification.

6. Restoring Purchases

Restoring purchases allows users to recover previously bought in-app items, particularly useful for iOS devices where purchases are tied to the user's Apple ID.

Property Description Notes
restorePurchases Method to restore previously purchased items Only available on iOS; Android returns empty list
productIdentifier Unique identifier for the product to restore Required for restoring specific purchases

Example: Restore Purchases

import { restorePurchases } from 'react-native-iap';

const handleRestore = async () => {
  try {
    const purchases = await restorePurchases();
    console.log('Restored purchases:', purchases);
  } catch (error) {
    console.error('Restore failed:', error);
  }
};
Note: On Android, restorePurchases only works for consumable items and returns a list of purchases that were made in the last 30 days.
Warning: On iOS, restoring purchases does not automatically re-deliver content. You must implement logic to verify purchase receipts and restore user entitlements manually.

7. Implementing Subscriptions

Property Description Notes
productType subscription Indicates the product is a subscription
subscriptionPeriod day, week, month, year Duration of the subscription period
gracePeriodDays 7 Number of days a user can still access content after subscription expires

Example: Subscription Product Configuration

const subscriptionProduct = {
  id: "premium_monthly",
  type: "subscription",
  period: "month",
  price: "$9.99",
  currency: "USD",
  description: "Monthly premium access"
};

Example: Purchase Subscription

import { purchase } from 'react-native-iap';

const handlePurchase = async () => {
  try {
    const purchaseResult = await purchase({
      productType: "subscription",
      productId: "premium_monthly"
    });
    console.log("Purchase successful:", purchaseResult);
  } catch (error) {
    console.error("Purchase failed:", error);
  }
};
Note: Subscriptions must be managed through the respective app store (Apple App Store or Google Play Store) and require proper configuration in the developer console.

8. Handling Subscription Status

Property Description Values
status Current status of the subscription active, expired, grace_period, inactive
renewalDate Date when the subscription will renew ISO 8601 date string
isInGracePeriod Indicates if the subscription is in a grace period true, false

Example: Check Subscription Status

import { purchaseManager } from 'react-native-iap';

const checkSubscriptionStatus = async () => {
  try {
    const purchases = await purchaseManager.getAvailablePurchases();
    const subscription = purchases.find(purchase => purchase.productId.includes('premium'));

    if (subscription) {
      console.log('Subscription Status:', subscription.status);
      console.log('Renewal Date:', subscription.renewalDate);
      console.log('Grace Period:', subscription.isInGracePeriod);
    }
  } catch (error) {
    console.error('Error checking subscription status:', error);
  }
};
Note: The subscription status can change based on user actions or external events like payment failures.

9. Testing In-App Purchases

Example: Setting up Purchase Testing

import { Platform } from 'react-native';
import { RNIap, purchaseUpdatedListener, purchaseErrorListener } from 'react-native-iap';

const itemSkus = Platform.select({
  ios: ['com.example.product1'],
  android: ['com.example.product1'],
});

const initIAP = async () => {
  try {
    await RNIap.initConnection();
    const products = await RNIap.getProducts(itemSkus);
    console.log('Products:', products);
  } catch (error) {
    console.error('IAP init error:', error);
  }
};

initIAP();
Platform Testing Method Notes
iOS StoreKit Test Configuration Use Xcode's StoreKit configuration to simulate purchases
Android Google Play Console Internal Testing Use internal test track to test purchases before release
Note: Ensure all test products are configured in the respective app stores before testing.

Testing Workflow

  1. Set up test user accounts in the app store (e.g., Google Play Console or Apple Developer)
  2. Configure test products in the store dashboard
  3. Use RNIap.initConnection() to connect to the store
  4. Retrieve product information using RNIap.getProducts()
  5. Simulate purchase flow with test users

Testing vs Production

  • Testing: Use sandbox environments and test users
  • Production: Real payments, live user accounts

10. Managing Purchase Errors

Error Code Description Handling Recommendation
1 PURCHASING_ERROR General purchasing error. Log the error and notify user to try again.
2 NETWORK_ERROR Network connectivity issue. Prompt user to check internet connection.
3 INVALID_PRODUCT_ERROR Product ID not found. Validate product IDs in app and backend.
4 USER_CANCELLED_ERROR User canceled the purchase. No action required, but can log for analytics.
5 USER_NOT_ALLOWED_ERROR Parental controls or user restrictions. Inform user about settings.
6 STORE_ERROR Store-specific error. Retry purchase or show store status page.

Example: Error Handling in Purchase Flow

import { purchase } from 'react-native-in-app purchases';

const handlePurchase = async (productId) => {
  try {
    const result = await purchase(productId);
    if (result && result.transactionId) {
      // Successful purchase logic
    }
  } catch (error) {
    switch (error.code) {
      case 1:
        console.error('General purchasing error:', error.message);
        // Notify user to try again
        break;
      case 2:
        console.error('Network error:', error.message);
        // Prompt user to check internet
        break;
      case 3:
        console.error('Invalid product:', error.message);
        // Validate and log product ID
        break;
      case 4:
        console.log('User cancelled purchase');
        // Log for analytics
        break;
      case 5:
        console.error('User not allowed:', error.message);
        // Inform about parental controls
        break;
      case 6:
        console.error('Store error:', error.message);
        // Show store status or retry
        break;
      default:
        console.error('Unexpected error:', error);
    }
  }
};
Note: Always implement retry logic for NETWORK_ERROR and STORE_ERROR to improve user experience.

Was this article helpful?