Someone opens the payment screen in your Android app, pauses, then closes it.
The app responds: “Payment failed.”
They may have simply changed their mind. Now the app is telling them something went wrong.
What did Google Play actually return?
Google Play Billing has a response for leaving the purchase flow: USER_CANCELED. Google lists it among the responses that do not indicate an error.
It tells you that the person left without completing the flow. It doesn’t tell you why.
The problem can start with a simple shortcut in the code: treating every non-OK response as a failed payment.
Give cancellation its own branch:
val purchasesUpdatedListener = PurchasesUpdatedListener { result, purchases ->
setCheckoutLoading(false)
when (result.responseCode) {
BillingClient.BillingResponseCode.USER_CANCELED -> {
recordCheckoutCanceled()
// Leave the person free to browse. No payment-error alert.
}
BillingClient.BillingResponseCode.OK -> {
handlePurchaseUpdates(purchases.orEmpty())
}
else -> {
handleOtherBillingResponse(result)
}
}
}
The helper functions here stand in for your app’s own code. Checkout closes over the browsing screen in this example, so there’s no extra navigation to do.
The purchase checks still belong in handlePurchaseUpdates. Don’t unlock content just because the response is OK: wait for a pending purchase to complete, then verify it before granting access. Google’s purchase-processing guide covers those steps.
Other response codes need their own handling, too. Some call for a retry; others don’t. That’s the job of handleOtherBillingResponse here. This callback is only part of the integration.
The report can get this wrong, too
Even after the warning is fixed, the same mistake can remain in your reports.
If someone backing out counts as “purchase failed” alongside a network problem, you can end up investigating the wrong thing.
You may still want to know how often people leave checkout. Keep that information, but separate it from technical errors.
An outcome such as checkout_canceled is enough to make the difference clear. Record it once for that attempt, without purchase tokens or other sensitive data.
One path worth checking
Open checkout in your test setup, then close it without completing the purchase. This checks leaving a new checkout, not cancelling an existing subscription.
Check that:
- The loading state ends.
- The person can keep browsing without a payment-error warning.
- The report records a cancellation rather than a technical error.
- No new access is granted, and any access they already had stays intact.
Top comments (0)