How to integrate the Google Play Billing Library into your app

17 min read

Share

This is the second chapter of our subscription guide for Android series. Previously, we’ve covered how to create and configure in-app subscriptions in Google Play Console, going the full distance in examining every tool Google has to offer. Be sure to check it out.

In this article, we will tell you how to integrate the Google Play Billing Library into your app and start selling products. We’ll dive into the more technical side of things and extract everything we consider essential.

Wait, what is Billing Library and why do I need it?

Imagine you’ve created an app and want to monetize it. If you wish to upload it to the Google Play Store (which most Android developers do), your monetization flow should use Google’s own billing solution, namely Google Play Billing Library. Google uses it to make money by taxing developers’ transactions. 

Last March, Google halved the usual 30% tax for the first $1 million of revenue developers earn using the Play billing system each year. Note that Google’s new approach is slightly different from Apple, which drops its commission from companies that generate no more than $1 million in revenue through the company’s platform. 

Note that you should use the Library if your app sells digital goods and services, like in-app currency in games or digital subscriptions. Apps that sell physical goods and services, e.g., Amazon Shopping or Uber Eats, shouldn’t use the Billing Library.

Starting August 2021, you can’t upload a new app to Google Play with a billing solution rather than Billing Library ver. 3. By November all updates to existing apps must use Billing Library version 3 or newer.

Let’s get terminology out of the way first.

Life of a purchase

Here's a typical purchase flow for a one-time purchase or a subscription.

  • Show the user what they can buy.
  • Launch the purchase flow for the user to accept the purchase.
  • Verify the purchase on your server.
  • Give content to the user, and acknowledge delivery of the content. Optionally, mark the item as consumed so that the user can buy the item again.

Subscriptions automatically renew until they are cancelled. A subscription can go through the following states:

States of a subscription on Android

Purchase tokens and Order IDs

Google Play tracks products and transactions using purchase tokens and Order IDs. Let’s directly take terms from Google:

  • A purchase token is a string that represents a buyer's entitlement to a product on Google Play. This indicates that a Google user is entitled to a specific product that is represented by a SKU. 
  • An Order ID is a string that represents a financial transaction on Google Play. This string is included in a receipt that is emailed to the buyer. You can use the Order ID to manage refunds used in sales and payout reports.

Order IDs are created every time a financial transaction occurs. Purchase tokens are generated only when a user completes the purchase flow.

For one-time products, every purchase creates a new purchase token. Most purchases also generate a new Order ID, except when the user isn’t charged at all, as described in Google’s Promo codes.

For subscriptions, an initial purchase creates a purchase token and an Order ID. For each continuous billing period, the purchase token stays the same and a new Order ID is issued.

Also note:

  • Order IDs for subscription renewals contain an additional integer that represents a specific renewal instance. For example, the initial subscription Order ID might be GPA.XXXX-XXXX-XXXX-XXXXX, and first renewal Order ID will be GPA.XXXX-XXXX-XXXX-XXXXX..0 and so on. 
  • Upgrades, downgrades, replacements, and re-sign-ups create new purchase tokens and Order IDs.

Connect to Google Play

The first step to integrating with Google Play's billing system is to add the library to your app and initialize a connection.

Add the Google Play Billing Library dependency

Add the Google Play Billing Library dependency to your app's build.gradle file:

dependencies {
    def billing_version = "3.0.3"
 
    implementation "com.android.billingclient:billing:$billing_version"
}


If you want to use coroutines, you can integrate dependency Play Billing Library KTX:

dependencies {
    def billing_version = "3.0.3"
 
    implementation "com.android.billingclient:billing-ktx:$billing_version"
}

Initialize a BillingClient

Every code chunk down below are written in Kotlin. Check with Google’s official guide to find code written in Java.

BillingClient is the main interface for communication between the Google Play Billing Library and the rest of your app. It provides convenient methods for many common billing operations. Once you've added a dependency to the Google Play Billing Library, you'll need to initialize a BillingClient instance. 

To create a BillingClient, use newBuilder(). To receive updates on purchases, you must also call setListener(), passing a reference to a PurchasesUpdatedListener. This listener receives updates for all purchases in your app.

private val purchasesUpdatedListener =
   PurchasesUpdatedListener { billingResult, purchases ->
       // To be implemented below.
   }
 
private val billingClient = BillingClient.newBuilder(activity)
   .setListener(purchasesUpdatedListener)
   .enablePendingPurchases()
   .build()


Establish a connection to Google Play

Once you created a BillingClient, you have to establish a connection to Google Play. This is how you do it:

billingClient.startConnection(object : BillingClientStateListener {
    override fun onBillingSetupFinished(billingResult: BillingResult) {
        if (billingResult.responseCode ==  BillingResponseCode.OK) {
            // The BillingClient is ready. You can query purchases here.
        }
    }
    override fun onBillingServiceDisconnected() {
        // Try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
    }
})

Error Handling

The Google Play Billing Library returns errors in the form of BillingResult. A BillingResult contains a BillingResponseCode, which categorizes possible billing-related errors that your app may encounter. Let’s say you receive a SERVICE_DISCONNECTED error code, so your app should reinitialize the connection with Google Play. Additionally, a BillingResult contains a debug message, which is useful during development to diagnose errors.

Note: It's strongly recommended that you implement your own connection retry logic and override the onBillingServiceDisconnected() method. Make sure you maintain the BillingClient connection when executing any method.

Show products available to buy

Once you have established a connection to Google Play, you are ready to query your available products and display them to your users. We’ve already covered how you create products in the first chapter.

You can find an example of the request on product info below:

fun querySkuDetails() {
    val skuList = listOf("product_id_1", "product_id_2")
    val params = SkuDetailsParams.newBuilder().setSkusList(skuList).setType(SkuType.INAPP).build()
    withContext(Dispatchers.IO) {
        billingClient.querySkuDetailsAsync(params) { billingResult, skuDetailsList ->
            // Process the result.
        }
    }
}


You should request info on subscriptions and info on one-time purchases separately by using constants SkuType.SUBS and SkuType.INAPP, accordingly.

The Library stores query results in a List of SkuDetails objects. After that, you can call a variety of methods on each SkuDetails object in the list to view relevant information about an in-app product like its price or description. To view the available product detail information, see the list of methods in the SkuDetails class.

Launch the purchase flow

To start a purchase request from your app, call the launchBillingFlow() method from your app's main thread, as shown below.

// skuDetails value is taken from the result for querySkuDetailsAsync() in the above paragraph.
val billingFlowParams = BillingFlowParams.newBuilder()
        .setSkuDetails(skuDetails)
        .build()
val responseCode = billingClient.launchBillingFlow(activity, billingFlowParams).responseCode


If responseCode equals to BillingResponseCode of OK, system displays the Google Play purchase screen:

google play purchase screen

Google Play calls a callback on PurchasesUpdatedListener that we have already covered in the “Initialize a Billing Client” section. Let’s fill it with code:Processing purchases

PurchasesUpdatedListener {   
if (billingResult.responseCode == BillingResponseCode.OK && purchases != null) {
       for (purchase in purchases) {
           handlePurchase(purchase)
       }
   } else if (billingResult.responseCode == BillingResponseCode.USER_CANCELED) {
       // Handle an error caused by a user cancelling the purchase flow.
   } else {
       // Handle any other error codes.
   }
}


Your app should process a purchase in the following way:

  1. Verify the purchase. To do that, you should check that the purchase state is PURCHASED. If it’s PENDING, then you should process the purchase as described in Handling pending transactions. For purchases received from onPurchaseUpdated() or queryPurchases, you should further verify the purchase to ensure legitimacy before your app grants entitlement. 
  2. Give content to the user, and acknowledge delivery of the content. This acknowledgement communicates to Google Play that you have granted entitlement for the purchase. If you do not acknowledge a purchase within three days, the user automatically receives a refund, and Google Play revokes the purchase.

Once you've verified the purchase, your app is ready to grant entitlement to the user. After granting entitlement, your app must then acknowledge the purchase. The process to grant entitlement and acknowledge the purchase depends on whether the purchase is a non-consumable, a consumable, or a subscription.

When talking about consumables, consumeAsync() method fulfills the acknowledgment requirement and shows that your app has granted entitlement to the user. This method also enables your app to make the one-time product available for purchase again.

To indicate that a one-time product has been consumed, call consumeAsync() and include the purchase token that Google Play should make available for repurchase. 

The following example illustrates consuming a product using the associated purchase token:

fun handlePurchase(purchase: Purchase) {
    // Verify the purchase.
    // Ensure entitlement was not already granted for this purchaseToken.
    // Grant entitlement to the user.
 
    val consumeParams =
        ConsumeParams.newBuilder()
            .setPurchaseToken(purchase.purchaseToken)
            .build()
 
    billingClient.consumeAsync(consumeParams, { billingResult, outToken ->
        if (billingResult.responseCode == BillingResponseCode.OK) {
            // Handle the success of the consume operation.
        }
    })
}


Before acknowledging a purchase, your app should check whether it was already acknowledged by using the isAcknowledged() method in the Google Play Billing Library. If you don’t have verification on your own back-end, then the only way you can acknowledge non-consumable purchases and subscriptions is BillingClient.acknowledgePurchase() from the Billing Library.

The following example shows how to acknowledge a purchase using the Google Play Billing Library:

fun handlePurchase() {
    if (purchase.purchaseState === PurchaseState.PURCHASED) {
        if (!purchase.isAcknowledged) {
            val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.purchaseToken)
            val ackPurchaseResult = withContext(Dispatchers.IO) {
               client.acknowledgePurchase(acknowledgePurchaseParams.build())
            }
        }
     }
}

Fetching purchases

Listening to purchase updates using a PurchasesUpdatedListener is not sufficient to ensure your app processes all purchases. It's possible that your app might not be aware of all the purchases a user has made. Here are some scenarios where your app could lose track or be unaware of purchases:

  • Network Issues during the purchase: A user makes a successful purchase and receives confirmation from Google, but their device loses network connectivity before receiving notification of the purchase through the PurchasesUpdatedListener.
  • Multiple devices: A user buys an item on one device and then expects to see the item when they switch devices.
  • Handling purchases made outside your app: Some purchases, such as promotional redemptions, can be made outside of your app.

To handle such situations, make sure that your app calls BillingClient.queryPurchases() in your onResume() and onCreate() methods. 

Handling pending transactions

Remember, in the “Initialize a BillingClient” section we've added enablePendingTransaction()? Google Play supports pending transactions, which require one or more additional steps between two stages: when a user initiates a purchase and when the payment method for the purchase is processed. Your app shouldn’t grant entitlement to such purchases until Google notifies you that it received the payment.

If your app is in the foreground and the purchase status has changed from “Pending” to “Purchased”, a callback on PurchasesUpdatedListener gets called again. In this case, you can take all necessary actions like in handlePurchase(). In this example, you do nothing when the purchase status is “Pending” as you should acknowledge a purchase only when the state is “Purchased”, and you can’t acknowledge when it’s “Pending”. The three-day acknowledgment window begins only when the purchase state transitions to “Purchased”.

Your app should also call queryPurchases() in your app's onResume() and onCreate() methods to handle purchases that have transitioned to the PURCHASED state while your app is not running.

That’s it, but stay hungry

This was a brief overview of how to handle purchases on Android devices. In the next (and last) chapter we will cover what you can do with your own back-end.

Mstislav Grivachev
March 24, 2021