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)
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() }
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)
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)
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)
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. } }
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)
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)
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:
Set Up Pub/Sub: In your Google Cloud Console project, create a Pub/Sub topic and subscription.
Configure in Google Play Console: Link your Pub/Sub topic to your Google Play app in the console.
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.
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.
Add Testers to the Test Group:
- Enter the email addresses of the testers or link the existing Google group to your application.
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).
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
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
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.