-
Notifications
You must be signed in to change notification settings - Fork 4
Consumable Products
Muhammad Hammad edited this page Nov 29, 2025
·
1 revision
Consumable products are items that users can purchase multiple times, such as virtual currency, power-ups, or credits.
In v2.9.0+, consumables are NOT auto-consumed. You must manually call consumePurchase() after granting items to the user.
- Server verification: Verify on your server before granting items
- Error recovery: If granting fails, you don't lose the purchase
- Accurate tracking: Track exactly when items are granted
- Multi-quantity: Handle quantity > 1 correctly
val products = listOf(
PurchaseItem("coins_100", TYPE_IAP.PURCHASE, PurchaseCategory.CONSUMABLE),
PurchaseItem("coins_500", TYPE_IAP.PURCHASE, PurchaseCategory.CONSUMABLE),
PurchaseItem("gems_50", TYPE_IAP.PURCHASE, PurchaseCategory.CONSUMABLE)
)AppPurchase.getInstance().setPurchaseHistoryListener(object : PurchaseHistoryListener {
override fun onNewPurchase(productId: String, purchase: PurchaseResult) {
// Called when purchase is acknowledged
handlePurchase(productId, purchase)
}
override fun onPurchaseConsumed(productId: String, purchase: PurchaseResult) {
// Called after consumePurchase() completes
Log.d("Billing", "Consumed: $productId")
}
})fun handlePurchase(productId: String, purchase: PurchaseResult) {
// Check purchase state
if (!purchase.isPurchased()) {
// Pending payment - don't grant yet
return
}
// Grant items based on product and quantity
val quantity = purchase.quantity
when (productId) {
"coins_100" -> {
userBalance.coins += 100 * quantity
}
"coins_500" -> {
userBalance.coins += 500 * quantity
}
"gems_50" -> {
userBalance.gems += 50 * quantity
}
}
// Save user balance
saveUserBalance()
// NOW consume so user can buy again
AppPurchase.getInstance().consumePurchase(productId)
}┌─────────────┐ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐
│ Purchase │ ──► │ Acknowledge │ ──► │ onNewPurchase│ ──► │ Grant Items │
└─────────────┘ └──────────────┘ └──────────────┘ └──────┬──────┘
│
┌──────────────┐ ┌───────────────┐ │
│onPurchaseCon-│ ◄── │consumePurchase│ ◄───┘
│ sumed │ └───────────────┘
└──────────────┘
If users see this error, the product wasn't consumed. Reasons:
- App crashed before
consumePurchase()was called - Developer forgot to call
consumePurchase() - Old code using deprecated auto-consume
// On app start, check for unconsumed purchases
AppPurchase.getInstance().setPurchaseListener(object : PurchaseListener {
override fun onProductPurchased(orderId: String?, originalJson: String?) {
// This fires for unconsumed purchases too
// Your PurchaseHistoryListener will handle it
}
// ...
})
// Verify purchases to trigger callbacks
AppPurchase.getInstance().verifyPurchased(true)For important purchases, verify on your server:
override fun onNewPurchase(productId: String, purchase: PurchaseResult) {
if (purchase.hasVerificationData()) {
// Send to server
verifyPurchaseOnServer(
productId = productId,
token = purchase.purchaseToken,
json = purchase.originalJson!!,
signature = purchase.signature!!
) { isValid ->
if (isValid) {
grantItems(productId, purchase.quantity)
AppPurchase.getInstance().consumePurchase(productId)
} else {
// Handle invalid purchase
}
}
}
}class PurchaseTracker : PurchaseHistoryListener {
private val prefs: SharedPreferences
override fun onNewPurchase(productId: String, purchase: PurchaseResult) {
// Increment total count
val key = "total_purchased_$productId"
val current = prefs.getInt(key, 0)
prefs.edit().putInt(key, current + purchase.quantity).apply()
// Grant and consume
grantItems(productId, purchase.quantity)
AppPurchase.getInstance().consumePurchase(productId)
}
fun getTotalPurchased(productId: String): Int {
return prefs.getInt("total_purchased_$productId", 0)
}
}- Use Google Play Console license testers
- Test with real products in closed testing
- Test app crash scenarios (purchase acknowledged but not consumed)
- Test multi-quantity purchases if supported
AdManageKit v3.3.4 | GitHub | API Docs | Report Issue | Buy me a coffee