Handle BillingResult response codes | Google Play’s billing system | Android Developers
When a Play Billing Library call triggers an action, the library returns a
BillingResult
response to inform developers of the outcome. For example, if you use
queryProductDetailsAsync
to get the available offers for the user, the response code either contains an
OK code and provides the right ProductDetails
object, or it contains a different response that indicates the reason why the
ProductDetails
object couldn’t be provided.
Not all response codes are errors. The BillingResponseCode
reference page provides a detailed description of each of the responses
discussed in this guide.
Some examples of response codes that don’t indicate errors are:
BillingClient.BillingResponseCode.OK
: the action triggered by the call was completed successfully.BillingClient.BillingResponseCode.USER_CANCELED
: for actions that display Play Store UI flows to the user, this response
indicates the user navigated away from those UI flows without completing the
process.
When the response code does indicate an error, the cause is sometimes due to
transient conditions, and thus recovery is possible. When a call to a Play
Billing Library method returns a BillingResponseCode
value that indicates a recoverable condition, you should retry the call. In
other cases, conditions are not considered transient and therefore a retry is
not recommended.
Transient errors call for different retry strategies depending on factors like
whether the error happens when users are in session—for example, when a user is
going through a purchase flow—or the error happens in the background—for
example, when you’re querying the user’s existing purchases duringonResume
.
The retry strategies section below provides examples of
these different strategies and the RetriableBillingResult
Responses section
recommends which strategy works best for each response code.Bạn đang đọc: Handle BillingResult response codes | Google Play’s billing system | Android Developers
In addition to the response code, some error responses include messages for debugging and logging purposes .
Retry strategies
Simple retry
In situations where the user is in session, it’s better to implement a simple retry strategy so that the error disrupts the user experience as little as possible. In that case, we recommend a simple retry strategy with a maximum number of attempts as an exit condition .
The following example demonstrates a simple retry strategy to handle an error
when establishing aBillingClient
connection:class BillingClientWrapper(context: Context) : PurchasesUpdatedListener { // Initialize the BillingClient. private val billingClient = BillingClient.newBuilder(context) .setListener(this) .enablePendingPurchases() .build() // Establish a connection to Google Play. fun startBillingConnection() { billingClient.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(billingResult: BillingResult) { if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { Log.d(TAG, "Billing response OK") // The BillingClient is ready. You can now query Products Purchases. } else { Log.e(TAG, billingResult.debugMessage) retryBillingServiceConnection() } } override fun onBillingServiceDisconnected() { Log.e(TAG, "GBPL Service disconnected") retryBillingServiceConnection() } }) } // Billing connection retry logic. This is a simple max retry pattern private fun retryBillingServiceConnection() { val maxTries = 3 var tries = 1 var isConnectionEstablished = false do { try { billingClient.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(billingResult: BillingResult) { if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { isConnectionEstablished = true Log.d(TAG, "Billing connection retry succeeded.") } else { Log.e( TAG, "Billing connection retry failed: ${billingResult.debugMessage}" ) } } }) } catch (e: Exception) { e.message?.let { Log.e(TAG, it) } tries++ } } while (tries <= maxTries && !isConnectionEstablished) } ... }
Exponential backoff retry
We recommend using exponential backoff for Play Billing Library operations that happen in the background and don't affect the user experience while the user is in session .
For example, it would be appropriate to implement this when acknowledging new purchases because this operation can happen in the background, and acknowledgment doesn't need to happen in real time if an error occurs .private fun acknowledge(purchaseToken: String): BillingResult { val params = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchaseToken) .build() var ackResult = BillingResult() billingClient.acknowledgePurchase(params) { billingResult -> ackResult = billingResult } return ackResult } suspend fun acknowledgePurchase(purchaseToken: String) { val retryDelayMs = 2000L val retryFactor = 2 val maxTries = 3 withContext(Dispatchers.IO) { acknowledge(purchaseToken) } AcknowledgePurchaseResponseListener { acknowledgePurchaseResult -> val playBillingResponseCode = PlayBillingResponseCode(acknowledgePurchaseResult.responseCode) when (playBillingResponseCode) { BillingClient.BillingResponseCode.OK -> { Log.i(TAG, "Acknowledgement was successful") } BillingClient.BillingResponseCode.ITEM_NOT_OWNED -> { // This is possibly related to a stale Play cache. // Querying purchases again. Log.d(TAG, "Acknowledgement failed with ITEM_NOT_OWNED") billingClient.queryPurchasesAsync( QueryPurchasesParams.newBuilder() .setProductType(BillingClient.ProductType.SUBS) .build() ) { billingResult, purchaseList -> when (billingResult.responseCode) { BillingClient.BillingResponseCode.OK -> { purchaseList.forEach { purchase -> acknowledge(purchase.purchaseToken) } } } } } in setOf( BillingClient.BillingResponseCode.ERROR, BillingClient.BillingResponseCode.SERVICE_DISCONNECTED, BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE, ) -> { Log.d( TAG, "Acknowledgement failed, but can be retried -- Response Code: ${acknowledgePurchaseResult.responseCode} -- Debug Message: ${acknowledgePurchaseResult.debugMessage}" ) runBlocking { exponentialRetry( maxTries = maxTries, initialDelay = retryDelayMs, retryFactor = retryFactor ) { acknowledge(purchaseToken) } } } in setOf( BillingClient.BillingResponseCode.BILLING_UNAVAILABLE, BillingClient.BillingResponseCode.DEVELOPER_ERROR, BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED, ) -> { Log.e( TAG, "Acknowledgement failed and cannot be retried -- Response Code: ${acknowledgePurchaseResult.responseCode} -- Debug Message: ${acknowledgePurchaseResult.debugMessage}" ) throw Exception("Failed to acknowledge the purchase!") } } } } private suspend fun
exponentialRetry( maxTries: Int = Int.MAX_VALUE, initialDelay: Long = Long.MAX_VALUE, retryFactor: Int = Int.MAX_VALUE, block: suspend () -> T ): T? { var currentDelay = initialDelay var retryAttempt = 1 do { runCatching { delay(currentDelay) block() } .onSuccess { Log.d(TAG, "Retry succeeded") return@onSuccess; } .onFailure { throwable -> Log.e( TAG, "Retry Failed -- Cause: ${throwable.cause} -- Message: ${throwable.message}" ) } currentDelay *= retryFactor retryAttempt++ } while (retryAttempt < maxTries) return block() // last attempt } Retriable BillingResult responses
Note:This error is only returned in Google Play Billing Library 6 and up. Previous versions are not supported .
Problem
This error indicates that there was a problem with the network connection between the device and Play systems .
Possible resolution
To avoid this error as much as possible, always check the connection to Google
Play services before making calls with the Play Billing Library by calling
BillingClient.isReady()
.To attempt recovery from
NETWORK_ERROR
,
your client app should try to re-establish the connection using
BillingClient.startConnection
.Use simple retries or exponential backoff, depending on which action triggered the error .
Note:SERVICE_TIMEOUT
is deprecated.This error is only returned in Google Play Billing Library 5.2.0 and earlier. Starting with Google Play Billing 6.0.0, SERVICE_UNAVAILABLE is returned for the problem described below, andis deprecated .Problem
This error indicates that the request has reached the maximum timeout before Google Play is able to respond. This could be caused, for example, by a delay in the execution of the action requested by the Play Billing Library call .
Possible resolution
This is usually a transient issue. Retry the request using either either a simple or exponential backoff strategy, depending on which action returned the error .
Unlike
SERVICE_DISCONNECTED
below, the connection to the Google Play Billing service is not severed, and you
only need to retry whatever Play Billing Library operation was attempted.Problem
This fatal error indicates that the client app’s connection to the Google Play
Store service via theBillingClient
has been severed.Possible resolution
To avoid this error as much as possible, always check the connection to Google
Play services before making calls with the Play Billing Library by calling
BillingClient.isReady()
.To attempt recovery from
SERVICE_DISCONNECTED
, your client app should try to re-establish the connection using
BillingClient.startConnection
.Just like with
SERVICE_TIMEOUT
, use simple retries or exponential backoff, depending on which action triggered
the error.Important Note:
Starting in Google Play Billing Library 6.0.0,
SERVICE_UNAVAILABLE
is no
longer returned for network issues. It is returned when the billing service is
unavailable and the deprecatedSERVICE_TIMEOUT
case scenarios.Problem
This transient error indicates the Google Play Billing service is currently
unavailable. In most cases, this means there is a network connection issue
anywhere between the client device and Google Play Billing services.Xem thêm: Định vị xe máy BK88M
Possible resolution
This is usually a transient issue. Retry the request using either either a simple or exponential backoff strategy, depending on which action returned the error .
Unlike
SERVICE_DISCONNECTED
, the connection to the Google Play Billing service is not severed, and you need
to retry whatever operation is being attempted.Problem
This error indicates that a user billing error occurred during the purchase process. Examples of when this can occur include :
- The Play Store app on the user's device is out of date.
- The user is in an unsupported country.
- The user is an enterprise user, and their enterprise admin has disabled users
from making purchases.- Google Play is unable to charge the user’s payment method. For example, the
user's credit card might have expired.Possible resolution
Automatic retries are unlikely to help in this case. However, a manual retry can help if the user addresses the condition that caused the issue. For example, if the user updates their Play Store version to a supported version, then a manual retry of the initial operation could work .
If this error occurs when the user is not in session, retrying might not make
sense.
When you receive aBILLING_UNAVAILABLE
error as a result of the purchase flow, it’s very likely the user received
feedback from Google Play during the purchase process and might be aware of what
went wrong. In this case, you could show an error message specifying something
went wrong and offer a “Try again” button to give the user the option of a
manual retry after they address the issue.ERROR (Error Code 6)
Problem
This is a fatal error that indicates an internal problem with Google Play itself .
Possible resolution
Sometimes internal Google Play problems that lead to
ERROR
are transient, and a retry with an exponential backoff can be implemented for
mitigation. When users are in session, a simple retry is preferable.Problem
This response indicates that the Google Play user already owns the subscription or one-time purchase product they are attempting to purchase. In most cases, this is not a transient error, except when it is caused by a stale Google Play’s cache .
Possible resolution
To avoid this error happening when the cause is not a cache issue, don't offer a
product for purchase when the user already owns it. Make sure you check the
user’s entitlements when you show the products available for purchase, and
filter what the user can purchase accordingly.
When the client app receives this error due to a cache issue, the error triggers
Google Play’s cache to get updated with the latest data from Play’s backend.
Retrying after the error should resolve this specific transient instance in this
case. CallBillingClient.queryPurchasesAsync()
after getting anITEM_ALREADY_OWNED
to check if the user has acquired the product, and if it’s not the case
implement a simple retry logic to reattempt the purchase.Problem
This purchase response indicates that the Google Play user does not own the subscription or one-time purchase product the user is attempting to replace, acknowledge or consume. This is not a transient error in most cases except when it is caused by Google Play’s cache getting into a stale state .
Possible resolution
When the error is received because of a cache issue, the error triggers Google
Play’s cache to get updated with the latest data from Play’s backend. Retrying
with a simple retry strategy after the error should resolve this specific
transient instance. CallBillingClient.queryPurchasesAsync()
after getting anITEM_NOT_OWNED
to check if the user has
acquired the product. If they have not, use simple retry logic to reattempt the
purchase.Non-Retriable BillingResult responses
You can't recover from these errors using retry logic .
Problem
This non-retriable error indicates that the Google Play Billing feature is not supported on the user's device, likely due to an old Play Store version .
For example, perhaps some of your users ' devices don't tư vấn in-app messaging .Possible mitigation
Use
BillingClient.isFeatureSupported()
to check feature support before making the call to the Play Billing
Library.when { billingClient.isReady -> { if (billingClient.isFeatureSupported(BillingClient.FeatureType.IN_APP_MESSAGING)) { // use feature } } }
Problem
The user has clicked out of the billing flow UI .
Possible resolution
This is informational only and can fail gracefully .
Problem
The Google Play Billing subscription or one-time purchase product is not available for purchase for this user .
Possible mitigation
Make sure your app refreshes the product details via
queryProductDetailsAsync
as recommended. Take into account how often
your product catalog changes on the Play Console configuration to implement
extra refreshes if needed.
Only attempt to sell products on Google Play Billing that return the right
information viaqueryProductDetailsAsync
.
Check the product eligibility configuration for any inconsistencies.
For example, you might be querying for a product that is only available for a
region other than the one the user is trying to purchase.
To be available for purchase, a product needs to be active, its app needs to be
published, and its app needs to be available in the user's country.Xem thêm: Định vị xe máy BK88M
Sometimes, in particular during testing, everything is correct in the product configuration, but users still see this error. This might be due to a propagation delay of the product details across Google’s servers. Try again later .
Problem
This is a fatal error that indicates you're improperly using an API.
For example, supplying incorrect parameters toBillingClient.launchBillingFlow
can
cause this error.
Possible resolution
Make sure that you are correctly using the different Play Billing Library calls. Also, check the debug message for more info about the error .
Source: https://thomaygiat.com
Category : Ứng Dụng
Máy Giặt Electrolux Lỗi E51 Làm Tăng Nguy Cơ Hỏng Nặng
Mục ChínhMáy Giặt Electrolux Lỗi E51 Làm Tăng Nguy Cơ Hỏng NặngNguyên Nhân Máy Giặt Electrolux Báo Lỗi E511. Động Cơ Hỏng2. Mạch Điều Khiển…
Hậu quả từ lỗi H-29 tủ lạnh Sharp Side by Side
Mục ChínhHậu quả từ lỗi H-29 tủ lạnh Sharp Side by SideMã Lỗi H-29 Tủ Lạnh Sharp Là Gì?Tầm Quan Trọng Của Việc Khắc Phục…
Hỏi đáp giấy dán tường chống ẩm mốc
Mục ChínhGiải Mã 25+ Hỏi Đáp Giấy Dán Tường Chống Ẩm MốcChống ẩm mốc cùng giấy dán tường1. Nguyên nhân gây ẩm mốc trong không…
Máy Giặt Electrolux Lỗi E-45 Kiểm Tra Ngay!
Mục ChínhMáy Giặt Electrolux Lỗi E-45 Kiểm Tra Ngay!Định Nghĩa Mã Lỗi E-45 Máy Giặt ElectroluxNguyên nhân lỗi E-45 máy giặt Electrolux1. Cảm biến cửa…
Hướng dẫn sửa Tủ lạnh Sharp lỗi H-28 chi tiết và an toàn
Mục ChínhHướng dẫn sửa Tủ lạnh Sharp lỗi H-28 chi tiết và an toànLỗi H-28 Trên Tủ Lạnh Sharp Là Gì?Dấu Hiệu Nhận Biết Lỗi…
Máy giặt Electrolux gặp lỗi E-44 điều bạn nên làm
Mục ChínhMáy giặt Electrolux gặp lỗi E-44 điều bạn nên làmĐịnh nghĩa mã lỗi E-44 máy giặt Electrolux5 Nguyên nhân gây ra mã lỗi E-44…