Implementing Google Play Billing (Consumable Purchases) for Android
Consumable in Google Play Billing is an INAPP product that must be explicitly "consumed" through consumeAsync() after purchase. Until the purchase is consumed, repeated purchase of the same product is impossible: BillingClient.launchBillingFlow returns ITEM_ALREADY_OWNED. This is the main difference from iOS, where consumable logic is determined on the application side, not the platform.
Order of consume and double-charge risk
The "buy → credit → consume" pattern works only with stable network. In reality:
// Wrong: consume immediately after receiving purchase
billingClient.consumeAsync(params) { result, token ->
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
addCoins(100) // crash here = consume passed, coins not credited
}
}
Correct order with server architecture:
- Get
purchaseTokenfromPurchasesUpdatedListener - Send token to server — server verifies the purchase through Google Play Developer API and idempotently credits currency
- Only after
200 OKresponse — callconsumeAsyncon the client - Without step 3 — no consume, transaction remains open
scope.launch {
val credited = serverApi.creditPurchase(purchase.purchaseToken)
if (credited) {
val consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
val result = billingClient.consumePurchase(consumeParams)
if (result.billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
// Log, but don't panic — consume next time on queryPurchasesAsync
}
}
}
Incomplete consume on restart
On every app startup — queryPurchasesAsync for INAPP products. If we find a purchase in PURCHASED state — this is an incomplete transaction (either consume didn't arrive or crash). Repeat verification and consume steps.
Server must store purchaseToken as an idempotency key. Repeated request with the same token does not credit currency twice — returns the previously created result.
Pending purchases for consumables
In regions where payment through kiosks or cash-on-delivery is available (India, Brazil, Southeast Asia), a purchase can be PENDING for hours. enablePendingPurchases() with enableOneTimeProducts() is mandatory from Billing Library 6. For pending state — do not consume, wait for transition to PURCHASED through PurchasesUpdatedListener or queryPurchasesAsync on next startup.
Estimated time — 2–3 days: integration with idempotent server logic, pending handling, testing through Google Play licensed testers.







