Mobile Development 17 min read

Mastering Google Play In‑App Billing for Consumable Products with Kotlin Coroutines

This article explains how to integrate Google Play's in‑app billing for consumable items on Android, covering terminology, end‑to‑end transaction flow, key implementation steps, Kotlin Coroutine‑based pipeline design, common pitfalls, and a practical checklist for reliable payment processing.

NetEase Cloud Music Tech Team
NetEase Cloud Music Tech Team
NetEase Cloud Music Tech Team
Mastering Google Play In‑App Billing for Consumable Products with Kotlin Coroutines

Introduction

China's mobile apps are expanding overseas, and Google Play in‑app billing is essential for transaction‑related apps. Although official docs provide a basic overview, developers often encounter numerous issues during integration. This guide focuses on consumable products and details the critical flow, technical points, and common pitfalls.

Terminology

One‑time vs. Subscription products : One‑time products are purchased once and include consumable (e.g., virtual coins) and non‑consumable (e.g., permanent upgrades). Subscription products renew automatically until cancelled. The article only discusses consumable items.

Consume vs. Acknowledge : Both confirm a purchase, but Acknowledge simply marks the order as non‑refundable and can be called via client API acknowledgePurchase() or Google server API. Consume is specific to consumable items, combines acknowledgment and makes the product purchasable again, and can only be invoked via client API consumeAsync().

Business server vs. Google server : The business server handles the app's own logic, while the Google server refers to Google Play’s billing backend.

Transaction Flow Overview

The high‑level transaction flow is illustrated below:

In practice, network instability, user errors, and other uncertainties can cause order anomalies. A robust design must handle all possible states to avoid over‑charging, under‑charging, or missed deliveries.

Key Process Details

Create Order : Business server creates its own order and maps it to a Google product ID.

Establish Connection : Use BillingClient to connect to Google Play; reconnect if the network drops.

Query Product : Retrieve product details from Google Console to obtain SKU information.

Launch Payment : Call launchBillingFlow() to show Google’s payment UI. Pass a obfuscated business order ID so the server can later correlate Google and business orders.

Confirm Order : After a successful callback, invoke consumeAsync() to acknowledge and enable repurchase; Google refunds unacknowledged orders after three days.

Fulfill Order : Business server grants the consumable (e.g., coins) ensuring idempotent execution.

Compensate Orders : Handle cases where payment succeeds but the user does not receive the item. Two scenarios: (1) Server receives Pub/Sub real‑time notifications and performs compensation; (2) Client proactively checks for unconsumed purchases on app start or purchase page and runs consumeAsync() followed by fulfillment.

Technical Implementation

The transaction is event‑driven, leading to deeply nested callbacks. To improve readability, the flow is refactored into a pipeline using Kotlin Coroutines CallbackFlow.

Logic Encapsulation

Each step is wrapped as a separate Flow module with defined input and output, allowing reuse and clear boundaries. Errors terminate the Flow via close() or cancel().

fun queryPurchasesFlow(client: BillingClient?): Flow<List<Purchase>> =
    callbackFlow {
        client?.queryPurchasesAsync(
            BillingClient.SkuType.INAPP
        ) { p0, p1 ->
            when (p0.responseCode) {
                BillingClient.BillingResponseCode.OK -> {
                    // emit the value to the flow
                    offer(p1)
                }
                else -> {
                    // close the flow
                    close()
                }
            }
        }
        awaitClose {
            // log & release resources
        }
    }

Pipeline Assembly

Modules are chained with flatMapConcat to create a linear processing sequence:

startConnectionFlow(client).flatMapConcat {
    querySkuDetailFlow(client, request)
}.flatMapConcat {
    launchBillingFlow(activity, client, it, request)
}.catch { e ->
    e.printStackTrace()
}.collect {
    processNext(it)
}

Compensation flows can be assembled similarly, reusing the same modules without duplicating code.

Overall Design

The billing component is organized into five layers:

Product Layer : Different checkout UI forms (Web, native).

Interface Layer : Simple APIs for initiating payment and order compensation.

Management Layer : Handles data (orders, products) and connection lifecycle.

Core Layer : Implements business logic such as connection establishment, product retrieval, logging, and monitoring.

Support Layer : Leverages existing app infrastructure.

Pitfalls & Checklist

Empty Product Details

billingClient.querySkuDetailsAsync(params) { p0, p1 ->
    when (p0.responseCode) {
        BillingClient.BillingResponseCode.OK -> {
            // p1 is empty
        }
    }
}

Root causes include:

Publishing an internal test version as required by Google Console.

Temporary delay after product creation; retry after a short interval.

Package name or signing certificate mismatch between the app and Play Console.

"Cannot purchase this item" Error

Possible reasons:

Test account not added to the internal test or license‑test list.

Test account has not accepted the invitation link.

Essential Preconditions

Device must have Google Services Framework installed.

Play Store region should not be Mainland China (or set a different country in settings).

Test account added to both internal‑test and license‑test lists.

Accept the test invitation.

Publish an internal test build (no review needed).

Package name and signing certificate must match those uploaded to Google.

Play Store app must be up‑to‑date.

Conclusion

Accurate payment handling is critical for revenue and user trust. Developers need a clear mental model of the end‑to‑end flow and should abstract the process into reusable, linear pipelines using Kotlin Coroutines. While the presented solution covers many scenarios, real‑world edge cases still require automated monitoring, alerting, and continuous improvement.

References

Google Play’s billing system overview

Integrate the Google Play Billing Library into your app

play‑billing‑samples (GitHub)

Is it possible to acknowledge consumable products from server side?

Kotlin flows on Android – Convert callback‑based APIs to flows

Simplifying APIs with coroutines and Flow

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Androidpayment integrationKotlin CoroutinesIn‑App PurchasesCallbackFlowConsumable ProductsGoogle Play Billing
NetEase Cloud Music Tech Team
Written by

NetEase Cloud Music Tech Team

Official account of NetEase Cloud Music Tech Team

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.