Image 1: Preparing for an in-app purchase

In-app purchases! A way, among others, to make money from your mobile application. Probably not the cheapest option out there, in terms of 3rd party commission, but it stands out as a native option for your apps.

Let’s get started

In order to make payments work there is a 2 step process we need to follow:

Configure the in-app product in Google Play Developers’ Console and choose the type of charge (one time fee or recurring subscription) along with its cost. Code it inside the app with the help of a bridge package like react-native-billing

Let’s move on and see how to configure an in-app product of one time charge.

Open the Google Play Console

As we login with our account to Google Play Console, we choose the application we want to configure and from the vertical left menu we navigate to Store presence -> In-app products:

Image 2: In-app products screen for an app in Google Play dev’s console

In this app we have setup 3 products. Each product consists of a set of properties that we first need to configure. In order to setup the new product, we click the top right button called Create Managed Product. A new screen opens that allows us to add the new product as shown below:

Image 3a: Mandatory properties to be configured for an Android in-app product — part a

Image 3b: Mandatory properties to be configured for an Android in-app product — part b

As you can see the mandatory fields include: name, id, description, status and price:

We need to have a unique product id, so that Google knows which product we are referring to from within our app.

Status means if the product is eligible to be purchased or not. Inactive ones cannot be purchased. We turn this to active as soon as we start testing our products and before the app goes live.

The price is the net value (without VAT) that we want to charge. The basic price is set to the currency of the locale that your account is configured to. In our example we show a UK account and a net value of 2.87 GBP. On top of this price, Google will automatically add the country’s VAT, which is 20% for UK. If your app is available in other countries as well, you can choose whether to sell or not your products there and Google will automatically perform the conversions/calculations needed. If we take Finland as an example the net value of the product is converted to EUR (3.22 EUR), while the country’s VAT of 24% (0.77 EUR) is added automatically; the final price of the product becomes 3.99 EUR as shown in image 3b. From this amount, Google deducts 30% transaction fees and in some countries it deducts the VAT automatically as well.

And that’s it! We save our first product and we are ready to code it within our app!

Let’s code this product!

We are going to be using react-native-billing library. Installation steps are few and easy to follow, so you should be up and running with it very fast.

Retrieve the product’s price

The first thing we need to do within our app is to get the prices of our newly created product. Price is retrieved from Google Play and through an API call, as we just configured it over the previous steps. We can retrieve this information within componentDidMount method as shown below:

async componentDidMount() {

try {

// make sure the service is close before opening it

await InAppBilling.close();

await InAppBilling.open(); // product with Google Play id: gems.pack.500

const details = await InAppBilling.getProductDetails('gems.pack.500');

this.gemsPack.priceText = details.priceText;

} catch (error) {

// debug in device with the help of Alert component

} finally {

await InAppBilling.close();

}

}

Now let’s elaborate on what we see above:

First we need to open the connection with Google Play and when we are finished, we should close it. As an extra level of safety we should close the connection before each new call to Google Play, just to make sure we will not be stuck to a previous session.

Then we use the product id we created before, in order to identify the product we are requesting information for. Within the response, we get the product’s price in a form of text (i.e. ‘3.99 EUR’).

In case we encounter an error here, we will need to debug in a real device. Emulator does not work for real products.

Finally, pay attention to the fact that your screen will first render and the prices will return asynchronously on a second time. That being said, you either need to show a loading component until the API call returns or you need to retrieve the prices earlier in your app and pass them as props to the “Shop” screen.

The actual purchase

Now in order to purchase the gems package (or any other product), we need to execute the following function when the user clicks the “purchase” button:

buyGoogleProduct = async id => {

const response = await new Promise((resolve, reject) => {

let repurchaseTries = 0;

const maxRepurchaseTries = 2; const buyInAppProduct = async () => {

try {

const purchase = await InAppBilling.purchase(id);

resolve(purchase);

} catch (error) {

if (error.message === 'Purchase or subscribe failed with error: 102') {

if (repurchaseTries >= maxRepurchaseTries) {

reject(new Error(`Failed to purchase ${id} after ${maxRepurchaseTries} retries.`));

} else {

repurchaseTries += 1;

buyInAppProduct();

}

} else if (error.message === 'Purchase or subscribe failed with error: 6') {

// Communicate to the user that the payment was declined

} else if (error.message === 'Purchase or subscribe failed with error: 1') {

// Communicate to the user that the payment was cancelled

}

}

};

buyInAppProduct();

}); let result;

if (response.purchaseState === 'PurchasedSuccessfully') {

result = response;

} else {

result = Promise.reject(new Error(response.purchaseState));

}

return result;

}

Here we create a custom promise, which we use to perform the real purchase and also handle all the known — and unknown — errors.

The actual purchase takes place in line:

const purchase = await InAppBilling.purchase(id);

and that is the moment where the user sees the native modal:

Image 4: Native Google purchase modal in Greek language

The rest of the code takes care of the error situations:

error code 6: payment was declined (i.e. insufficient funds). Make sure to notify the user with a proper error message error code 1: payment was cancelled (i.e. when the user presses the Android Back Button). Again make sure to notify with proper message error code 102: a bug either from the package we use or the original one it wraps; purchases fail the 1st time and this why we count repurchaseTries and invoke buyInAppProduct twice. It is a known issue that we work around successfully Other unknown errors that might come up

Create your custom APIs

In order to make use of the aforementioned method, you should create your own custom API call(s) and encapsulate it inside them. This needs to happen for 2 reasons:

You probably need to give the user something in exchange for the purchase he made (in this case it’s gems). Your back end code must perform validation of the incoming payment with Google Play. I was surprised to find out that there are many tools out there that fake the purchases, giving the opportunity to users who use them, to get your services for free.

Let’s see an example:

buyGems = async () => {

try {

// make sure the service is close before opening it

await InAppBilling.close();

await InAppBilling.open(); // perform the purchase

const purchase = await this.buyGoogleProduct('gems.pack.500'); // Parse Google Play response data and update BE for payment

// validation

const args = {

productName: 'PACK',

receiptData: purchase.receiptData.replace(/"/g, "'"),

receiptSignature: purchase.receiptSignature

};

const response = await buyGemsCustomAPI(args); // update user gems in-app state

this.props.saveUser({gems: response.data.userBuyGems.gems}); // consume Android store product

await InAppBilling.consumePurchase(this.state.productId);



} catch (error) {

// If an error that is not handled occurs show a generic message

} finally {

// close connection

await InAppBilling.close();

}

}

We first invoke the buyGoogleProduct method by supplying the proper product id and then we invoke our own custom API method buyGemsCustomAPI . As arguments to this method, we provide an internal productName , along with the receiptData and the receiptSignature as received from API call response. The latter 2 values are needed for payment validation in our back end server.

Finally, we consume the purchase for the specific product type, so that we allow the user to buy again gems instantly (i.e. for monthly recurring service we should not consume the purchase until the month is over in order not to allow the user to re-purchase something that he has already paid for).

How do my sales look?

In order to see how many sales you are performing, open Google Play Console and before you choose an app, navigate from the vertical menu bar the “order management” option:

Image 5: Order management screen in Google Play Console

What do you think?

What do you think about this article and the proposed solution? Have you tried any other library for Android in-app purchases in RN apps? Feel free to offer your perspective and ideas to the comments section below.