Sell consumable and non-consumable In-App Purchases in your desktop and mobile applications. Learn how to setup and process payments for IAP products on both macOS/iOS and Android devices.
Level: Advanced
Platforms: macOS, iOS, Android
Classes: InAppPurchases::Listener, SoundPlayer, AsyncUpdater, ListBoxModel
Download the demo project for this tutorial here: PIP | ZIP. Unzip the project and open the first header file in the Projucer.
Resources
folder into the generated Projucer project.If you need help with this step, see Tutorial: Projucer Part 1: Getting started with the Projucer.
The project offers different voices that can be acquired through in-app purchases to provide several auditive flavours when phrases are played using speech dictation. By default, the user has access to a generic robotic voice but will inevitably want to try JUCE developer voices instead. If we run the mobile application in the iOS simulator, the window should look something like this:
In order for this project to function properly, we have to perform some initial setup procedures with appropriate developer consoles for specific deployment platform. Let's first allow appropriate permissions for in-app purchases in the Projucer. Under macOS/iOS, make sure that the In-App purchases capability checkbox is ticked. Under Android, make sure that the In-App Billing checkbox is ticked.
The Projucer will automatically add the required entitlements to your deployment targets when saving the project and opening in your favourite IDE.
On macOS and iOS, you will need to sign in with your Apple Developer account within Xcode and choose a development team in order to sign the application. Choose a unique bundle ID for your project. Xcode should automatically provide you with a Signing Certificate and Provisioning Profile as shown in the following screenshot:
Also make sure that the correct app capabilities have been ticked and approved in the Capabilities settings window. You should see the same information as follows:
For the in-app purchases to display properly on iOS, we need to create IAP products in iTunes Connect. First, create a new app in your dashboard under My Apps. If you navigate to the Features tab of your app, you can access the In-App Purchases feature on the following screen:
Click on the + sign to create six products that correspond with the options in the app with appropriate names and prices.
For the in-app purchases to display properly on Android, we need to create in-app billing products in the Google Play Console. First, navigate to your app page from the All applications panel and open up the In-app Products page under Store presence. You can access the items under the Managed products tab as shown on the following screen:
Click on Create managed product to create six products that correspond with the options in the app with appropriate names and prices.
In order for in-app purchases to function properly on Android, we have to sign the Android version of the app and authenticate the requests to the Google Play store API. First launch Android Studio and navigate to the menu bar under Build > Generate Signed APK...:
You will then be prompted to enter the location, alias and passwords for the keystore file like so:
Next, select release build type, "release_" flavour and ensure that both V1 and V2 signatures are checked.
This will generate the keystore file that you can now refer to in the Projucer. Under the Android release settings, enter the relative path, alias and passwords to the keystore file in the Key Signing fields:
Setup for in-app purchases should be complete by now and we can finally start implementing these features into the app.
In-App Purchases are useful to offer customers extra content and features directly within the app. They can be premium functionalities, exclusive items or even subscriptions. In general, there are four main types of puchases on all relevant platforms:
In this tutorial, we will implement non-consumable in-app purchases.
The project is structured using the following classes to handle different parts of the application:
As we can see, it roughly follows an MVC design pattern to separate application functionalities into separate classes.
The VoiceProduct struct contains useful information for non-consumable products namely the following variables:
Let's start by displaying the IAP products that we want to sell in the user interface. In the VoicePurchases constructor of the VoicePurchases
class, we have initialised the VoiceProduct objects using the structure described above like so:
In order to easily retrieve human-readable versions of all our products, we have implemented a helper function that returns a StringArray of all capitalised names:
Let's create another helper function in the same class called findVoiceIndexFromIdentifier()
to get the index of a VoiceProduct when passed an identifier. This private function will be helpful later on when dealing with callbacks from the InAppPurchases object:
The InAppPurchases class works as a broadcaster so let's inherit from InAppPurchases::Listener to become a listener for this class and receive callbacks from the IAP server [1]:
We can now start overriding the InAppPurchases callback functions in our VoicePurchases class. First, implement the productsInfoReturned()
function. This function gets called to return product information after a call to InAppPurchases::getProductsInformation().
When dealing with in-app purchases, the very first thing your application should check is for past purchases when the user signs in. As a customer, one of the most frustrating things that can happen is to lose previous purchases made on another device or on a previous version of the same app. So let's make sure that the user gets an updated view of the products page as soon as possible on app startup.
First, create additional private variables in the VoicePurchases
class to store temporary states of the app [1]:
We also need a new helper function called getPurchase()
to retrieve a VoiceProduct from an index. In this function, we also insert initialisation code for when this function is first called:
restoreProductsBoughtList()
function to trigger the restoration process and wait for the subsequent callback.Since the VoicePurchases class gets registered as a listener when the above function gets initially called, we need to make sure we unregister when this class gets destroyed. Remove the class as an InAppPurchases listener in the class destructor:
In the previously defined getPurchase()
function, we called the restoreProductsBoughtList()
function to trigger a callback to restore our purchases. Now let's implement that callback function called purchasesListRestored()
:
getProductsInformation()
function.In order to update the GUI for these in-app purchases products, it would be useful to have temporary variables to store purchase states. Let's declare these as private member variables in the VoiceRow subclass of the VoiceModel
class:
At the moment, all the in-app purchases products look visually the same and do not clearly indicate their availability in a visually-striking manner. Let's apply a different look and feel to purchasable products. In the paint()
function, modify the code to apply a white background to the product image when available [10] and a white semi-transparent overlay when unavailable [11]:
Also modify the code in the update()
function to reflect purchase status on the name and price labels. Use the previously defined helper function to retrieve the VoiceProduct from the row index first [12] and set the temporary variable to indicate whether the item was purchased [13]. Then update the GUI accordingly as follows:
In this piece of code, we mainly update the font styles of the name and price labels to remain as default when the item is purchased and change its colour to white. In addition, we enable the purchase buttons if the corresponding product is available for purchase. We also need to disable mouse clicks on these buttons after purchase to avoid errors when charging the customer [14].
We have yet to implement purchasing behaviour in our app so let's do that. Implement a purchaseVoice()
public function in the VoicePurchases
class that transfers the request to the InAppPurchases instance:
This will trigger a callback from the server when the purchasing has ended and a response was received. We implement this productPurchaseFinished()
callback here:
Using the same helper function as before, retrieve the VoiceProduct index from its identifier [4] and set appropriate variables on the object in question depending on whether the purchase was successful and exit purchasing state [5].
To indicate the purchasing state to the user while waiting for the response from the server, display a spinning animation in the paint()
function of the VoiceModel
class [6]:
Let's implement the clickPurchase() function to initiate the purchasing process when the user interacts with the UI:
purchaseVoice()
function and enter purchasing state.Lastly in the update()
function, retrieve the purchasing state for the row-specific product [10] and stop the spinning animation if the purchase is finished by calling the stopTimer()
function [11]:
The mechanics to handle product purchasing and retrieval of product information have all been implemented at this point in the tutorial but we still need to tell the GUI when to update itself.
Since purchases and synchronisation with the IAP server are performed on a separate thread, we need to handle responses in an asynchronous manner. In the MainContentComponent
class, let's inherit from the AsyncUpdater class [1] and pass a reference of this class to the VoicePurchases instance [2]:
In the VoicePurchases constructor and member initialisation list, assign a reference to the AsyncUpdater instance in a private variable [3]:
Declare that variable as a private member to be able to refer to the AsyncUpdater later on [4]:
Now in the purchaseVoice()
function and all the callback functions of the InAppPurchases instance namely productsInfoReturned()
, purchasesListRestored()
and productPurchaseFinished()
, trigger an asynchronous update to the GUI as a last step to the corresponding code segments [5]:
Now whenever the triggerAsyncUpdate()
function is called on the AsyncUpdater instance in the VoicePurchases class, we can handle the callback in the handleAsyncUpdate()
function to update any outdated GUI components [6]:
Add the following getter in the VoicePurchases
class as a public helper function:
This ensures that the GUI is updated whenever purchases are made or retrieved. You should now see previously-made in-app purchases when the app is launched again.
When the user clicks on the play button, we need to trigger the correct audio file to play using the right voice and phrase. The audio files are stored as binary resources using the name of the voice and an index relating to the phrase number as a naming convention. In the MainContentComponent
class, we handle the behaviour as follows:
play()
function of the SoundPlayer with the corresponding file.You should now be able to hear the spoken phrase with the corresponding voice when the play button is pressed.
InAppPurchaseTutorial_02.h
file of the demo project.In this tutorial, we have learnt how to handle In-App Purchases on mobile and desktop. In particular, we have: