/react-native-iap

react native in app purchase module.

Primary LanguageJavaOtherNOASSERTION

react-native-iap

npm version npm version

This is a react-native link library project for in app purchase for both android and ios platforms. The goal for this project is to have similar experience between the two platforms for in-app-purchase. Basically android platform has more functions for in-app-purchase and is not our specific interests for this project.

We are willing to share same in-app-purchase experience for both android and ios platform and will continuously merge methods which are standing alone.

Android iap is implemented with iap version 3 which is currently recent.

Playstore & Itunnesconnect configuration

  • Please refer to Blog.

Important

react-native-iap module versions that are not described in change logs may not run as expected so please refer to version mentioned in Changelogs below.

Migration Guide

To migrate 0.2.* to 0.3.*, You can follow below guide.

0.2.* 0.3.*
prepareAndroid prepare
getItems getProducts
getSubscribeItems getSubscriptions
getPurchasedItemsAndroid getPurchaseHistory
`` getAvailablePurchases
buySubscribeItem buySubscription
buyItem buyProduct
consumeItemAndroid consumePurchase
refreshAllItems Not Available
refreshPurchaseItemsAndroid Not Available

From above method changes, getProducts gets itemSkus as parameter in different way then as used in getItems. In getItems you had to put parameter as

const itemSkus = {
  ios: [
	'point_1000',
  ],
  android: [
    'point_1000',
  ],
};

But now you should do like below which will just pass single array instead of object.

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

Also, note that this is our last migration for renaming method names without any deprecation warning. Thank you for your understanding.

Breaking Changes

0.3.0-alpha1 has released. All the methods are renamed and current methods are merged into each single method. See Methods section below to see what's been changed.

Breaking changes have made from 0.2.17. refreshAllItems has changed name to fetchHistory. See the changelogs below.

Breaking changes have made from 0.2.16 in android. Package name has been fixed to com.dooboolab.RNIap.RNIapPackage. Read the changelogs below. There was linking issue with wrong package name.

Breaking changes have made from 0.2.12. Please read the changelogs below. The summary of change is that it now returns receipt in different format.

Changes from react-native-iap@0.1.* to react-native-iap@0.2.* is that you have prepare() method deprecated which you should call before using RNIap methods. Now you have to call prepareAndroid() instead just to know that it is just android dependent method. Also to import module, previously in react-native-iap@0.1.* you had to import RNIap from 'react-native-iap' but now you have to do like import * as RNIap from 'react-native-iap'.

For new method, refreshAllItems has been implemented for both ios and android. This feature will support senario for non-consumable products. Also there are some other methods that is not supported in ios and implemented in android. You can see more in Changelogs below. Lastly, this module also supports types for typescript users from 0.2.5.

Changelogs

  • [0.3.1]
    • Fixed linking manual dependency in ios from PR.
    • Fixed returning localizedPrice when need actual price in Android from ISSUE.
    • Fixed other minor bugs relied on ios.
    • Some purchasing senarios have been tested throughly.
  • [0.3.0-alpha1]
    • Methods names are fully renamed to avoid the confusion. Current methods are prepare, getProducts, getSubscriptions, getPurchaseHistory, getAvailablePurchases, buySubscription, buyProduct, consumeProduct. Please compare these methods with your previous methods used in 0.2.* if you want to upgrade to 0.3.0.
  • [0.2.17]
    • refreshAllItems has changed name to fetchHistory since android and ios had different functionality and fixed to fetching history of purchases.
  • [0.2.16]
    • Changed android package name com.reactlibrary.RNIapPackage to com.dooboolab.RNIap.RNIapPackage;.
  • [0.2.15]
    • Removed react dependency in pod(deprecated). Handle android buySubscribeItem callback.
  • [0.2.14]
    • Improve typings with JSDoc.
  • [0.2.13]
    • buyItem will now return object instead string. The receipt string will be result.data and signature is added in result.signature. Currently ios signature will be always empty string.
  • [0.2.12]
    • Added signiture to android purchase. From this version, the verification string for json string after purchasing will be receipt.data instead of receipt itself because of changes in here. We will apply this changes to ios too so you do not have to handle these two differently.
  • [0.2.11]
  • [0.2.9]
    • Android catch error message when IAP service not prepared during refreshAllItems.
  • [0.2.8]
    • homepage now is mandatory attribute in cocoapods from pull request.
  • [0.2.7]
    • Android buyItem cancel callback.
  • [0.2.6]
    • Android buyItem method do not consume item right away from 0.2.6.
  • [0.2.5]
    • types support. alt text
    • call new Method for android inside refreshItems(). This will now return object values like ios.
  • [0.2.3]
    • Support annotations to hint while using our module.
  • [0.2.0]
    • Implemented senario for consumable and non-consumable item.
    • Seperated methods that only exists in IOS and Android.
      • prepareAndroid()
      • refreshPurchaseItemsAndroid(type: string)
      • getPurchasedItemsAndroid(type: string)
      • consumeItemAndroid(token: string)
    • Able to call prepareAndroid() function without any conditional statement like if (Platform.OS === 'android'). Just use it.
    • Updated Readme.
  • [0.1.10]
    • Fixed potential bug relied on preparing IAP module in Android. Updated readme to see how to use it.

Methods

Func Param Return Description
prepare Promise<void> Prepare IAP module. Must be called on Android before any other purchase flow methods. No-op on iOS.
getProducts string[] Product IDs/skus Promise<Product[]> Get a list of products (consumable and non-consumable items, but not subscriptions). Note: On iOS versions earlier than 11.2 this method will return subscriptions if they are included in your list of SKUs. This is because we cannot differentiate between IAP products and subscriptions prior to 11.2.
getSubscriptions string[] Subscription IDs/skus Promise<Subscription[]> Get a list of subscriptions. Note: On iOS versions earlier than 11.2 this method has the same output as getProducts. This is because we cannot differentiate between IAP products and subscriptions prior to 11.2.
getPurchaseHistory Promise<Purchase[]> Gets an invetory of purchases made by the user regardless of consumption status (where possible)
getAvailablePurchases Promise<Purchase[]> Get all purchases made by the user (either non-consumable, or haven't been consumed yet)
buySubscription string Subscription ID/sku Promise<Purchase> Create (buy) a subscription to a sku
buyProduct string Product ID/sku Promise<Purchase> Buy a product
consumeProduct string Purchase token Promise<void> Consume a product (on Android.) No-op on iOS.

Npm repo

https://www.npmjs.com/package/react-native-iap

Git repo

https://github.com/dooboolab/react-native-iap

Getting started

$ npm install react-native-iap --save

Mostly automatic installation

$ react-native link react-native-iap

Note for Ejected iOS Apps:

The above command will add the following to your Podfile:

pod 'RNIap', :path => '../node_modules/react-native-iap'

You should remove this before running pod install and follow the manual installation instructions below.

Manual installation

iOS

  1. In XCode, in the project navigator, right click LibrariesAdd Files to [your project's name]
  2. Go to node_modulesreact-native-iap and add RNIap.xcodeproj
  3. In XCode, in the project navigator, select your project. Add libRNIap.a to your project's Build PhasesLink Binary With Libraries
  4. Run your project (Cmd+R)<

Android

  1. Open up android/app/src/main/java/[...]/MainActivity.java
  • Add import com.dooboolab.RNIap.RNIapPackage; to the imports at the top of the file
  • Add new RNIapPackage() to the list returned by the getPackages() method
  1. Append the following lines to android/settings.gradle:
    include ':react-native-iap'
    project(':react-native-iap').projectDir = new File(rootProject.projectDir, 	'../node_modules/react-native-iap/android')
    
  2. Insert the following lines inside the dependencies block in android/app/build.gradle:
      compile project(':react-native-iap')
    
  3. Add the following to the <permission> block in android/app/src/main/AndroidManifest.xml:
    <uses-permission android:name="com.android.vending.BILLING" />
    

Usage

You can look in the RNIapExample folder to try the example. Below is basic implementation which is also provided in RNIapExample project.

Prepare IAP, In App Billing.

First thing you should do is to define your items for iOS and android separately like defined below.

import * as RNIap from 'react-native-iap';

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

Next, call the prepare function (ios it's not needed, but android it is. No need to check platform though since nothing will happen in ios:

async function() {
  try {
    await RNIap.prepare();
    // Ready to call RNIap.getProducts(), etc.
  } catch(err) {
    console.warn(err); // standardized err.code and err.message available
  }
}

Get Valid Items

Once you called prepare(), call getProducts(). Both are async funcs. You can do it in componentDidMount(), or other area as appropriate for you app. Since a user may first start your app with a bad internet connection, then later have an internet connection, making preparing/getting items more than once may be a good idea. Like if the user has no IAPs available when the app first starts, you may want to check again when the user enters the your IAP store.

async componentDidMount() {
  try {
    await RNIap.prepare();
    const products = await RNIap.getProducts(itemSkus);
    this.setState({ items });
  } catch(err) {
    console.warn(err); // standardized err.code and err.message available
  }
}

Each item is a JavaScript object containing these keys:

iOS Android Comment
price Will return localizedPrice on Android (default) or a string price (eg. 1.99) (iOS)
productId Returns a string needed to purchase the item later
currency Returns the currency code
localizedPrice Use localizedPrice if you want to display the price to the user so you don't need to worry about currency symbols.
title Returns the title Android and localizedTitle on iOS
description Returns the description of the product
type Returns SKU type (subscription or in-app product). iOS < 11.2 will always return null

Purchase

Once you have called getProducts(), and you have a valid response, you can call buyProduct().

  // Will return a purchase object with a receipt which can be used to validate on your server.
  const purchase = await RNIap.buyProduct('com.example.coins100');

In RNIapExample, upon receiving receiving a purchase receipt, main page will navigate to Second.js.

Purchase Example 2 (Advanced)

this.setState({ progressTitle: 'Please wait...' });
RNIap.buyProduct('com.example.coins100').then(purchase => {
    this.setState({
      receipt: purchase.transactionReceipt, // save the receipt if you need it, whether locally, or to your server.
      progressTitle: 'Purchase Successful!',
      coins: this.state.coins + 100
    });
  }).catch(err => {
    // resetting UI
    console.warn(err); // standardized err.code and err.message available
    this.setState({ progressTitle: 'Buy 100 Coins for only $0.99' });
    alert(err.message);
  })

Subscribable products can be purchased just like consumable products. Users can cancel subscriptions by using the iOS System Settings.

Consumption and Restoring Purchases

You can use getAvailablePurchases() to do what's commonly understood as "restoring" purchases. Once an item is consumed, it will no longer be available in getAvailablePurchases() and will only be available via getPurchaseHistory(). However, this method has some caviats on Android -- namely that purchase history only exists for the single most recent purchase of each SKU -- so your best bet is to track consumption in your app yourself. By default all items that are purchased will not be consumed unless they are automatically consumed by the store (for example, if you create a consumable item for iOS.) This means that you must manage consumption yourself. Purchases can be consumed by calling consumePurchase(). If you want to consume all items, you have to iterate over the purchases returned by getAvailablePurchases().

getPurchases = async() => {
  try {
    const purchases = await RNIap.getAvailablePurchases();
    let restoredTitles = '';
    let coins = CoinStore.getCount();
    purchases.forEach(purchase => {
      if (purchase.productId == 'com.example.premium') {
        this.setState({ premium: true });
        restoredTitles += 'Premium Version';
      } else if (purchase.productId == 'com.example.no_ads') {
        this.setState({ ads: false });
        restoredTitles += restoredTitles.length > 0 ? 'No Ads' : ', No Ads';
      } else if (purchase.productId == 'com.example.coins100') {
        CoinStore.addCoins(100);
        await RNIap.consumePurchase(purchase.transactionReceipt);
      }
    })
    Alert.alert('Restore Successful', 'You successfully restored the following purchases: ' + restoredTitles);
  } catch(err) {
    console.warn(err); // standardized err.code and err.message available
    Alert.alert(err.message);
  }
}

Returned purchases is an array of each purchase transaction with the following keys:

{
  transactionDate,
  transactionId,
  productId,
  transactionReceipt,
  purchaseToken, // available on Android (same as transactionReceipt)
  autoRenewing, // available on Android
  originalTransactionDate, // available on iOS
  originalTransactionIdentifier // available on iOS
}

You need to test with one sandbox account, because the account holds previous purchase history.

Todo

  • Add introductory price as in issue.
  • Local receipt validation as in issue.

Contribution Guide

Issue

  • Please search and register if you already have the issue you want to create. If you have a similar issue, you can add additional comments.
  • Please write a problem or suggestion in the issue. Never include more than one item in an issue.
  • Please be as detailed and concise as possible.
    • If necessary, please take a screenshot and upload an image.

Pull request(PR)

  • Do not modify the code in the master branch.
  • PR allows only the dev branch.
  • It is useful to use a topic branch that has the parent dev as its parent.

Coding Guidelines

Please follow the Coding conventions as much as possible when contributing your code.

  • The indent tab is two spaces.
  • The class declaration and the {} in curly brackets such as function, if, foreach, for, and while should be in the following format. Also if you installed eslint in vscode or in your code editor, it will help you with linting.
    • { should be placed in same line and } should be placed in next line.
for (let i = 0; i < 10; i++) {
  ...
}
array.forEach((e) => {
  ...
});
  • Space before ( and after ).
  • If you find code that does not fit in the coding convention, do not ever try to fix code that is not related to your purpose.

LICENSE

The MIT License (MIT)

Copyright (c) 2017 dooboolab

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.