DEV Community

Cover image for Best React Native in-app subscription libraries
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Best React Native in-app subscription libraries

Written by Emmanuel John✏️

Implementing in-app subscription purchases (IASP) is one of the key ways you can earn money from your mobile application. With IAP, you can easily monetize your app’s features, and content, and even offer digital products for sale.

React Native offers different libraries built on top of both Apple’s StoreKit framework and the Google Play Billing Library, which helps simplify IAP integration in mobile apps. In this article, we’ll explore a selection of some of the best IAP libraries for implementing in-app subscriptions in your React Native application.

Jump ahead:

Choosing the right React Native in-app purchase subscription library

When deciding on which IAP library to use in your application, there are several factors you need to consider, including the following:

  • Documentation and support: You should generally go for libraries that have up-to-date and comprehensive documentation to ensure a smooth integration process. Additionally, check if there is an active support channel or community that will assist you in case of any issues or questions
  • Features offered: Assess the features provided by a library and determine if they align with your requirements. For instance, you may need a library that can handle receipt validation, dynamic product listings, refunds, and so on
  • Security and compliance: Consider the security measures a library’s developer has put in place to ensure adequate protection of user data, secure payment processing, and adherence to relevant store guidelines
  • Flexibility: Assess whether the library is flexible in terms of localization, UI customization, pricing models, and integration with third-party tools
  • Restoration and synchronization: Consider whether the library supports restoring purchases across devices and platforms. This feature allows users to regain access to previously purchased content or services when switching devices or reinstalling the application

react-native-iap

react-native-iap is a popular IAP library developed and maintained by dooboolab. It supports in-app subscription implementation for the App Store, Play Store, and Amazon Appstore.

react-native-iap provides a set of Hooks and exposes a set of APIs for processing product purchases and subscriptions, listening for completed and failed transactions, and fetching your product listing.

However, react-native-iap does not support receipt validation, so you must verify transactions on your backend.

Prior to integrating the react-native-iap library, you need to configure and create your product catalog with unique identifiers in the respective stores; react-native-iap uses these IDs to fetch your product listing.

You can install react-native-iap through npm:

npm install react-native-iap
Enter fullscreen mode Exit fullscreen mode

Afterward, follow the installation instructions outlined in the documentation for both Android and iOS to complete the configuration process.

Example usage:

import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View, Platform, Pressable } from 'react-native';
import {
  initConnection,
  endConnection,
  finishTransaction,
  flushFailedPurchasesCachedAsPendingAndroid,
  purchaseUpdatedListener,
  purchaseErrorListener,
  getProducts,
  requestPurchase,
} from 'react-native-iap'
const App = () => {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    const initializeConnection = async () => {
      try {
        await initConnection();
        if (Platform.OS === "android") {
          await flushFailedPurchasesCachedAsPendingAndroid();
        }
      } catch (error) {
        console.error("An error occurred", error.message);
      }
    }
    const purchaseUpdate = purchaseUpdatedListener(
      async (purchase) => {
        const receipt = purchase.transactionReceipt;

        if (receipt) {
          try {
            await finishTransaction({ purchase, isConsumable: true });
          } catch (error) {
            console.error("An error occurred", error.message);
          }
        }
      });

    const purchaseError = purchaseErrorListener((error) =>
      console.error('Purchase error', error.message));
    initializeConnection();
    purchaseUpdate();
    purchaseError();
    fetchProducts();
    return () => {
      endConnection();
      purchaseUpdate.remove();
      purchaseError.remove();
    }
  }, []);

  const fetchProducts = async () => {
    try {
      const products = await getProducts({
        skus: Platform.select({
          ios: ['com.rniap.product10', 'com.rniap.product20'],
          android: ['com.rniap.product100', 'com.rniap.product200'],
        })
      });
      setProducts(products);
    } catch (error) {
      console.error("Error occurred while fetching products", error.message);
    }
  };
  const makePurchase = async (sku) => {
    try {
      requestPurchase({ sku })
    } catch (error) {
      console.error("Error making purchase", error.message);
    }
  }

  return (
    <View style={styles.container}>
      {
        products.map((product) => (
          <View key={product.productId} style={styles.row}>
            <Text>{product.title}</Text>
            <Pressable onPress={() => makePurchase(product.productId)}>Buy Product</Pressable></View>
        ))
      }
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 10
  },
  row: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 16,
    marginEnd: 20
  }
});
export default App;
Enter fullscreen mode Exit fullscreen mode

This code snippet sets up the necessary connections and listeners for in-app purchases, fetches products from the store, and allows the user to make purchases by pressing a button for each product displayed on the screen.

The initializeConnection function initializes the in-app purchase connection using the initConnection function. If the platform is Android, it also flushes any failed purchases that are cached as pending using flushFailedPurchasesCachedAsPendingAndroid.

The purchaseUpdate function is a listener that is triggered when a purchase is updated. It calls the finishTransaction function to finish the transaction after a successful purchase. The purchaseError function is a listener that is triggered when a purchase error occurs.

react-native-adapty

The react-native-adapty library allows you to integrate in-app subscriptions into your React Native applications seamlessly. It provides a comprehensive set of features you‘d typically need to manage subscriptions, such as A/B testing, free trials, refunds, renewals, upgrades, downgrades, one-time purchases, and lifetime subscriptions. react-native-adapty can be used in both Expo-managed and native React Native workflows.

In addition, it offers an intuitive dashboard where you can track performance metrics, view users’ payment history, customize UI elements, and manage product listings without releasing new versions of your application.

To begin using react-native-adapty, you need to create an Adapty account and then register your application, after which you will get your public SDK key for use in your application. You can install the react-native-adapty SDK using Yarn:

yarn add react-native-adapty
Enter fullscreen mode Exit fullscreen mode

After installation, follow the instructions described in the documentation to complete installation for both Android and iOS.

Example usage:

import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View, Pressable } from 'react-native';
import { adapty } from 'react-native-adapty';
const App = () => {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    const initialize = async () => {
      try {
        await adapty.activate("PUBLIC_SDK_KEY", {
          customerUserId: "USER_ID",
          logLevel: 'ERROR',
        });
      } catch (error) {
        console.error("An error occurred while activating adapty", error.message);
      }
    }

    initialize();
    fetchPaywall("paywall_id");
  }, []);

  //pawywallId is the id specified when creating a paywall in the adapty dashboard
  const fetchPaywall = async (paywallId) => {
    try {
      const paywallResult = await adapty.getPaywall(paywallId);
      const productResult = await adapty.getPaywallProducts(paywallResult);
      setProducts(productResult);
    } catch (error) {
      console.error("Error occured while fetching paywall", error.message);
    }
  };
  const makePurchase = async (product) => {
    try {
      await adapty.makePurchase(product)
    } catch (error) {
      console.error("Error making purchase", error.message);
    }
  }

  return (
    <View style={styles.container}>
      {
        products.map((product) => (
          <View key={product.vendorProductId} style={styles.row}>
            <Text>{product.localizedTitle}</Text>
            <Pressable onPress={() => makePurchase(product)}>Buy Product</Pressable></View>
        ))
      }
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 10
  },
  row: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 16,
    marginEnd: 20
  }
});
export default App;
Enter fullscreen mode Exit fullscreen mode

Use the adapty.makePurchase method enables in-app purchases. The fetchPaywall function is an asynchronous function that fetches the paywall using the adapty.getPaywall method and then fetches the paywall products using the adapty.getPaywallProducts method.

The initialize function is an asynchronous function that activates the Adapty SDK using the adapty.activate method. It takes a public SDK key, customer user ID, and log level as parameters.

To use this code, you need to have the necessary dependencies installed, including the Adapty SDK. Additionally, you need to replace:

  • "PUBLIC_SDK_KEY" with your actual Adapty SDK public key
  • "USER_ID" with the customer user ID
  • "paywall_id" with the ID of the paywall you want to fetch

expo-in-app-purchases

expo-in-app-purchases is a lightweight IAP library created by Expo, and is basically a wrapper for both the Apple StoreKit framework and Google Play Billing Library. It exposes a simple set of APIs for retrieving product listings, fetching user purchase history, handling purchases, and so on. It’s important to note that the library’s development is currently on hold at the time of writing this article.

Before using the expo-in-app-purchases library, you should configure in-app purchases and product listing details in App Store Connect and the Google Play console.

Install the library through npm:

npm install expo-in-app-purchases
Enter fullscreen mode Exit fullscreen mode

If you’re using an Expo-managed workflow, you will also have to create a development build.

After installation, run the following command to perform additional configuration for iOS:

npx pod-install
Enter fullscreen mode Exit fullscreen mode

Example usage:

import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View, Pressable, Platform } from 'react-native';
import * as InAppPurchases from 'expo-in-app-purchases';

const storeItems = Platform.select({
  ios: ['com.rniap.product10', 'com.rniap.product20'],
  android: ['com.rniap.product100', 'com.rniap.product200'],
});
const App = () => {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    const initializeConnection = async () => {
      try {
        await InAppPurchases.connectAsync();
      } catch (error) {
        console.error('Error initializing connection', error.message);
      }
    }
    initializeConnection();
    InAppPurchases.setPurchaseListener(purchaseListener);
    fetchProducts();
    return () => {
      InAppPurchases.disconnectAsync();
    }
  }, []);
  const purchaseListener = ({responseCode, results, errorCode}) => {
    if(responseCode === InAppPurchases.IAPResponseCode.OK) {
      results.forEach(purchase => {
        if(!purchase.acknowledged) {
          //Process transaction and unlock content for user
          InAppPurchases.finishTransactionAsync(purchase, true);
        }
      });
    }
  }

  const fetchProducts = async () => {
    try {
      const { responseCode, results } = await InAppPurchases.getProductsAsync(storeItems);
      if(responseCode === InAppPurchases.IAPResponseCode.OK) {
        setProducts(results);
      } 
    } catch (error) {
      console.error("Error occured while fetching products", error.message);
    }
  };
  const makePurchase = async (productId) => {
    try {
      await InAppPurchases.purchaseItemAsync(productId);
    } catch (error) {
      console.error("Error making purchase", error.message);
    }
  }

  return (
    <View style={styles.container}>
      {
        products.map((product) => (
          <View key={product.productId} style={styles.row}>
            <Text>{product.title}</Text>
            <Pressable onPress={() => makePurchase(product.productId)}>Buy Product</Pressable></View>
        ))
      }
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 10
  },
  row: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 16,
    marginEnd: 20
  }
});
export default App;
Enter fullscreen mode Exit fullscreen mode

The makePurchase function is an asynchronous function that makes a purchase using the purchaseItemAsync method from the expo-in-app-purchases library. If an error occurs, it is logged to the console. getProductsAsync method from the expo-in-app-purchases fetches the products from the store. The initializeConnection function is an asynchronous function that attempts to connect to the in-app purchase service.

To use this code, you need to have the necessary dependencies installed, including expo-in-app-purchases. Additionally, you need to set up in-app purchases for your app on both the iOS and Android platforms, and configure the product IDs accordingly.

react-native-purchases

react-native-purchases is a user-friendly IAP library developed and maintained by RevenueCat. It is a wrapper around the StoreKit framework, Google Play Billing Library, and the RevenueCat backend. However, if you’re using an Expo-managed workflow, you will need to create a development build as well.

It has built-in support for receipt validation, subscription status tracking, analytics metrics, and webhooks, which you can use to set up notifications for specific purchase events. In addition, you can remotely configure your product listings from your dashboard, view users' transaction history, and easily integrate with third-party analytic tools.

To get started with integrating react-native-purchases in your application, you first need to sign up for a RevenueCat account and also set up a new project, and then proceed to add your application, after which you will obtain an API key for use in your project.

You can install the react-native-purchases SDK through npm:

npm install react-native-purchases
Enter fullscreen mode Exit fullscreen mode

After installation, you also have to perform some additional configuration for iOS. Additionally, remember to also include the “Billing” permission in your AndroidManifest.xml file.

<uses-permission android:name="com.android.vending.BILLING" />
Enter fullscreen mode Exit fullscreen mode

Example usage:

import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View, Pressable } from 'react-native';
import Purchases from 'react-native-purchases';
const App = () => {
  const [packages, setPackages] = useState([]);

  useEffect(() => {
    Purchases.setLogLevel(Purchases.LOG_LEVEL.ERROR);
    const initialize = () => {
      Purchases.configure({
        apiKey: "API_KEY",
        appUserID: null,
        observerMode: false,
        useAmazon: false
      });
    }
    initialize();
    fetchPackages();
  }, []);

  const fetchPackages = async () => {
    try {
      const offerings = await Purchases.getOfferings()
      if(offerings.current !== null) {
        setPackages(offerings.current.availablePackages)
      }
    } catch (error) {
      console.error("Error occured while fetching packages", error.message);
    }
  };
  const makePurchase = async (pkg) => {
    try {
      const {customerInfo} = await Purchases.purchasePackage(pkg);
      if(typeof customerInfo.entitlements.active["ENTITLEMENT_ID"] !== 'undefined') {
        //do something
      }
    } catch (error) {
      if(!error.userCancelled) {    
      console.error("Error making purchase", error.message);
      }
    }
  }

  return (
    <View style={styles.container}>
      {
        packages.map((pkg) => (
          <View key={pkg.identifier} style={styles.row}>
            <Text>{pkg.title}</Text>
            <Pressable onPress={() => makePurchase(pkg)}>Buy Product</Pressable></View>
        ))
      }
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 10
  },
  row: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 16,
    marginEnd: 20
  }
});
export default App;
Enter fullscreen mode Exit fullscreen mode

This code sets up a React Native app with a purchases integration for in-app purchases. It initializes purchases, fetches the available packages, and provides a UI to display and purchase those packages.

A call to Purchases.purchasePackage initiates the purchase process. Purchases.getOfferings fetches the offerings. The initialize function configures purchases with the API key, appUserID, observerMode, and useAmazon properties using Purchases.configure. If an error occurs during configuration, it is logged to the console.

react-native-qonversion

react-native-qonversion is another notable IAP library that allows you to swiftly incorporate in-app subscriptions into your application, eliminating the need to set up a backend for receipt validation — this is all handled by Qonversion.

You can also send users personalized push notifications triggered by purchase events, discover optimal pricing options via A/B tests, view detailed subscription analytics, share subscription data with your favorite analytics tool, handle both App Store and Play Store purchases, and so on.

To begin integrating react-native-qonversion, you need to create an account and proceed to create a project and register your application. After that, you can get your project key for use in your application. Also, don’t forget to create your IAP products on App Store Connect and the Google Play console!

The react-native-qonversion library can be installed through npm:

npm install react-native-qonversion
Enter fullscreen mode Exit fullscreen mode

However, if you’re developing with Expo, you need to create a development build as well.

Example usage:

import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View, Pressable } from 'react-native';
import Qonversion, {QonversionConfigBuilder, LaunchMode, Entitlement} from 'react-native-qonversion'
const config = new QonversionConfigBuilder(
  'PROJECT_KEY',
  LaunchMode.SUBSCRIPTION_MANAGEMENT
).build();
const App = () => {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    const initialize = () => {
      Qonversion.initialize(config);
    }
    initialize();
    fetchProducts();
  }, []);

  const fetchProducts = async () => {
    try {
      const offerings = await Qonversion.getSharedInstance().offerings();
      if(offerings.main != null && offerings.main.products.length > 0) {
        setProducts(offerings.main.products);
      }
    } catch (error) {
      console.error("Error occured while fetching products", error.message);
    }
  };
  const makePurchase = async (product) => {
    try {
      const entitlements = await Qonversion.getSharedInstance().purchaseProduct(product);
      if(entitlements !== null && entitlements['premium'].isActive) {
        //do something
      }
    } catch (error) {
      if(!error.userCanceled) {    
      console.error("Error making purchase", error.message);
      }
    }
  }

  return (
    <View style={styles.container}>
      {
        products.map((product) => (
          <View key={product.qonversionID} style={styles.row}>
            <Text>{product.qonversionID}</Text>
            <Pressable onPress={() => makePurchase(product)}>Buy Product</Pressable></View>
        ))
      }
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 10
  },
  row: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 16,
    marginEnd: 20
  }
});
export default App;
Enter fullscreen mode Exit fullscreen mode

This code sets up a React Native app with Qonversion integration for subscription management. It initializes the Qonversion SDK, fetches the available products, and provides a UI to display and purchase those products.

The QonversionConfigBuilder object is created using the 'PROJECT_KEY' and 'LaunchMode.SUBSCRIPTION_MANAGEMENT' values. This configures the Qonversion SDK with the appropriate project key and sets the launch mode to subscription management.

A call to Qonversion.getSharedInstance().purchaseProduct(product) initiates the purchase process. Qonversion.getSharedInstance().offerings() fetches the product offerings.

Comparison summary

The following table shows an overview of the features of each of the highlighted IAP libraries:

Library react-native-iap react-native-adapty expo-in-app-purchases react-native-purchases react-native-qonversion
Stores supported Apple App Store, Google Play Store, Amazon Appstore Apple App Store and Google Play Store Apple App Store and Google Play Store Apple App Store, Google Play Store, and Amazon Appstore Apple App Store and Google Play Store
Receipt validation No Yes No Yes Yes
Supports A/B tests No Yes No Yes Yes
No-code paywall No Yes No No No
Dynamic paywall No Yes No Yes Yes
Third-party integrations No Yes No Yes Yes
Pricing Free Free up to $10,000 monthly tracked revenue (MTR) Free Free up to $10,000 MTR Free up to $1,000 MTR

Conclusion

In-app purchase is an important method of app monetization. When incorporating IAP in React Native applications, several libraries stand out for their robust features, flexibility, and cross-platform support. These libraries simplify the process of integrating IAP functionality and offer various capabilities such as purchasing products, verifying receipts, listing products, managing subscriptions, and so on.

By harnessing the features provided by these libraries, you can streamline the integration of in-app purchases in your React Native application, enabling effective app monetization and enhancing the overall user experience.


LogRocket: Instantly recreate issues in your React Native apps.

LogRocket Signup

LogRocket is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.

LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.

Start proactively monitoring your React Native apps — try LogRocket for free.

Top comments (0)