Реализация Google Play Billing (расходуемые покупки) для Android
Consumable в Google Play Billing — это INAPP-продукт, который после покупки нужно явно «потребить» через consumeAsync(). Пока покупка не consumed, повторная покупка того же продукта невозможна: BillingClient.launchBillingFlow вернёт ITEM_ALREADY_OWNED. Это главное отличие от iOS, где consumable-логика определяется на стороне приложения, а не платформы.
Порядок consume и риск двойного начисления
Паттерн «купил → начислил → consume» работает только при стабильной сети. В реальности:
// Неправильно: consume сразу после получения покупки
billingClient.consumeAsync(params) { result, token ->
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
addCoins(100) // краш здесь = consume прошёл, монеты не начислены
}
}
Правильный порядок с серверной архитектурой:
- Получаем
purchaseTokenизPurchasesUpdatedListener - Отправляем token на сервер — сервер через Google Play Developer API верифицирует покупку и идемпотентно начисляет валюту
- Только после ответа
200 OK— вызываемconsumeAsyncна клиенте - Без шага 3 — не consume, транзакция остаётся открытой
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) {
// Логируем, но не паникуем — следующий раз consume при queryPurchasesAsync
}
}
}
Незавершённые consume при перезапуске
При каждом запуске приложения — queryPurchasesAsync для INAPP-продуктов. Если находим покупку в PURCHASED состоянии — это незавершённая транзакция (либо consume не дошёл, либо краш). Повторяем шаги верификации и consume.
Сервер должен хранить purchaseToken как idempotency key. Повторный запрос с тем же токеном не начисляет валюту дважды — возвращает ранее созданный результат.
Pending purchases для consumables
В регионах, где доступна оплата через киоски или наложенный платёж (Индия, Бразилия, Юго-Восточная Азия), покупка может быть PENDING часами. enablePendingPurchases() с enableOneTimeProducts() обязателен с Billing Library 6. Для pending-состояния — не consume, ждём перехода в PURCHASED через PurchasesUpdatedListener или queryPurchasesAsync при следующем старте.
Сроки — 2–3 дня: интеграция с идемпотентной серверной логикой, обработка pending, тесты через лицензионных тестеров Google Play.







