In-App Purchases on Android: A Deep Dive—Part 2 (with Google Cloud and APIs)

In-App Purchases on Android: A Deep Dive—Part 2 (with Google Cloud and APIs)

Introduction

In Part 1, we introduced the concept of In-App Purchases (IAP) and set up our products in the Google Play Console. Part 2 builds on that by diving deeper into the Android-specific implementation and exploring related Google Cloud technologies. We’ll now cover how these technologies work together to create a complete IAP system, focusing on the Google Play Billing Library, the Google Play Developer API, Google Cloud Console, and Cloud Pub/Sub.

Recap from Part 1

  • IAP Types: Consumable (used once) and Non-Consumable (permanent unlocks).

  • Product Setup: Products are configured in the Google Play Console with unique IDs.

  • Goal: Implement code that fetches products, handles purchases, verifies them, and manages the app state accordingly.

II. Diving Deeper: Key Concepts, Technologies, Google Cloud and Testing

Let’s explore the core aspects of Android IAP, including the mentioned Google Cloud technologies, referencing the Google Play Billing Documentation.

A. The Core: Google Play Billing Library

As discussed before, the Google Play Billing Library is an API that helps you to communicate with the Play Store for all purchase-related activities. This API is a part of your Android app and helps you to fetch product details, handle purchases, and more.

  • Key Classes:

    • BillingClient: Manages communication with Google Play.

    • Purchase: Represents a purchase by a user.

    • ProductDetails: Contains information about a specific product.

    • BillingFlowParams: Used to launch purchase flows.

B. Google Cloud Console and the Google Play Developer API

The Google Cloud Console is a powerful platform for managing your Google Cloud resources, including backend APIs like the Google Play Developer API.

  • Google Play Developer API: This is a REST API that lets you manage your Google Play store listings, user reviews, and more programmatically. This also includes managing in-app products and subscriptions, making it very useful for backend management of IAP.

    • Use Cases:

      • Backend verification: You can use the API to securely verify purchases on your server and check the status of subscriptions.

      • Automated product management: You can automate the creation, updates, and deletion of in-app products from the backend.

      • Access purchase history: Retrieve purchase history programmatically.

C. Connecting to the Billing Library (App Side)

  1. Build BillingClient: Initialize BillingClient using the builder and enable enablePendingPurchases.

     private lateinit var billingClient: BillingClient
     private fun setupBillingClient() {
         billingClient = BillingClient.newBuilder(this)
             .setListener(purchasesUpdatedListener)
             .enablePendingPurchases()
             .build()
     }
    
  2. Connect to Billing Service: Call the startConnection with BillingClientStateListener.

     billingClient.startConnection(object : BillingClientStateListener {
             override fun onBillingSetupFinished(billingResult: BillingResult) {
                 if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                     // Connection Successful
                     queryProducts()
                 } else {
                     Log.e("Billing", "Billing setup failed with code: ${billingResult.responseCode}")
                 }
             }
    
             override fun onBillingServiceDisconnected() {
                 // Try to restart the connection.
                 Log.e("Billing", "Billing Service Disconnected.")
                setupBillingClient()
             }
         })
    

D. Querying Product Details (App Side)

  1. Prepare QueryProductDetailsParams: Fetch details using QueryProductDetailsParams with Product IDs.

     private fun queryProducts() {
         val queryProductDetailsParams = QueryProductDetailsParams.newBuilder()
             .setProductList(
                 ImmutableList.of(
                     QueryProductDetailsParams.Product.newBuilder()
                         .setProductId("premium_access") // Your Product ID
                         .setProductType(BillingClient.ProductType.INAPP)
                         .build()
                 )
             )
             .build()
    
         billingClient.queryProductDetailsAsync(queryProductDetailsParams) { billingResult, productDetailsList ->
             if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                 // Handle product details.
                for (productDetails in productDetailsList) {
                     //Display product in UI
                }
             } else {
                  Log.e("Billing", "Product details query failed: ${billingResult.debugMessage}")
             }
         }
     }
    

E. Handling Purchases (App Side)

  1. Set Purchase Listener: Implement PurchasesUpdatedListener for handling purchases

     private val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->
         if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
             for (purchase in purchases) {
               // Handle purchase
                 handlePurchase(purchase)
             }
         } else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
             // Handle cancelation
            Log.e("Billing", "Purchase canceled by user.")
         } else {
             // Handle Purchase error
             Log.e("Billing", "Purchase error with code: ${billingResult.responseCode}")
         }
     }
    

F. Purchase State Management and Acknowledgement (App Side)

  1. Handle Purchase: Manage purchases with different states (PURCHASED or PENDING).

     private fun handlePurchase(purchase: Purchase) {
         if (purchase.purchaseState == PurchaseState.PURCHASED) {
             // Handle purchase logic
             if (!purchase.isAcknowledged) {
                 acknowledgePurchase(purchase)
             }
         } else if (purchase.purchaseState == PurchaseState.PENDING) {
            // Handle pending state.
         }
     }
    
  2. Acknowledge Purchases: Use billingClient.acknowledgePurchase() to acknowledge purchases after product delivery.

     private fun acknowledgePurchase(purchase: Purchase) {
        val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
             .setPurchaseToken(purchase.purchaseToken)
             .build()
    
        billingClient.acknowledgePurchase(acknowledgePurchaseParams) { billingResult ->
             if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                 // Purchase acknowledged
             } else {
                  //Handle acknowledgment error
                   Log.e("Billing", "Purchase acknowledgement failed: ${billingResult.debugMessage}")
             }
         }
     }
    

G. Consuming Products (App Side)

  1. Consume Products: Call billingClient.consumeAsync() for consumable products to allow repurchase.

     private fun consumePurchase(purchase: Purchase) {
         val consumeParams = ConsumeParams.newBuilder()
             .setPurchaseToken(purchase.purchaseToken)
             .build()
    
        billingClient.consumeAsync(consumeParams) { billingResult, purchaseToken ->
             if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                 // Product consumed successfully.
             } else {
                 // Handle Consumption error
                  Log.e("Billing", "Purchase consumption failed: ${billingResult.debugMessage}")
             }
         }
     }
    

H. Initiating the Purchase Flow (App Side)

  1. Launch Purchase: Use billingClient.launchBillingFlow() to launch the purchase flow using ProductDetails.

     fun startPurchaseFlow(product: ProductDetails) {
           val productDetailsParamsList =
               listOf(
                   BillingFlowParams.ProductDetailsParams.newBuilder()
                       .setProductDetails(product)
                       .build()
               )
           val billingFlowParams = BillingFlowParams.newBuilder()
               .setProductDetailsParamsList(productDetailsParamsList)
               .build()
    
           val billingResult = billingClient.launchBillingFlow(this, billingFlowParams)
           if(billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
                 // Handle launch billing flow error.
                 Log.e("Billing", "Launch billing flow failed with code: ${billingResult.responseCode}")
           }
       }
    

I. Cloud Pub/Sub and Real-Time Developer Notifications (RTDN) (Server Side)

Cloud Pub/Sub is a fully managed real-time messaging service from Google Cloud. Google Play uses Cloud Pub/Sub to deliver real-time developer notifications (RTDN) for subscription events. These notifications can be configured in the Google Play Console.

  • Use Cases:

    • Subscription Status Updates: Receive immediate notifications for subscription events like renewals, cancellations, and expirations.

    • Real-Time Data: Get up-to-date purchase data pushed directly to your server.

    • Automation: Automate backend processes such as granting access to features or content.

  • How it Works:

    1. Set Up Pub/Sub: In your Google Cloud Console project, create a Pub/Sub topic and subscription.

    2. Configure in Google Play Console: Link your Pub/Sub topic to your Google Play app in the console.

    3. Receive Notifications: Your server receives real-time messages whenever there is a subscription event.

J. Testing Your In-App Purchases (Crucial!)

Before launching your app with IAP, thorough testing is essential to ensure a seamless user experience. This includes setting up internal testers and external testers.

  1. Create a Test Group:

    • In the Google Play Console, navigate to "Release" > "Testing" (e.g., Internal testing, Closed testing, Open testing).

    • Choose a testing track based on how widely you want to release your app (internal is for your immediate team, closed is for a limited group, and open is for anyone who wants to join).

    • Create an email list (Google Group) to manage your testers or upload a CSV file of email addresses.

      • Click on Manage testers.

      • Now there are two ways you can add users.

  2. Add Testers to the Test Group:

    • Enter the email addresses of the testers or link the existing Google group to your application.
  3. Use Test Accounts:

    • Make sure your testers have Google accounts and those accounts are added to the testing group.

    • Testers will need to install the test version of your app from the Play Store (using the opt-in link provided in the test track setup).

  4. Testing Different Scenarios:

    • Test successful purchases.

    • Test purchase cancellations (user-initiated).

    • Test deferred purchases (e.g., "Ask to buy" in Family Sharing).

    • Test error handling.

    • Test subscription renewals, expirations, and cancellations (if you're using subscriptions).

    • Test restore Purchases

  5. Enable License Testing (for older versions of the Billing Library):

    • Navigate to License Testing: Go to the "Home Page Google Play Console" -> "Settings" → “License Testing“ section in the Google Play Console.

    • Add Test Accounts: Add the Google accounts you want to use for testing in-app purchases.

  • Important: For the testing of the subscriptions, remember that test subscriptions renew frequently (e.g., daily, weekly) to facilitate testing.

  • Remember, your test users should not use their primary Google account if they are purchasing in-app purchases with testing credentials, and it should only be used with a dedicated testing account.

III. Key Takeaways

  • Billing Library: The core API for handling purchases in your Android app.

  • Google Cloud Console: A platform to manage your Google Cloud resources.

  • Google Play Developer API: Enables you to programmatically manage your Google Play app and products.

  • Cloud Pub/Sub: Delivers real-time subscription notifications to your backend, which are configured in the Google Play console.

IV. Useful Links

Conclusion

Part 2, expanded with Google Cloud technologies and testing, provides a comprehensive picture of Android IAP. Thorough testing with test accounts ensures a smooth IAP experience for your users. Remember to consult the official documentation. Now that you have a strong understanding of Android IAP, Part 3 will guide you through the process of implementing In-App Purchases on iOS, providing insights into App Store Connect configuration and the StoreKit framework.