Google Play Billing provides a comprehensive API surface for handling in-app purchases and subscriptions on Android. Most developers are comfortable with the standard purchase flow: launch the billing flow, receive a result, acknowledge the purchase, and grant entitlements. But production billing systems must handle a wider range of scenarios that are often underrepresented in tutorials and sample code. Pending purchases, multi-quantity consumables, subscription downgrades with proration, and the ITEM_ALREADY_OWNED response are all situations your app will encounter in the real world, and mishandling any of them can result in lost revenue, confused users, or failed purchases.
In this article, we’ll explore the most common edge cases in Google Play Billing, understand why they occur, examine how to handle each one correctly with the Play Billing Library, and see how RevenueCat simplifies these scenarios so you can focus on your product instead of billing infrastructure.
The fundamental problem: the happy path is not enough
Most billing implementations start from the sample code in the Android documentation:
This handles a successful, immediate purchase. But what happens when the payment is delayed by 48 hours because the user is paying at a convenience store? What happens when the user already owns the item because a previous acknowledgment failed silently? What happens when a subscription downgrade takes effect at the next renewal instead of immediately? Each of these scenarios requires specific handling, and ignoring them leads to support tickets, refund requests, and lost subscribers.
Pending purchases: when payment is not immediate
Not all purchases complete instantly. Certain payment methods, including cash payments at convenience stores, bank transfers, and some carrier billing options, require asynchronous processing. When a user initiates a purchase with one of these methods, Google Play returns a purchase in the PENDING state rather than the PURCHASED state.
Why pending purchases happen
Pending purchases are common in markets where credit card penetration is low:
| Payment method | Common regions | Typical processing time |
|---|---|---|
| Cash payments (convenience stores) | Japan, Mexico, Indonesia | 24-48 hours |
| Bank transfers | Germany, Netherlands, Brazil | 1-3 business days |
| Carrier billing (some carriers) | Various | Minutes to hours |
If your app is available globally, you’re bound to encounter pending purchases. Ignoring this state means users in these regions cannot purchase your products at all — or worse, they see confusing behavior where their purchase ‘disappears’.
Detecting and handling the pending state
The PurchasesUpdatedListener receives pending purchases alongside completed ones. The critical distinction is in the purchaseState field:
The key rule is: do not grant entitlements for pending purchases. The user has not paid yet. Instead, record the pending purchase and communicate the status clearly:
Completing a pending purchase
When the payment is eventually confirmed, your app receives an updated purchase via onPurchasesUpdated or through queryPurchasesAsync. The purchaseState will now be PURCHASED, and you can proceed with acknowledgment and entitlement granting.
However, there is a subtlety: the user might not have your app open when the payment completes. Your backend should handle this through Real-Time Developer Notifications (RTDN). When you receive a ONE_TIME_PRODUCT_PURCHASED or SUBSCRIPTION_PURCHASED notification for a previously pending token, your backend should update the entitlement and notify the user: