Dark Mode
Image

React Native IAP

The react-native module will help us access the phone's in-app purchase capabilities on Android and iOS platforms of the Amazon platform (beta).

React-native-app provides the basic features we need but is not a fundamental solution. There is a lot of work to do in implementing in-app purchases in the app.

Follow the installation instructions and then go to the Getting Started section to get started right away.

Package limits

Remember, the react-native app will provide us with the basic functionality we need, but it is not a good solution; implementing in-app purchases in your app will still require some work.

Also, we should implement the client-side another one of the coins. It would be best if you implemented server-side validation of receipts.

Installing

Using react-native >0.60

Install the package in your React Native project.

    npm install --save react-native-app  

The package will be linked automatically using auto-linking. Then follow the instructions below, depending on the platform you're working with:

After installation

iOS Platform:

Install cocoa pods: cd ios & pod install

Also, add a swift bridging header if you haven't created one for swift compatibility.

React Native IAP

Android Platform with Android Support:

Modify your android/build.gradle configuration:

buildscript {  
  ext {  
    buildToolsVersion = "28.0.3"  
    minSdkVersion = 21  
    compileSdkVersion = 28  
    targetSdkVersion = 28  
    # Only using Android Support libraries  
    supportLibVersion = "28.0.0"  
  }  
Note: By using the Jetifier tool for backward compatibility.

Android Platform with AndroidX:

Modify the android/build.gradle configuration by the help of code below:

buildscript {  
  ext {  
    buildToolsVersion = "28.0.3"  
    minSdkVersion = 21  
    compileSdkVersion = 28  
    targetSdkVersion = 28  
    # Remove 'supportLibVersion' property and put specific versions for AndroidX libraries  
    androidXAnnotation = "1.1.0"  
    androidXBrowser = "1.0.0"  
    // Put here other AndroidX dependencies  
  }  

You have two options depending on the store you support:

If you only need the Google Play IAP, put it inside the Default Configuration section in Android/App/Build. Gradle:

defaultConfig {  
      ...  
      // react-native-iap: we only use the Google Play flavor  
      missingDimensionStrategy 'store', 'play'  
  }  

If you are using it for Google Play and Amazon, put the following lines inside the android block in android/app/build.gradle.

android {  
  ...  
  flavorDimensions "appstore"  
  productFlavors{  
      googlePlay{  
          dimension "appstore"  
          missingDimensionStrategy "store", "play"  
      }  
      amazon{  
          dimension "appstore"  
          missingDimensionStrategy "store", "amazon"  
      }  
  }  
}  

At the end, we need to enable kotlin from react-native-iap@8.0.0+. Please change the line below in android/build.gradle.

buildscript {  
  ext {  
      buildToolsVersion = "29.0.3"  
+     // Note: Below change is necessary for pause / resume the audio feature. It is aoso Not for Kotlin.  
+     minSdkVersion = 24  
      compileSdkVersion = 29  
      targetSdkVersion = 29  
+     kotlinVersion = '1.5.0'  
  
      ndkVersion = "20.1.5948944"  
  }  
  repositories {  
      google()  
      mavenCentral()  
  }  
  dependencies {  
      classpath("com.android.tools.build:gradle:4.1.0")  
+     classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"  
  }  
...  

Using react-native<=0.60

Follow the steps above and then link the package using:

     native react link native react app  

Upgrade from previous versions

Updating to 6.1.0

On Android, please follow the three steps of the manual installation instructions.

Updating to 3.4.0

Upgrade to the new checkout flow. It is unnecessary to call endConnection in Android, as it is done automatically.

Lifecycle

Initializing.

To initialize a native module, you can call initConnection() at the beginning of your application's lifecycle. It must be done on a top-level component as the library caches the native connection.

Initializing just earlier than you need is discouraged as it affects performance. Calling this method multiple times without terminating the previous connection will result in an error. Not calling this method will cause other calls to be rejected because the connection must be established prematurely.

import * as RNIap from 'react-native-iap';  
componentDidMount() {  
    RNIap.initConnection();  
    ...  

Ending Connection

Call endConnection() when you no longer need interaction with the library to release the resources.

componentWillUnmount() {  
  ...  
  RNIap.endConnection();  
}  

Dos and Don't

You shouldn't be calling initConnection and endConnection every time you want to interact with the library.

It is considered an anti-pattern because it consumes more time and resources and can lead to unwanted side effects, such as multiple callbacks.

DO:

import * as RNIap from 'react-native-iap';  
  
componentDidMount() {  
  await RNIap.initConnection();  
  
  await RNIap.getProducts(productIds)  
  ...  
}  
  
buyProductButtonClick() {  
  //startPurchaseCode  
}  
  
subscribeButtonClick() {  
  //startPurchaseCode  
}  
componentWillUnmount() {  
  ...  
  RNIap.endConnection();  
}  

DON'T:

import * as RNIap from 'react-native-iap';  
componentDidMount() {  
  ...  
}  
const buyProductButtonClick = async() => {  
  await RNIap.initConnection();  
  await RNIap.getProducts(productIds)  
  // Purchase IAP Code  
  ...  
    await RNIap.endConnection();  
}  
const subscribeButtonClick = async() => {  
  await RNIap.initConnection();  
    await RNIap.getProducts(productIds)  
    //Purchase Subscription Code  
    ...  
    await RNIap.endConnection();  
}  
componentWillUnmount() {  
  ...  
}  

Long-lived connections (Android)

Although it is recommended to use hooks and bind the connection lifecycle to a component, you may want to handle connections separately from the UI layer (e.g., Redux Sagas).

You need to take extra care of the connection lifecycle, the connection may break, and the only way to reconnect is to call the RNIap.endConnection() method and then RNIap.initConnection() creates a new internal instance of BillingClient again and reconnects to Play Store services.

You can check the excellent connection by calling the RNIap.isReadyAndroid() method. If it is false, it is necessary to call initConnection.

Retrieve Available Items

First, you should define your product ID for iOS and Android separately, as defined below.

import * as RNIap from 'react-native-iap';  
const productIds = Platform.select({  
  ios: [  
    'com.example.coins100'  
  ],  
  android: [  
    'com.example.coins100'  
  ]  
});  

To get the list of valid objects, call getProducts().

You can do this in componentDidMount() or any other area appropriate for your app.

Since a user can start your application with a bad internet connection and then have an internet connection, preparing the item more than once is good.

For example: if any user doesn't have an IAP available when the app starts, you should check when the user signs in to your IAP store.

async componentDidMount() {  
    try {  
      const products: Product[] = await RNIap.getProducts(productIds);  
      this.setState({ products });  
    } catch(err) {  
      console.warn(err); // standardized err.code and err.message which is available  
    }  
  }  

Each product return from getProducts() contains a Product object.

Making a purchase

Purchase Flow Redesign

The resulting flow is redesigned to not rely on promises or callbacks.

Below are the important reasons for redesign methods:

  • There is more than one response when we request payment.
  • Purchases are asynchronous between the sessions, where requests will take some hours to complete and continue to exist after the application closes or crashes.
  • There are pending purchases, and it will be challenging to track it done.
  • The billing flow is the event pattern is the callback pattern.

Once getProducts()received a valid response, you can call requestPurchase(). Like consumable products, subscribable products will be purchased, and users can unsubscribe using the iOS system settings.

Before requesting any purchase, you have to set BuyUpdatedListener from React-native-app. It is recommended that you start listening for updates as soon as the application starts.

You have to receive successful purchases completed during app shutdown or weren't completed, consumed, or approved due to errors or network failures.

import RNIap, {  
  buyerErrorListener,  
  buyUpdatedListener,  
  write product purchase,  
  write purchase error  
} from 'react-native-iap';  
  
class RootComponent extends Component<*> {  
  purchaseUpdateSubscription = null  
  purchaseErrorSubscription = null  
  
  componentDidMount() {  
    RNIap.initConnection().then(() => {  
             // (ghost = failed pending payment which still marked as pending in Google's native module cache)  
        RNIap.flushFailedPurchasesCachedAsPendingAndroid().catch(() => {  
          // exception occur here   
          // - there is  pending purchases which is still pending (we can't consume the pending purchase)  
          //you cannot want to do special with the error  
        }).then(() => {  
        this.purchaseUpdateSubscription = buyUpdatedListener((purchase: InAppPurchase | SubscriptionPurchase | ProductPurchase ) => {  
          console.log('purchaseUpdatedListener', purchase);  
          receipt const = purchase.transactionReceipt;  
          if (receipt) {  
            yourAPI.deliverOrDownloadFancyInAppPurchase(purchase.transactionReceipt)  
            .then( asynchronous (deliveryresult) => {  
           if (isSuccess(deliveryResult)) {  
                // It tell the store which delivered paid for.  
                // Otherwise, the purchase will be refunded on Android and  
                // the purchase event will reappear on every app restart until it succeeds  
                // It is impossible for the user to purchase.  
                // again you can do it.  
                wait RNIap.finishTransaction(purchase);  
                // Since the react-native-iap@4.1.0 you can simplify the `method` above. Try wrapping the statement with `try` and `catch` to catch the `error` message as well.  
                // If it is consumable (it can be bought again)  
                  expect RNIap.finishTransaction(buy, true);  
                  // If not consumable  
                  expect RNIap.finishTransaction(buy, false);  
                } the rest {  
                  // Retry / conclude that the purchase is fraudulent, etc...  
                }  
              });  
            }  
          });  
  
          this.purchaseErrorSubscription = buyErrorListener((error: PurchaseError) => {  
            console.warn('purchaseErrorListener', error);  
          });  
        })  
      })  
    }  
    componentToUnmount() {  
      if (this.purchaseUpdateSubscription) {  
        this.purchaseUpdateSubscription.remove();  
        this.purchaseUpdateSubscription = null;  
      }  
      if (this.purchaseErrorSubscription) {  
        this.purchaseErrorSubscription.remove();  
        this.purchaseErrorSubscription = null;     
 }  
  }  
}  

Define the method given below and call it the user presses the button.

requestPurchase = async (sku: string) => {  
  try {  
    await RNIap.requestPurchase(sku, false);  
  } catch (err) {  
    console.warn(err.code, err.message);  
  }  
}  
  
requestSubscription = async (sku: string) => {  
  try {  
    await RNIap.requestSubscription(sku);  
  } catch (err) {  
    console.warn(err.code, err.message);  
  }  
}  
  
render() {  
  ...  
    onPress={() => this.requestPurchase(product.productId)}  
  ...  
}  

New Purchase Flow

You may want to manage The "Store Procedure" [[2]] [Apple Store Set Procedure], which occurs when the user checks for a fixed account issue.

For example, when the credit card information expires.

We decided to delete requestPurchase, and instead, it does not depend on the Promise PurchaseUpdateListener function.

  • The purchase is forwarded to the updated listener with the app restart until we complete the purchase.
  • All purchases require a call.finishTransaction()
  • Once the item has been consumed, it will be removed from getAvailablePurchases(), so it's up to you to save the purchases in your database before calling. finishTransaction().
  • Non-consumable purchases are accepted on Android; otherwise, they can return after a few days. It will remember purchases when you deliver them to your users.
  • On iOS, free purchases automatically expire, and it will change in the future so that we can recommend this method for non-consumable items.
  • Equivalent to Finalize for iOS + Consume and Approve buy for Android.

Restore Purchase

You can use getAvailablePurchases() to do what is commonly understood as a "restore" purchase.

If you want to use all objects for debugging, You must duplicate purchases returned by getAvailablePurchases()

Suppose you consume a product without registering the purchase in the database. You can pay for anything without any delivery, and you can never be able to retrieve the receipt when validating and resetting your purchase.

getPurchases = async() => {  
   try {  
     const purchases = wait RNIap.getAvailablePurchases();  
     const newState = { premium: false, ads: true }  
     leave titles restored = [];  
     purchases.forEach(purchase => {  
       change(purchase.productId) {  
         case 'com.example.premium':  
           newStatus.premium = true  
           TitlesRestored.push('Premium Version');  
         case 'com.example.no_ads':  
           newstate.ads = false  
           RestoredTitles.push('No Ads');  
           to break  
         case 'com.example.coins100':  
           expect RNIap.consumePurchaseAndroid(purchase.purchaseToken);  
  
           Coin store.addCoins(100);  
         }  
       })  
       Alert.alert('Successful restore', 'You can successfully restore the purchases: ' + restoreTitles.join(', '));  
     } catch (error) {  
       console.warn(err); // standard err.code and err.message       Alert.alert(err.message);  
  }  
}  

Each item from getAvailablePurchases() contains a AvailablePurchase object.

Validating receipts

Since react-native-iap@0.3.16, we support receipt verification.

With IAPHUB

IAPHUB is a service that handles iOS/Android receipt verification for you. You can configure the webhook to automatically receive notifications on your server about activities such as purchases, subscription renewals...

You can manually call the API to process your receipts or use the module. react-native-iaphub, which is just a gown for react-native-iap with IAPHUB built-in

With GooglePlay

For Android, you need a separate service account JSON file to get access_token from google-API, so it can't be serverless.

It will require the backend and get access_token with the access_token, where we can call the validateReceiptAndroid().

With the app store

local inspection

Local encryption authentication is not supported at this time. More details are here: https://developer.apple.com/documentation/appstorereceipts/validating_receipts_on_the_device.

Check with the app store.

Note: This method is not recommended for production and is explicitly warned by Apple in its documentation:

 

 https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/validating_receipts_with_the_app_store.  

It is suitable for development and receipt verification testing throughout the development cycle.

Verification of app store receipts can be done locally using validateReceiptIos()

You must send a transaction receipt in the first parameter. The second parameter must be passed if it is a test environment. If valid, it will request the sandbox, and false will request production.

const receiptBody = {  
  
  'receipt-data': purchase.transactionReceipt,  
  
  'password': '******' // app shared secret, can be found in App Store Connect  
};  
const result = await RNIap.validateReceiptIos(receiptBody, false);//for result body  
console.log(result);//result  

We need to get the receipt after the purchase. For example, when any user asks permission to buy unstable internet connections.

For the cases, we should getReceiptIOS(), which gets the latest receipt from the app at a given time - the iOS payment the right way.

  • On iOS, you usually get good products during the app launch process.
  • If you fetch to get a valid subscription, the products will be added to the array object on the iOS side (NSMutableArray).
  • Has unexpected behavior when getting a part of the product lists.
  • If we have products of [A, B, C] and we call the lookup function with only [A], this module returns [A, B, C]). But the strange result is strange, so we created a new method that removes the correct products.

If we need to clear all the products and subscriptions in the array, call clearProductsIOS () and do the recovery job again, and we will get what we expected.

Example backend (Node.js)

Here you can find a sample backend for idempotent validation of receipts on iOS/Android and storage and delivery of subscription status to the customer.

How we can use hooks

You have to wrap your app with IAPContext HOC:

import {withIAPContext} from 'react-native-iap';  
const App = () => <View />;  
export default with IAPContext(application); 

Later then, somewhere in the components

import {useIAP} from 'react-native-iap';  
  
const YourComponent = () => {  
  constant {  
    connected,  
    products,  
    Promoted ProductsIOS,  
    subscriptions,  
    purchase histories,  
    shopping available,  
    current purchase,  
    current purchase error,  
    finish transaction,  
    getProducts,  
    get subscriptions,  
    get purchases available,  
    get purchase histories,  
  } = useIAP();  
  
  return <View />;  
};   

Reference

Since react-native-iap@6.0.0+, we support the IAP hook, which handles purchases better.

Comment / Reply From