當 Play 帳款服務程式庫的呼叫觸發動作時,程式庫會傳回 BillingResult 回應,通知開發人員結果。舉例來說,如果您使用 queryProductDetailsAsync 為使用者取得可用優惠,回應代碼會包含 OK 代碼,並提供合適的 ProductDetails 物件,或是包含不同回應,指出無法提供 ProductDetails 物件的原因。
並非所有回應代碼皆表示發生錯誤。BillingResponseCode 參考頁面針對本指南說明的各個回應提供詳細說明。以下列舉幾個並非發生錯誤的回應代碼示例:
BillingClient.BillingResponseCode.OK:呼叫觸發的動作已成功完成。BillingClient.BillingResponseCode.USER_CANCELED:針對向使用者顯示 Play 商店 UI 操作流程的動作,此回應表示使用者已離開 UI 操作流程,但未完成操作程序。
如果回應代碼指出錯誤,有時原因可能是暫時性的,因此可以進行復原。若呼叫 Play 帳款服務程式庫方法時傳回 BillingResponseCode 值,表示此為可復原的狀況,您可以重試呼叫。在其他情況下,系統不會將錯誤視為暫時性的狀況,因此不建議重試。
暫時性錯誤會根據不同的因素採用不同的重試策略,例如錯誤發生時使用者是否處於工作階段狀態 (例如使用者正在進行購買流程),或錯誤是在背景發生 (例如,您在 onResume 期間查詢使用者的現有購買交易時)。下方的重試策略一節提供了這些不同策略的範例,可重試的 BillingResult 回應章節則提供建議,針對每個回應代碼選擇最適合的重試策略。
除了回應代碼之外,有些錯誤回應還包括用於偵錯及記錄的訊息。
重試策略
簡易重試
在使用者處於工作階段中時,您最好實作簡易的重試策略,盡可能避免干擾使用者體驗。在這種情況下,建議您採用簡易的重試策略,並以最大重試次數做為結束條件。
以下示例說明如何在建立 BillingClient 連線時,使用簡易的重試策略處理錯誤:
// Initialize the BillingClient. private val billingClient = BillingClient.newBuilder(context) .setListener(this) .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()) .build() private val coroutineScope = kotlinx.coroutines.CoroutineScope( kotlinx.coroutines.SupervisorJob() + kotlinx.coroutines.Dispatchers.Main.immediate ) private var connectionJob: kotlinx.coroutines.Job? = null // Establish a connection to Google Play. fun startBillingConnection() { connectionJob?.cancel() connectionJob = coroutineScope.launch { connectWithRetry() } } // Suspended helper to perform a single connection attempt private suspend fun connectBilling(): BillingResult = kotlinx.coroutines.suspendCancellableCoroutine { continuation -> billingClient.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(billingResult: BillingResult) { if (continuation.isActive) { continuation.resume(billingResult) } } override fun onBillingServiceDisconnected() { Log.e(TAG, "Google Play Billing Service disconnected") if (continuation.isActive) { continuation.resume( BillingResult.newBuilder() .setResponseCode(BillingClient.BillingResponseCode.SERVICE_DISCONNECTED) .setDebugMessage("Service disconnected during connection setup") .build() ) } else { startBillingConnection() } } }) } // Billing connection retry logic. This is a simple max retry pattern private suspend fun connectWithRetry() { val maxTries = 3 var tries = 1 var isConnectionEstablished = false while (tries <= maxTries && !isConnectionEstablished) { val billingResult = connectBilling() if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { isConnectionEstablished = true Log.d(TAG, "Billing response OK") } else { Log.e(TAG, "Billing connection retry failed: ${billingResult.debugMessage}") tries++ if (tries <= maxTries) { delay(2000L) // Wait 2 seconds before retrying } } } } fun cleanUp() { coroutineScope.cancel() } // ...
指數輪詢重試
建議您為 Play 帳款服務程式庫作業進行指數輪詢重試,因為這項策略是在背景執行,且在使用者處於工作階段時不會影響使用者體驗。
例如,在確認新購買交易時就很適合使用這項策略,因為它可以在背景執行,如果發生錯誤,也不需要即時確認。
private suspend fun acknowledge(purchaseToken: String): BillingResult = kotlinx.coroutines.suspendCancellableCoroutine { continuation -> val params = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchaseToken) .build() billingClient.acknowledgePurchase(params) { billingResult -> continuation.resumeWith(Result.success(billingResult)) } } private suspend fun queryPurchases(productType: String): Pair<BillingResult, List<Purchase>> = kotlinx.coroutines.suspendCancellableCoroutine { continuation -> val params = QueryPurchasesParams.newBuilder() .setProductType(productType) .build() billingClient.queryPurchasesAsync(params) { billingResult, purchaseList -> continuation.resumeWith(Result.success(Pair(billingResult, purchaseList))) } } suspend fun acknowledgePurchase(purchaseToken: String) { val retryDelayMs = 2000L val retryFactor = 2 val maxTries = 3 var tries = 1 var currentDelay = retryDelayMs var acknowledgePurchaseResult: BillingResult do { acknowledgePurchaseResult = acknowledge(purchaseToken) val playBillingResponseCode = acknowledgePurchaseResult.responseCode when (playBillingResponseCode) { BillingClient.BillingResponseCode.OK -> { Log.i(TAG, "Acknowledgement was successful") return } BillingClient.BillingResponseCode.ITEM_NOT_OWNED -> { Log.d(TAG, "Acknowledgement failed with ITEM_NOT_OWNED") val (billingResult, purchaseList) = queryPurchases(BillingClient.ProductType.SUBS) if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { purchaseList.forEach { purchase -> acknowledge(purchase.purchaseToken) } } return } 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}" ) if (tries < maxTries) { delay(currentDelay) currentDelay *= retryFactor tries++ } else { break } } else -> { Log.e( TAG, "Acknowledgement failed and cannot be retried -- " + "Response Code: ${acknowledgePurchaseResult.responseCode} -- " + "Debug Message: ${acknowledgePurchaseResult.debugMessage}" ) throw Exception("Failed to acknowledge the purchase!") } } } while (tries <= maxTries) throw Exception("Failed to acknowledge the purchase after $maxTries attempts!") }
可重試的 BillingResult 回應
NETWORK_ERROR (錯誤代碼 12)
問題
此錯誤表示裝置和 Play 系統之間的網路連線發生問題。
可能的解決方法
如要進行復原,請依據觸發錯誤的動作,判斷要採用簡易的重試方法或指數輪詢。
SERVICE_TIMEOUT (錯誤代碼 -3)
問題
這個錯誤表示在 Google Play 可回應之前,該要求已達到逾時上限。這可能是因 Play 帳款服務程式庫呼叫了動作要求,而執行動作延遲所導致。
可能的解決方法
這通常是暫時性問題。請依據傳回錯誤的動作,決定要採用簡易策略或指數輪詢策略來重試要求。
與下方的 SERVICE_DISCONNECTED 不同,與 Google Play 帳款服務的連線並未中斷,您只需重試先前嘗試執行的任何 Play 帳款服務程式庫作業。
SERVICE_DISCONNECTED (錯誤代碼 -1)
問題
此嚴重錯誤表示用戶端應用程式透過 BillingClient 連至 Google Play 商店服務的連線已中斷。
可能的解決方法
強烈建議:啟用自動重新連線服務
Play 帳款服務程式庫 8.0.0 版導入了enableAutoServiceReconnection()功能。強烈建議您在建構 BillingClient 時啟用這項功能。這樣一來,當服務中斷連線時,程式庫就能在發出帳單 API 呼叫時自動嘗試重新建立連線,大幅減少發生這類錯誤的次數。
Kotlin
val billingClient = BillingClient.newBuilder(context) .setListener(listener) .enablePendingPurchases( PendingPurchasesParams.newBuilder().enableOneTimeProducts().build() ) .enableAutoServiceReconnection() // Enable automatic service reconnection .build()
Java
BillingClient billingClient = BillingClient.newBuilder(context)
.setListener(listener)
.enablePendingPurchases()
.enableAutoServiceReconnection() // Enable automatic service reconnection
.build();
如果已啟用自動重新連線服務
Play Billing Library 會自動嘗試重新連線。如果在發出 API 呼叫時仍收到 SERVICE_DISCONNECTED 回應代碼,表示程式庫在自動嘗試後仍無法重新連線。在這種情況下,您應在應用程式中實作重試邏輯:
- 使用者啟動的動作 (工作階段內):簡單重試 API 呼叫。根本問題可能只是暫時性的。
- 背景要求:如果連線中斷時間較長,請實作指數輪詢重試機制,避免系統負載過重。
如果尚未啟用自動重新連線服務
為了避免發生這個錯誤,請一律透過 BillingClient.isReady() 呼叫,先檢查與 Google Play 服務的連線,再使用 Play 帳款服務程式庫進行呼叫。
如要嘗試從 SERVICE_DISCONNECTED 復原,您的用戶端應用程式應使用 BillingClient.startConnection,嘗試重新建立連線。
和 SERVICE_TIMEOUT 一樣,請依據觸發錯誤的動作決定要採用簡易重試或指數輪詢重試策略。
SERVICE_UNAVAILABLE (錯誤代碼 2)
重要注意事項:
自 Google Play 帳款服務程式庫 6.0.0 版起,網路問題將不再傳回 SERVICE_UNAVAILABLE。在帳單服務無法使用和已淘汰 SERVICE_TIMEOUT 的情況下,系統會傳回這個錯誤。
問題
這個暫時性錯誤表示 Google Play 帳款服務服務目前無法使用。在大多數情況下,這表示用戶端裝置和 Google Play 帳款服務之間發生網路連線問題。
可能的解決方法
這通常是暫時性問題。請依據傳回錯誤的動作,決定要採用簡易策略或指數輪詢策略來重試要求。
有別於 SERVICE_DISCONNECTED,與 Google Play 帳款服務的連線並未中斷,您只需重試先前嘗試執行的任何作業。
BILLING_UNAVAILABLE (錯誤代碼 3)
問題
這個錯誤代表購買流程中發生使用者結帳錯誤。以下列出幾項可能原因:
- 使用者裝置上的 Play 商店應用程式版本過舊。
- 使用者位於不支援的國家/地區。
- 使用者是企業使用者,且該企業的管理員已禁止使用者購買產品。
- Google Play 無法依據使用者的付款方式收取款項。舉例來說,使用者的信用卡可能已過期。
- 系統封鎖了 Play 商店應用程式 (例如在 OEM 客製化兒童模式中)。在這種情況下,
BillingResult會包含「Play Store is blocked」(Play 商店已遭封鎖) 的偵錯訊息。
可能的解決方法
在這種情況下,自動重試可能無法解決問題。不過,如果使用者能解決造成問題的原因,就能透過手動重試方式解決問題。舉例來說,如果使用者將 Play 商店更新至支援的版本,就能以手動重試方式執行初始作業。
如果錯誤是在使用者非處於工作階段的期間發生,重試可能不太合理。如果您收到因購買流程而產生的
BILLING_UNAVAILABLE錯誤,很可能是因為使用者在購買過程中收到來自 Google Play 的意見回饋,且可能知道問題為何。在這種情況下,您可以對使用者顯示錯誤訊息,指出系統發生錯誤,並提供「再試一次」按鈕,讓使用者在解決問題後可以手動重試。
ERROR (錯誤代碼 6)
問題
此為嚴重錯誤,表示 Google Play 本身發生了內部問題。
可能的解決方法
有時造成 ERROR 的 Google Play 內部問題是暫時性的,且您可以使用指數輪詢策略進行重試來緩解問題。使用者在工作階段中時,建議您進行簡易重試。
ITEM_ALREADY_OWNED
問題
此回應表示 Google Play 使用者已擁有他們要購買的訂閱項目或一次性消費產品。在大多數情況下,這並非暫時性錯誤。除非這項錯誤是過時的 Google Play 快取資料所造成。
可能的解決方法
為避免錯誤是由快取以外的問題所造成,請勿在使用者擁有某項產品時販售相同產品。顯示可供購買的產品時,請務必檢查使用者的授權,藉此篩選使用者可購買的產品。當用戶端應用程式因快取問題而收到這個錯誤時,就會觸發 Google Play 快取,從 Play 後端取得最新資料。在錯誤發生後進行重試,應該就能解決這個暫時性的情況。在收到 ITEM_ALREADY_OWNED 後呼叫 BillingClient.queryPurchasesAsync(),確認使用者是否已購買該產品。如果沒有,請實作簡易重試邏輯,重新嘗試購買。
ITEM_NOT_OWNED
問題
此購買回應表示 Google Play 使用者並未擁有想要取代、確認或使用的訂閱項目或一次性消費產品。在大多數情況下,這並非暫時性錯誤。除非這項錯誤是 Google Play 快取取得過時資料所導致。
可能的解決方法
如果錯誤是因快取問題而產生,則錯誤會觸發 Google Play 快取,讓系統使用 Play 後端的最新資料進行更新。在錯誤發生之後進行簡易重試策略,應該能解決這個特定的暫態錯誤。取得 ITEM_NOT_OWNED 後呼叫 BillingClient.queryPurchasesAsync(),檢查使用者是否已取得相應產品。如果沒有,請使用簡單的重試邏輯來重新嘗試購買。
無法重試的 BillingResult 回應
您無法使用重試邏輯從這些錯誤中復原。
FEATURE_NOT_SUPPORTED
問題
此無法重試的錯誤表示使用者的裝置不支援 Google Play 帳款服務功能,原因可能是 Play 商店版本較舊。
舉例來說,可能有部分使用者的裝置不支援應用程式內通訊功能。
可能的緩解措施
呼叫 Play 帳款服務程式庫之前,請使用 BillingClient.isFeatureSupported() 檢查功能支援情況。
when { billingClient.isReady -> { val billingResult = billingClient.isFeatureSupported(BillingClient.FeatureType.IN_APP_MESSAGING) if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { // use Feature } } }
USER_CANCELED
問題
使用者已退出結帳流程 UI。
可能的解決方法
這項資訊僅供參考,且可能會在不中斷的情況下失敗。
ITEM_UNAVAILABLE
問題
此使用者無法購買 Google Play 帳款服務訂閱或一次性消費產品。
可能的緩解措施
請務必讓您的應用程式按照建議,透過 queryProductDetailsAsync 更新產品詳細資料。請考量產品目錄在 Play 管理中心設定上的變更頻率,以便視需要執行額外更新。只嘗試在 Google Play 帳款服務上販售透過 queryProductDetailsAsync 傳回正確資訊的產品。檢查產品資格設定是否有任何不一致。舉例來說,您可能正在查詢某項產品,而該產品只在使用者嘗試購買的地區外販售。想在某個國家/地區販售應用程式內產品時,除了啟用產品外,您也必須在當地發布產品所屬的應用程式。
有時在特定測試期間,所有產品設定都正確無誤,但使用者仍會看到這個錯誤。原因可能是 Google 伺服器上的產品詳細資料傳播延遲。請稍後再試。
DEVELOPER_ERROR
問題
此為嚴重錯誤,表示您使用 API 的方式不當。舉例來說,向 BillingClient.launchBillingFlow 提供不正確的參數可能會發生這項錯誤。
可能的解決方法
請確認您正確使用各種 Play 帳款服務程式庫的呼叫。此外,請查看偵錯訊息,進一步瞭解錯誤內容。