In-app purchase in Android using Ionic 4
In this post, you’ll learn how to implement the famous (and infamous) In-App Purchase functionality in Android apps using Ionic 4.
Complete code of this post can be found in the Github repository ionic-4-in-app-purchase (master branch)
What is In-app purchase, again ?
In-App Purchase is the functionality that lets you purchase items from within your mobile apps. These are different from other payment gateways in the sense that the payment is taken care of by Google Play store or Apple App store itself. These one-time purchases are money in the bank for app developers, while the app store also takes its share.
Types of In-app purchase
There are four types of products / purchases available using In-app purchase method
- Consumable — Consumable allow the user to buy a certain item once. Often used in game apps, one-time in-app purchases can give you added functionality like extra characters, extra life, new powers etc. This purchase can be done again and again, e.g. You can buy more and more lives for your game character.
E.g. Buying a life in Candy Crush ? - Non-Consumable — This type of product can be bought only once, and it never expires. This can be used for something like a premium membership in the app.
E.g. Getting a premium membership of an app for lifetime - Non-renewable Subscriptions — Subscription is different from one time payment, it is supposed to run for a period of time and then it expires. Non-renewable Subscription expires after a set period of time and does not renew automatically.
E.g. Premium membership of Netflix (Did they become auto-renewable ? I need to check my credit card bills) - Auto-renewable Subscriptions — Auto-renewable Subscription expires after a set period of time and renews automatically. This is the most preferred choice for subscription based apps. But this option also faces the maximum scrutiny from app store reviews.
E.g. Premium membership of Tinder
Why is In-app purchase so important ?
In-app purchases can give you major advantages in terms of sales. Following are few reason why In-app purchases are so important
- Creative Control — While most of the payment gateways require you to have the payment UI a certain way, In-app purchases allows you to be as creative as you want in terms of UI and UX. Because everything is integrated inside the app itself, the payment becomes a part of the app, rather than a third-party service.
- Freemium model — You allow the user to “try” the free version before investing even a few cents into the rest of your app and when they are convinced to upgrade no additional trip into the app store is required. This way your user engagement increases, and more users convert to paid ones.
- Less user interaction, better UX — In-app purchase only takes a single tap. Sending the user to the App/Play Store to buy other products requires at least 2 or more taps. Even more if you integrate payment gateways in the app
- Allows variety of options — As we discussed above, the four types of in-app purchase products can provide a wide variety of product and add-on experience for your users. Creating the same experience using a third-party payment gateway can be cumbersome for developers
- Reliable Google and Apple ecosystem — Since the in-app purchase is offered by Google and Apple themselves, it can be assumed to be of highest quality. Any app containing in-app purchase is reviewed very closely before being allowed to published. So users can rest assured that these apps are least risky in terms of payment issues etc.
- Regularly updated — Since the in-app purchase is an App/Play store feature itself, it can be safely assumed that these functionalities will be regularly updated by the platforms as iOS and Android versions change. While the same cannot be said about third-party services, which struggle to update when a new version of Android / iOS is released in market
What is Ionic 4?
You probably already know about Ionic, but I’m putting it here just for the sake of beginners. Ionic is a complete open-source SDK for hybrid mobile app development created by Max Lynch, Ben Sperry and Adam Bradley of Drifty Co. in 2013. Ionic provides tools and services for developing hybrid mobile apps using Web technologies like CSS, HTML5, and Sass. Apps can be built with these Web technologies and then distributed through native app stores to be installed on devices by leveraging Cordova.
So, in other words — If you create Native apps in Android, you code in Java. If you create Native apps in iOS, you code in Obj-C or Swift. Both of these are powerful but complex languages. With Cordova (and Ionic) you can write a single piece of code for your app that can run on both iOS and Android (and windows!), that too with the simplicity of HTML, CSS, and JS.
Structure of the post
I will go ahead in step-by-step fashion so you can follow easily
- Setup your app in Android Play Store
- Create a basic Ionic 4 app
- Upload the app on store
- Setup In-app purchasing testing on Play Store
- Setup the In-App purchase plugin
- Test the app on android device
- Keep in mind !
- Subscriptions and Receipt validations
Step 1 — Setup your app in Android Play Store
First of all, you will need to become an Android developer on Google Play. This requires a membership, worth USD25 , and it never expires (unlike Apple’s one year membership). Go to https://play.google.com/apps/publish. Without the membership, you’ll see this
After registration and fee payment, you’ll go to your dashboard that look like this. I have a list of apps made from before.
Create a new app from the button on left-top. Fill the basic details as shown below and you’ll see your app created in the list of apps
Though, you’ll have to fill a lot of other details. See the gray checkbox in few of the options in the left hand panel ? Those all need to go green before you can publish your first app, or even an alpha/beta version for testing.
Get you Billing Key
For in-app purchase related operations in Android app, you will need Billing Key from Play Store.
Go to your app page → Development Tools → Services & APIs, and pick your Billing key from there, and save it. We’ll use it later
2 — Create a basic Ionic 4 app
I have covered this topic in detail in this blog.
In short, the steps you need to take here are
- Make sure you have node installed in the system (V10.15.3 at the time of this blog post)
- Install ionic cli using npm (my Ionic version is 4.7.4 currently)
- Create an Ionic app using
ionic start
You can create a blank
starter for the sake of this tutorial. On running ionic start ionic-4-in-app-purchase blank
, node modules will be installed. Once the installation is done, run your app on browser using
$ ionic serve
The app will launch on browser. You can go to Inspect → Device Mode to see the code in a mobile layout. You can create a basic layout for In-app purchase, or you can just use a simple button for all actions, doesn’t matter. All the real action will happen when we build the app for Android. Here’s how my layout looks like (layout taken from Woocommerce Ionic Starter)
Step 3 - Upload the app on store
Create a release build of your app to upload it on Play Store. Don’t worry, you’ll only need to publish in Alpha or Beta testing. The app won’t go live for this. We’re uploading a build before anything else for two reasons
- Play store does not allow to create in-app products through developer console until there is an APK uploaded
- Once submitted, the APK can take up to one day to publish, even if it is in Beta or Alpha or Internal testing.
Change App name and package name
Before creating a release APK, give a proper package name to the app in config.xml
. By default, Ionic apps have package nameio.ionic.starter
, but this is not unique for play store. So change it to something related to you, e.g. my app is com.enappd.ionicpurcahse
. You can also change the display name in config.xml
file
Create a release APK
Create an unsigned release APK simply by running
$ ionic cordova build android --prod --release
Note: In some cases, developers have reported that creating an app with — prod
flag results in in-app purchase not working. If you feel so, try creating the release with just --release
flag. The resulting APK will be larger, but you can always update it with --prod
later when in-app purchase works fine.
Sign your app for upload
To upload and publish an APK on Play store, you need to sign the app with a keystore
signature. For that, you need a keystore file and jarsigner
utility from JAVA (default in most systems)
- Create the keystore
$ keytool -genkey -v -keystore release.keystore -alias ionicpurchase -storepass android -keypass android -keyalg RSA -validity 36500
where ionicpurchase
is the alias, release.keystore
is my keystore filename, and I’ve kept android
password for both store and keystore password
- Sign the APK using
$ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore KEYSTORE_FILE_LOCATION UNSIGNED_APK_LOCATION ionicpurchase
Replace KEYSTORE_FILE_LOCATION
and UNSIGNED_APK_LOCATION
with correct values
- Zipalign your APK
Zip aligning is not a mandatory step, it just reduces the size of app and makes it faster for downloads (somehow). Change your directory to your android build tools directory e.g. for me it is …./Android/sdk/build-tools/28.0.3
and run
$ ./zipalign -v 4 /Users/abhijeetrathore/ionic-4-in-app-purchase/platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk ~/Desktop/ionic-purchase.apk
Upload the app in Play store
Go to your Play store dashboard, go to appdetails, fill in all information required to turn the gray checkboxes to green (as mentioned in Step 1) and then upload the APK to Internal testing track (and alpha testing track)
Once uploaded, proceed and publish the app to Internal testing and alpha testing. It will take few hours to a day for the app to publish. To check if the app is published, look for the published
tag in the dropdown
Step 4 — Setup In-app purchasing testing on Play Store
Before starting in-app purchase implementation and testing the app, few things need to be done on Play store.
Create in-app purchase products
After a successful upload of APK with the in-app plugin, you can start adding in-app products in the Play store dashboard.
Go to app details in Play store → Store Presence → In-app products
Here, you’ll see three options
- Managed Products — Fore consumables, non-consumables and non-renewing subscriptions
- Subscriptions — For auto renewing subscriptions
- Rewarded products — Rewarded products are earned by users in your app in return for viewing an ad.
We’ll create a CONSUMABLE as an example. So go to Managed Products and create a new product
Once you fill all details and save the product, it will appear in your managed products list
Note: Product ID is important as this will be used by the app to query Play store for validation of the product.
Create internal testers
You always want to test your app in Internal testing / Alpha test / Beta test track before publishing it to production. For Internal Testing and Alpha closed testing track, you’ll need to create Testers, and attach them to your Internal testing or Alpha testing track.
Create a Tester group by going to
App releases → Alpha testing → Manage . In the first card, create a new Tester group and select it once created
Select this tester group in both Internal testing and Alpha testing to enable testing.
Step 5— Setup the In-App purchase plugin in app
To implement In-app purchase functionality, we’ll use the plugin cordova-plugin-purchase
. This is also available as Ionic Native.
Install plugin
Install the plugin using
$ ionic cordova plugin add "https://github.com/j3k0/cordova-plugin-purchase.git#v9"
--variable BILLING_KEY="<ANDROID_BILLING_KEY>"
$ npm install @ionic-native/in-app-purchase-2
Notice : You need to use ANDROID_BILLING_KEY
from Step 1 when installing the plugin.
I am using the repository from Github directly, as this change is recently made and not available in npm
Once installed, include the module in your app.module.ts
as well as in your page where you will implement in-app purchase
// app.module.ts import { InAppPurchase2 } from '@ionic-native/in-app-purchase-2';... providers: [ InAppPurchase2 ] ...
and in your cart.page.ts
// cart.page.ts import { InAppPurchase2 } from '@ionic-native/in-app-purchase-2'; ...
constructor(public platform: Platform, private store: InAppPurchase2) {...}
Implement plugin methods
In-app purchase works broadly in four steps
- Check if platform and store is ready
- Register your product with product ID (we’ll create product in Play Store in Section 5). Register multiple products if you have.
- Register handles for a particular product’s purchase. These handles are event listeners, which will listen if the purchase was successful, failed etc.
- Purchase — If above steps go fine, you can start the purchase flow, which will be handled by Google’s native in-app purchase UI. This will ask for authentication on your phone before purchase
Let’s me first print out the cart.page.ts
file here, and then we’ll discuss the methods
Let’s go through the code step-by-step
- Check for Platform ready — All store function will strictly run only inside Platform ready. If you don’t put your functions inside it, it can throw a
plugin not installed
error - Register product — Registering a product basically validates the product with Play store if the product really exists, and if the type and ID of product is correct. Any purchase or refresh action should be performed only after registration
- Event handlers — There are several event handlers registered for different events of the purchase process. Let’s see the important ones
- loaded: Called when product data is loaded from the store
- updated: Called when any change occurred to a product
- error: Called when an order failed. The `err` parameter is an IAPError object
- approved: Called when a product order is approved
- owned: Called when a non-consumable product or subscription is owned
- cancelled: Called when a product order is cancelled by the user
- refunded: Called when an order is refunded by the user
- registered: Called when product has just been registered
- valid: Called when the product details have been successfully loaded
- invalid: Called when the product cannot be loaded from the store
- initiated: Called when the purchase process has been initiated
- finished: Called when the purchase process has completed.
- expired: Called when validation find a subscription to be expired.
Each of these event can help you detect what the status of the purchase is. To understand the major events in the life-cycle, refer to the flow diagram below
4. Purchase product — The .order
method starts the purchase process. This process will be successful only when the product is valid and successfully loaded on app start. On success, you’ll receive an object with product details.
Once the transaction is approved, the product still isn’t owned: the store needs confirmation that the purchase was delivered before closing the transaction.
To confirm delivery, you’ll use the product.finish()
method.
5. Restore purchase — In an app where you use in-app products, you need to check every time if the user has the ownership (has purchased) of the product or not. E.g. you may remove ads in the app based on whether the user has purchased a premium membership.
This process is done by getting the details of the product after registering. If the user has purchased a particular product, the product will return owned: true
in the details fetched by store.get('product_id')
method. Accordingly you can take decision in the app.
Note: The product will return owned: true
only for products other than CONSUMABLE
. Consumable go back to valid
state, and purchase can be verified by transaction
details in product info
Step 6 — Test the app on android device
The major step, fingers crossed 🤞
First thing to understand — An android app NEEDS to have a published version for proper functioning of in-app purchases. So you will HAVE to wait for first version to publish. After that, each new version gets updated in an hour or so. Better stick to Internal testing track, as it gets quicker updates than Alpha /Beta testing tracks.
Download as a Tester
Best way to test is to download the app from your opt-in URL once you are a tester. You can find the Opt-in url in your alpha testing / Internal testing page
Remember, the opt-in URL will only allow those users to test who are added in the tester group. Once you click the opt-in url, you’ll go to a page that invites you to become a tester, look like following
Test the app
Once you download the APK, you can simply click the checkout button (in my UI) and that will call the checkout
method of the cart.page.ts
as mentioned in Step 5 above. The flow will look like follow
You need to have a payment method added with your Google account for this to work. I have used my credit card for this.
How to test before APK gets approved
Since APK approval can take its own time, you can test a release APK with only --release
flag. This will keep the console (debug) logs in your code, and will display this information in an Android Studio console. You can test limited functionality in Chrome inspect tool as well.
Since the app is not published in this case, you won’t be able to go through the purchase flow UI as shown in the images above, but you can receive the proper consoles of purchase success in Android Studio console.
Making changes to app after first APK is published
Once you APK is published and you have a downloadable version, you need not upload every change as a new APK to store and wait for it to get published. For further changes and debugging, you can simply test a release APK with only --release
flag, check the consoles in Android Studio, and upload the APK only once you are satisfied with the functionality. This saves a lot of time as you don’t have to upload every new version for every change.
But it seems you still can’t make purchases with this APK.
For this method to work, make sure you use the same package name in the release build you test on device as the package name give in Play store app
Detailed study of Store functions
We can now check how each method works in the flow.
- Register — When you register your product, and the product is successfully registered, you get a response like this from the plugin
[store.js] DEBUG: state: ionic_101 -> registered
Note : Even if you try to register a product not existing in Play console, it will return the same message. You will face error when trying to purchase such a product.
Just after register
, you will receive only limited details of the product. Rest of the details will come when you refresh
store
- Refresh — Refresh function refreshes the state of in-app products and fetches detailed information
> Refresh function fetches all the products from the store, and also reveals which registered product is valid and which is invalid, as shown in above console log.
> Refresh also fetches the purchase details that tells the app which products have been purchased.
> In the app, this detailed information of products can be fetched by product.get()
method. This information can be used to know the exact status of the product’s purchase. Following is the response of product.get('ionic_101)
method after Refresh
.
> Calling the product.get()
method for ionic_102
will return a valid: false
in details.
- Handlers — Registering the handlers in the app after purchase of a product triggers the
approved
event for me. In response, you get the same product info that we got fromproduct.get('ionic_101')
method, where we get the transaction info of the product as well. This can help us figure out the status of the product’s purchase.
Also, the product info containsowned: false
because CONSUMABLES return tovalid
state after purchase, while others go toowned
state - Purchase — Once a purchase is made successfully, you will get to know the success from the UI itself
But on the code level, there are few things happening →
- Immediately after purchase (in the
.then()
oforder()
), we receive the product data, but that is not the valid picture of the product status, so don’t use it for decision making. - After this,
updated()
event is triggered, which provides following details of the product
Here, you can see the value of “transaction”: { “type”: “android-playstore” }
. This shows that transaction was made.
- After this, the
approved()
event is triggered, which is a signal that the purchase is successful. The data received in this trigger is as follows
Notice, “state”: “initiated”
has now changed to “state”: “approved”
You can also see the transaction
object containing the transaction info. This confirm that the purchase is made. You can save this information in your database for future usage.
Un-finished Purchases
If your app wasn’t able to deliver the content, product.finish()
won't be called.
Don’t worry, the approved
event will be re-triggered the next time you call store.refresh()
, which can very well be the next time the application starts. Pending transactions are persistent.
Few important points
- When finished, a consumable product will get back to the
VALID
state, while other will enter theOWNED
state. So don’t put a condition onowned: true
forCONSUMABLE
- Any error in the purchase process will bring a product back to the
VALID
state. - During application startup, products may go instantly from
REGISTERED
toAPPROVED
orOWNED
, for example if they are purchased non-consumables or non-expired subscriptions. - Non-Renewing Subscriptions are iOS products only. Please see the iOS Non Renewing Subscriptions documentation for a detailed explanation.
Step 7 — Keep in mind
In-app purchase is a difficult functionality to implement, especially because it’s difficult to get console logs in release builds. You can face several issues during the development. I faced a few during the blog development, and sharing them just in case you also face them
- Don’t try to debug the app in
debug
mode on Chrome inspect. You can do some functions here, but won’t be able to make purchases. This can lead you to believe that things are working, just the purchase isn’t working. Always userrelease
build for this. - You won’t see the Android in-app purchase default UI until the APK is published on one of the testing tracks. So wait for the APK to get published. This UI only appears in a release APK
- Don’t call the store functions only after
Platform
is ready - Don’t use the product description returned by
register
function, as it does not return all the details of the product. Only afterrefresh
function, you will get the full description and valid state of products
Step 8 — Subscriptions and Receipt validations
All this trouble has been taken to test the in-app purchase consumables. Subscriptions work very similar way up to purchase, but a server receipt validation is mandatory for Subscriptions.
When the receipt validator returns a store.PURCHASE_EXPIRED
error code, the subscription will automatically loose its owned
status.
Typically, you’ll enable and disable access to your content this way.
this.iap2.when("my_subcription").updated((product: IAPProduct) => {
if (product.owned)
// serve the app with subscription
else
// serve the app without subscription
});
For more details on Receipt validation, read thedocumentation provided by Ionic.
Conclusion
In this post we learnt how to implement the In-App purchase feature in Android apps. We also tested the app in and android device and understood what all the events and methods mean.
It is a long a meticulous feature where a lot of things are required to be done correctly. But it’s a very stable feature, which once implemented correctly, will not cause you future troubles.
Complete code of this post can be found in the Github repository ionic-4-in-app-purchase (master branch)