How to Add In-App Purchases & Subscriptions to your Ionic App with Glassfy Last update: 2022-09-06
If you want to monetise your Ionic app you can use the native iOS and Android platforms to integrate items to purchase, but you can make the whole process a lot more transparent with another tool.
This tutorial is sponsored by Glassfy, an open source SDK and backend to integrate in-app subscriptions in minutes!
But you are not only limited to subscriptions, you can also add simple purchases to your app and Glassfy keeps track of every transaction a user made. This is not only helpful for statistics, but also to give access to specific content or functions of your app.
Today we will therefore go through the steps of integrating the Glassfy SDK into an Ionic app using Capacitor so you can easily create purchases and subscriptions right through their platform.
In the end we will be able to unlock or hide content in our app simply by grabbing the settings from Glassfy without running our own backend or custom validation on our end!
How Glassfy works
The idea of Glassfy is to make it as easy as possible to handle subscriptions and purchases in mobile apps by capturing all relevant information and giving you a better view of your data.
Although we will later only interact with the Glassfy SDK, we still need to setup our iOS and Android items for purchase on their respective platforms. After you have added those products inside the Google Play Console and Appstore Connect, you can then easily access them inside your Glassfy dashboard by their SKU and make them available to your app as offerings.
You can also use this logic to bundle items into groups that give specific permissions for users, so you have not only abstracted away the native purchase flow but also additional information for your app.
On top of that Glassfy can act as a backend to accept webhooks to validate purchases made or events in relation to the subscription status of a user, for which you otherwise would have to run your own solution.
The best part? You can get started for free and use all of this up to $10k revenue!
Note: You can also manage Paddle subscriptions with Glassfy, but in this post we are focusing on iOS and Android purchases only.
Ionic App Setup
Let’s now start with our Ionic app and install the Glassfy Capacitor plugin like this:
ionic start ionicGlassfy blank --type=angular
cd ./ionicGlassfy
ionic g service product
# Add the plugin
npm install capacitor-plugin-glassfy
# Add native platforms
ionic build
ionic cap add ios
ionic cap add android
npx cap sync
To later use payment features we now need to make sure our native apps are configured correctly, so for iOS we need to add the In-App purchase capability inside our Xcode project:
We also need to make sure we have the right permissions on Android, therefore bring up the android/app/src/main/AndroidManifest.xml and add the following line to the permissions:
<uses-permission android:name="com.android.vending.BILLING" />
Now we need to bring in our Glassfy key, so create an account for free (e.g. using your Github account) and then click “Create App” inside Glassfy to create your new app.
After this you can navigate to the Settings page of your new app and grab the ApiKey, which we can then paste into the src/environments/environment.ts of our Ionic app like this:
export const environment = {
production: false,
glassfyKey: 'YOUR-GLASSFY-API-KEY'
};
Before we can now implement the purchase functionality in our app we need to take a step back and configure our products within Glassfy.
Setting up In-App Purchases
To add IAP to your app you of course need a native app, so make sure you have set up and according app ID for iOS and Android before you continue.
You need to be able to work on your app in the App Store Connect portal for iOS as well as the Google Developer Console for Android where you can add In-App purchases and subscriptions.
We are not going through the full process of setting up items in the native platforms because Glassfy described the process already in detail inside their App Store Configuration guide for iOS and the Play Store Configuration guide for Android - make sure to follow them accordingly to create your items.
You can also see me going through this in the video version of this tutorial at the end of the article!
For example, this is my list of items a user can purchase on iOS:
All of this can (and should) be done on Android as well if you want to sell items on both platforms, so create the same kind of items under In-App products inside the Google Play Developer console:
Notice something?
I used almost identical product IDs only containing ios or android at the start, but then following the scheme recommended by Glassfy so you can easily identify and group your items later!
In my example I have created both a consumable item (the 100 gems) and also a non-consumable in form of a custom (fictional) skin.
With all this information in place we can now head over to our Glassfy dashboard and click Create Sku to add these items to Glassfy. In the opening dialog you can specify the IDs for both Android and iOS and group them together under one SKU that will be used within Glassfy from now on.
Because I created two different items on iOS and Android I also created the two different SKUs:
At this point we need two more things:
- Give specific permissions to a SKU
- Make something available for purchase as an offering
Just like we did with the SKU before, we can now click create permission to create a permission group that includes a number of SKUs, and then we can click create offering to create an according offer that we can display in our app for users to purchase an item.
At this point we have pretty much abstracted away all native platform logic and work entirely on the information we’ve given to Glassfy - and it will continue like that!
Setting up Subscriptions
So far we have set up normal in-app purchases, items users can simply purchase one or multiple times.
Many times you also want to add a subscription so you can put certain parts of your app behind a paywall, and you can again follow the guide from Glassfy on setting up your subscriptions first of all, which works mostly like setting up a regular item users can purchase.
This process also includes setting up an App-Specific Shared Secret for iOS that you then need to paste into your Glassfy settings.
However, there’s one more thing we now need to take care of: Making sure Apple can send events back to Glassfy!
To achieve this, you can set up App Store Server to Server notifications by grabbing your Apple S2S notification URL from the Glassfy settings page and then pasting it into the production and sandbox server URL inside your App Store Connect entry.
By doing this, Glassfy gets all the subscription relevant events and you can monitor the state of your users better.
After creating your subscriptions for iOS and Android, make sure you group them again under one SKU in Glassfy, create a new permission group and finally an offering.
At this point you should get a good feeling of how the process works, and we are close to seeing the fruits of our labour in action!
Working with the Glassfy SDK
After all the setup we gonna have a very relaxed life now as we only need to talk to Glassfy now and grab the information we set up before.
To do so, let’s begin by creating a service that pulls in all permissions of a user and all offerings after creating a connection to Glassfy with our key
.
Therefore bring up the src/app/services/product.service.ts and change it to:
import { environment } from './../../environments/environment';
import { Injectable } from '@angular/core';
import {
Glassfy,
GlassfyOffering,
GlassfyPermission,
GlassfySku,
GlassfyTransaction
} from 'capacitor-plugin-glassfy';
import { BehaviorSubject } from 'rxjs';
import { AlertController, ToastController } from '@ionic/angular';
@Injectable({
providedIn: 'root'
})
export class ProductService {
// Init an "empty" user
user = new BehaviorSubject({ gems: 0, skins: [], pro: false });
private offerings: BehaviorSubject<GlassfyOffering[]> = new BehaviorSubject([]);
constructor(private toastController: ToastController, private alertController: AlertController) {
this.initGlassfy();
}
async initGlassfy() {
try {
// Initialise Glassfy
await Glassfy.initialize({
apiKey: environment.glassfyKey,
watcherMode: false
});
// Get all user permissions
const permissions = await Glassfy.permissions();
this.handleExistingPermissions(permissions.all);
// Get all offerings (products)
const offerings = await Glassfy.offerings();
this.offerings.next(offerings.all);
} catch (e) {
console.log('init error: ', e);
}
}
async purchase(sku: GlassfySku) {
// TODO
}
handleExistingPermissions(permissions: GlassfyPermission[]) {
// TODO
}
async handleSuccessfulTransactionResult(transaction: GlassfyTransaction, sku: GlassfySku) {
// TODO
}
// Helper functions
getOfferings() {
return this.offerings.asObservable();
}
async restore() {
const permissions = await Glassfy.restorePurchases();
console.log(permissions);
// Handle those permissions!
}
}
We gonna fill in the gaps soon, for now this will make sure that we pull in the information from our Glassfy app and make it available through the BehaviourSubject
where we can easily emit new data once the user purchases something else.
Because the Glassfy Capacitor plugin comes with a bunch of interfaces we can strongly type most of the functions and already see in advanced the objects we are dealing with!
With the basic functionality in place, let’s head over to the src/app/home/home.page.ts where we can call the service functions like this:
import { ProductService } from './../services/product.service';
import { Component } from '@angular/core';
import { GlassfySku } from 'capacitor-plugin-glassfy';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss']
})
export class HomePage {
offerings = this.productService.getOfferings();
user = this.productService.user;
constructor(private productService: ProductService) {}
async purchase(sku: GlassfySku) {
await this.productService.purchase(sku);
}
async restore() {
await this.productService.restore();
}
}
Now we have an active connection to the user and all offerings, so once we retrieve data from Glassfy we are able to update the value easily and the view will change.
The last missing piece is now the view, in which we want to display a list of products based on the offerings. This is exactly the information we set up previously in the Glassfy dashboard of our app, and those are the items a user can purchase.
Because this is only a testing app I’ll simply iterate the items, but in a real world app you of course would display specific offerings in different places or views, hidden behind some other buttons or marketing texts!
In our case we can now iterate the offerings and display the different SKUs that we can purchase grouped by the offering - for example I added two non-consumables which are both available under one offering.
Bring up the src/app/home/home.page.html now and change it to:
<ion-header>
<ion-toolbar color="secondary">
<ion-title> Ionic + Glassfy </ion-title>
<ion-buttons slot="end">
<ion-button (click)="restore()">Restore</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<div *ngFor="let offeringGroup of offerings | async">
<ion-item-divider>
<ion-label> {{ offeringGroup.offeringId }} </ion-label>
</ion-item-divider>
<ion-item *ngFor="let sku of offeringGroup.skus">
<ion-label class="ion-text-wrap">
{{ sku.product.title }}
<p>{{sku.product.description }}</p>
</ion-label>
<ion-button slot="end" fill="outline" (click)="purchase(sku)">
{{ sku.product.price | number:'1.0-2' | currency: sku.product.currencyCode }}
</ion-button>
</ion-item>
</div>
</ion-list>
<ion-card>
<ion-card-content>
<ion-item>
<ion-label> Gems: {{ (user | async).gems }} </ion-label>
</ion-item>
<ion-item>
<ion-label> Skins: {{ (user | async).skins.join(',') }} </ion-label>
</ion-item>
<ion-item>
<ion-label> Pro Features: {{ (user | async).pro }} </ion-label>
</ion-item>
</ion-card-content>
</ion-card>
</ion-content>
This should display all information and even pricing from Glassfy in our Ionic app, and a card below that will update when we make a test purchase.
Compare this to the information from Glassfy and you will see we got exactly those texts!
Now we can take things a step further and implement the actual purchase()
functionality in our service and at the same time make sure we are handling all permissions a user has (meaning purchased items or active subscriptions) and reflect those values on our dummy user
object.
The purchase function only needs an SKU from Glassfy, and since we used those types in all places we can confidently implement the function like this:
async purchase(sku: GlassfySku) {
try {
const transaction = await Glassfy.purchaseSku({ sku });
if (transaction.receiptValidated) {
this.handleSuccessfulTransactionResult(transaction, sku);
}
} catch (e) {
const alert = await this.alertController.create({
header: 'Purchase failed',
message: e,
buttons: ['OK'],
});
await alert.present();
}
}
In fact this function does a lot - it triggers the whole payment flow in your app, simply based on the SKU we fetched from Glassfy.
Now there are two more functions left which will parse the permissions that were granted to a user based on a recent transaction or on a permission to user has because he/she purchased something in the past.
In those functions you need to enable access to premium features if the user has an active subscription, add gems after purchasing a consumable or making sure the tiger skin is available for use if it’s been purchased in the past.
Bring up the service one more time and implement them like this:
handleExistingPermissions(permissions: GlassfyPermission[]) {
for (const perm of permissions) {
if (perm.isValid) {
if (perm.permissionId === 'skin_shark') {
const user = this.user.getValue();
user.skins.push('Shark');
this.user.next(user);
} else if (perm.permissionId === 'skin_tiger') {
const user = this.user.getValue();
user.skins.push('Tiger');
this.user.next(user);
} else if (perm.permissionId === 'pro_features') {
const user = this.user.getValue();
user.pro = true;
this.user.next(user);
}
}
}
}
async handleSuccessfulTransactionResult(
transaction: GlassfyTransaction,
sku: GlassfySku
) {
if (transaction.productId.indexOf('gems_100_consumable_1.99') >= 0) {
const user = this.user.getValue();
user.gems += +sku.extravars.gems;
this.user.next(user);
}
if (transaction.productId.indexOf('skin_tiger_nonconsumable_4.99') >= 0) {
const user = this.user.getValue();
user.skins.push(sku.extravars.skin);
this.user.next(user);
}
if (
transaction.productId.indexOf('glassfyapp_profeatures_monthly_9.99') >= 0
) {
const user = this.user.getValue();
user.pro = true;
this.user.next(user);
}
const toast = await this.toastController.create({
message: 'Thanks for your purchase!',
position: 'top',
duration: 2000,
});
toast.present();
}
Note that at this point we need to tie some real world logic / outcome to the SKUs we defined in Glassfy.
Since the local IDs might contain “ios” or “android” we are using the indexOf()
comparison to see if a specific SKU is equal to that string.
This part of enabling access or granting items is of course 100% custom for every app, however given the permissions and information we receive from Glassfy we don’t need to run our own backend to keep track of things a user purchased, and we also don’t have to store it somewhere in localstorage.
If you now want to purchase an item, a dialog comes up showing you that you are running the app in sandbox mode (at least if you simply deployed the app from your local code).
In sandbox mode subscriptions work way faster, so a week is probably 5 minutes, and after 4 cycles (or when you manually cancel it) the subscription expires.
Usually this happens somewhere in the background without a place for you to see what’s going on, and this is where the customers view of Glassfy comes in handy.
This is how my view looked after purchasing a few items with a test user:
That means I can exactly see the purchases of a user and even get into some more metrics and analytics within Glassfy to better understand how and what customers are purchasing in my Ionic app.
Conclusion
I’ve implemented basic IAP with Ionic before, but the process felt kinda magical to me sometimes as you don’t really get any logs from iOS or Android and you rely on some local information in your app.
By using Glassfy the process to manage your subscriptions and purchases becomes a lot easier as you can set things up in their dashboard and use the according SDK instead of talking directly to Apple/Google.
Most of all I enjoyed seeing the events around subscriptions which are handled by their own backend, so it’s one item less I need to worry about when implementing in app purchases with Ionic!
You can also find a video version of this tutorial below!