當 Play 帳款服務程式庫的呼叫觸發動作時,程式庫會傳回 BillingResult
回應,通知開發人員結果。舉例來說,如果您使用 queryProductDetailsAsync
為使用者取得可用優惠,回應代碼會包含 OK 代碼,並提供合適的 ProductDetails
物件,或是包含不同回應,指出無法提供 ProductDetails
物件的原因。
並非所有回應代碼皆表示發生錯誤。BillingResponseCode
參考頁面針對本指南說明的各個回應提供詳細說明。以下列舉幾個並非發生錯誤的回應代碼示例:
BillingClient.BillingResponseCode.OK
:呼叫觸發的動作已成功完成。BillingClient.BillingResponseCode.USER_CANCELED
:針對向使用者顯示 Play 商店 UI 操作流程的動作,此回應表示使用者已離開 UI 操作流程,但未完成操作程序。
如果回應代碼指出錯誤,有時原因可能是暫時性的,因此可以進行復原。若呼叫 Play 帳款服務程式庫方法時傳回 BillingResponseCode
值,表示此為可復原的狀況,您可以重試呼叫。在其他情況下,系統不會將錯誤視為暫時性的狀況,因此不建議重試。
暫時性錯誤需要因應各種因素而採用不同的重試策略,例如:
錯誤是否發生在使用者處於工作階段時 (例如使用者在
購買流程中或會在背景執行錯誤,
例如,在 onResume
期間,查詢使用者現有的購買交易時。
下方的重試策略一節提供了這些不同策略的範例,可重試的 BillingResult
回應章節則提供建議,針對每個回應代碼選擇最適合的重試策略。
除了回應代碼之外,有些錯誤回應還包括用於偵錯及記錄的訊息。
重試策略
簡易重試
在使用者處於工作階段中時,您最好實作簡易的重試策略,盡可能避免干擾使用者體驗。在這種情況下,建議您採用簡易的重試策略,並以最大重試次數做為結束條件。
以下示例說明如何在建立 BillingClient
連線時,使用簡易的重試策略處理錯誤:
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)
}
...
}
指數輪詢重試
建議您為 Play 帳款服務程式庫作業進行指數輪詢重試,因為這項策略是在背景執行,且在使用者處於工作階段時不會影響使用者體驗。
例如,在確認新購買交易時就很適合使用這項策略,因為它可以在背景執行,如果發生錯誤,也不需要即時確認。
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 <T> 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
}
可重試的 BillingResult 回應
NETWORK_ERROR (錯誤代碼 12)
問題
此錯誤表示裝置和 Play 系統之間的網路連線發生問題。
可能的解決方法
如要進行復原,請依據觸發錯誤的動作,判斷要採用簡易的重試方法或指數輪詢。
SERVICE_TIMEOUT (錯誤代碼 -3)
問題
這個錯誤表示在 Google Play 可回應之前,該要求已達到逾時上限。這可能是因 Play 帳款服務程式庫呼叫了動作要求,而執行動作延遲所導致。
可能的解決方法
這通常是暫時性問題。請依據傳回錯誤的動作,決定要採用簡易策略或指數輪詢策略來重試要求。
與下方的 SERVICE_DISCONNECTED
不同,與 Google Play 帳款服務的連線並未中斷,您只需重試先前嘗試執行的任何 Play 帳款服務程式庫作業。
SERVICE_DISCONNECTED (錯誤代碼 -1)
問題
此嚴重錯誤表示用戶端應用程式透過 BillingClient
連至 Google Play 商店服務的連線已中斷。
可能的解決方法
為了避免發生這個錯誤,請一律透過 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 商店更新至支援的版本,就能以手動重試方式執行初始作業。
如果錯誤是在使用者非處於工作階段的期間發生,重試可能不太合理。如果您收到因購買流程而產生的 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 -> {
if (billingClient.isFeatureSupported(BillingClient.FeatureType.IN_APP_MESSAGING)) {
// use feature
}
}
}
USER_CANCELED
問題
使用者已退出結帳流程 UI。
可能的解決方法
這項資訊僅供參考,且可能會在不中斷的情況下失敗。
ITEM_UNAVAILABLE
問題
此使用者無法購買 Google Play 帳款服務訂閱或一次性消費產品。
可能的緩解措施
請務必讓您的應用程式按照建議,透過 queryProductDetailsAsync
更新產品詳細資料。請考量產品目錄在 Play 管理中心設定上的變更頻率,以便視需要執行額外更新。只嘗試在 Google Play 帳款服務上販售透過 queryProductDetailsAsync
傳回正確資訊的產品。檢查產品資格設定是否有任何不一致。舉例來說,您可能正在查詢某項產品,而該產品只在使用者嘗試購買的地區外販售。想在某個國家/地區販售應用程式內產品時,除了啟用產品外,您也必須在當地發布產品所屬的應用程式。
有時在特定測試期間,所有產品設定都正確無誤,但使用者仍會看到這個錯誤。原因可能是 Google 伺服器上的產品詳細資料傳播延遲。請稍後再試。
DEVELOPER_ERROR
問題
此為嚴重錯誤,表示您使用 API 的方式不當。舉例來說,向 BillingClient.launchBillingFlow
提供不正確的參數可能會發生這項錯誤。
可能的解決方法
請確認您正確使用各種 Play 帳款服務程式庫的呼叫。此外,請查看偵錯訊息,進一步瞭解錯誤內容。