React Native: In-App Purchases on Android

Martin
React Native & Expo
4 min readApr 23, 2024

--

If you’re looking for an article to learn how to integrate purchases into your React Native app, you’ve come to the right publication. You’ll learn how to create items, retrieve and sell items using react-native-iap. Additionally, I’ll show you how to detect when an item purchase has been refunded.

Let’s get started!

The first thing you’ll need to do is set up your project to accept in-app purchases. To do this, we’ll add the BILLING permission and the billingclient library to dependencies.

AndroidManifest.xml

<uses-permission android:name="com.android.vending.BILLING"/>

android/app/build.gradle

implementation "com.android.billingclient:billing:6.2.0"

Now you should upload this version to Google Play from your developer console. I recommend doing it in a test environment, and make sure that the email account you’ll use to download the app and perform tests is among your tester accounts.

Once this version is deployed, let’s create the items. Go to the Google developer console, select the app, and scroll down to Monetization -> Products -> In-app products. Click on create product and fill in the fields. The product ID must be unique, so I recommend using the strategy of putting your application’s package name first, and then something that identifies your product. For example: com.example.yourapp.coins.

Enter a name and a description. Keep in mind that you can handle multiple languages, and when retrieving the item, you’ll receive it in the language configured on the device. Set a price and define whether this item can be purchased multiple times or not. Then click save.

Let’s start with the code!

The first thing we need to do is install the react-native-iap library.

npm install react-native-iap

Then we’ll import it in the code

import { 
getProducts,
purchaseErrorListener,
purchaseUpdatedListener,
initConnection,
requestPurchase,
finishTransaction,
getAvailablePurchases,
withIAPContext } from 'react-native-iap';

We must wrap the screen where we are going to use react-native-iap with withIAPContext

export default withIAPContext(HomeScreen)

With getProducts, we retrieve the created products, for which we must pass an array with the product IDs. Keep in mind that you need to initialize the connection with the initConnection method. You’ll have to create a list to display the products

const productIds = [
YOUR_PRODUCT_ID
];
await initConnection()
const products = await getProducts({skus: productIds})

Create the method to purchase the product where we will call requestPurchase

const buyPack = async (productId) => {
const purchase = await requestPurchase({ skus: [productId] });
}

Now comes the most important part, capturing the state of the requestPurchase. Inside the useEffect, we call the purchaseErrorListener in case the payment encounters any errors and the purchaseUpdatedListener to monitor when payments are made

const purchaseErrorSubscription = purchaseErrorListener((error) => {
Toast.show({
type: 'error',
position: 'bottom',
text1: error?.message
});
});

Within the purchaseUpdatedListener, we’ll handle the status of the payment, which can be pending (2) or paid (1). In the case of being paid, it’s crucial to retrieve the available purchases with getAvailablePurchases and call finishTransaction. If we don’t finish the purchase by calling finishTransaction, the payment remains pending and eventually gets canceled, refunding the money to the customer

const purchaseUpdatedSubscription = purchaseUpdatedListener(
async (purchase) => {
const state = purchase.purchaseStateAndroid;
switch(state) {
case 1:
const availablepurchase = await getAvailablePurchases();
const finishTransactionResult = await finishTransaction({ purchase: availablepurchase[0], isConsumable: false });

Toast.show({
type: 'success',
position: 'bottom',
text1: i18n.t('paymentsuccesfull')
});

break;
case 2: Toast.show({
type: 'info',
position: 'bottom',
text1: i18n.t('pendingpayment')
});
break;
}
}
);

We’ve got the purchase process ready! But what happens if a customer, for whatever reason, requests a refund? How do we handle that refund in the application to revoke the privilege granted with the purchase? Well, we’ll do it by querying the androidpublisher API.

Using fetch, we’ll make a GET request to the URL ‘https://content-androidpublisher.googleapis.com/androidpublisher/v3/applications/{your_app_package_name}/purchases/voidedpurchases'. This call will return an array with the identifiers of voided purchases. So, you simply need to check if the identifier of the purchase that granted the privilege matches one returned by this call, and if so, you’ll need to revoke the privilege.

const voidedPurchase = async (token, productId, acces_token) => {
console.log('cancelled consumido con éxito:');
try {
const response = await fetch(`https://content-androidpublisher.googleapis.com/androidpublisher/v3/applications/com.arekjaar.BeastIptv/purchases/voidedpurchases`, {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + access_token,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
for (let cancel of data?.voidedPurchases) {
const idPurchase = await DB.getIdPurchase()
if (idPurchase === cancel?.orderId) {
removePrivilege()
}
}
}

} catch (error) {
}
}

As you can see in the call, an access_token is required to access the API. In the following article, I explain how to generate a refresh token to then generate an access token to call any Google API

Remember, if you liked the article, you can buy me a coffee ;-)

https://ko-fi.com/arekjaar

Remember to follow the following post for more articles on React Native with Expo

--

--

Martin
React Native & Expo

Experto en integración de aplicaciones con más de 5 años de experiencia con IBM WMB y IIB y en la creación de flujos para Mule ESB con Anypoint Studio