rc-error-handling

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Error Handling

错误处理

Phase 1: Understand

第一阶段:理解

With raw Google Play Billing you enumerate every
BillingResponseCode
, split them into retriable and non retriable groups, and build backoff retry logic. RevenueCat collapses this into a single type you deal with:
PurchasesError
.
kotlin
public class PurchasesError(
    val code: PurchasesErrorCode,
    val underlyingErrorMessage: String? = null,
) {
    val message: String // technical description, for logs
}
Key facts you rely on:
  • PurchasesErrorCode
    is a cross platform enum with stable, readable codes.
  • error.message
    is a technical string. It belongs in logs, not in the UI.
  • awaitPurchase()
    throws
    PurchasesTransactionException
    , which adds a
    userCancelled: Boolean
    flag.
  • Every other
    await*
    call (
    awaitOfferings
    ,
    awaitGetProducts
    ,
    awaitCustomerInfo
    ,
    awaitRestore
    ) throws
    PurchasesException
    .
  • The SDK already retries transient billing and network failures internally. Any error that reaches you has exhausted the SDK retry budget. You do not add your own backoff loop. The only retry you implement is a user triggered "Try Again" button.
在原生Google Play Billing中,你需要枚举每个
BillingResponseCode
,将它们分为可重试和不可重试组,并实现退避重试逻辑。而RevenueCat将这些简化为一种你只需处理的类型:
PurchasesError
kotlin
public class PurchasesError(
    val code: PurchasesErrorCode,
    val underlyingErrorMessage: String? = null,
) {
    val message: String // 技术描述,用于日志记录
}
你需要了解的关键信息:
  • PurchasesErrorCode
    是一个跨平台枚举,具有稳定且易读的错误码。
  • error.message
    是技术字符串,应仅用于日志,不要展示在UI中。
  • awaitPurchase()
    会抛出
    PurchasesTransactionException
    ,该异常包含
    userCancelled: Boolean
    标志。
  • 其他所有
    await*
    调用(
    awaitOfferings
    awaitGetProducts
    awaitCustomerInfo
    awaitRestore
    )都会抛出
    PurchasesException
  • SDK已在内部对临时账单和网络故障进行重试。任何到达你这里的错误都已耗尽SDK的重试次数,你无需自行添加退避循环,仅需实现用户触发的“重试”按钮即可。

Phase 2: Plan

第二阶段:规划

Before writing a
catch
block, decide three things:
  1. Which
    await*
    call are you wrapping? That picks the exception type.
  2. Which codes have specific handling? Everything else falls into a generic branch.
  3. What user facing string does each handled code map to?
Use this table to categorize
PurchasesErrorCode
values and pick the UX response.
CodeMeaningHandling
PurchaseCancelledError
User backed out of the flowDo nothing.
userCancelled
is also
true
.
ProductAlreadyPurchasedError
Product already active for the userRefresh
CustomerInfo
and check entitlements.
PaymentPendingError
Purchase entered pending stateShow a pending message. Wait for
UpdatedCustomerInfoListener
.
NetworkError
Request failed due to connectivityPrompt the user to retry.
StoreProblemError
Google Play issuePrompt to retry or update Play Store.
PurchaseNotAllowedError
Device or account cannot purchaseShow an explanatory message.
IneligibleError
User not eligible for the offerShow the base plan instead.
Exception type decision:
CallException to catch
userCancelled
available?
awaitPurchase()
PurchasesTransactionException
Yes
awaitRestore()
PurchasesException
No
awaitOfferings()
PurchasesException
No
awaitGetProducts()
PurchasesException
No
awaitCustomerInfo()
PurchasesException
No
编写
catch
块之前,先确定三件事:
  1. 你要包装哪个
    await*
    调用?这会决定异常类型。
  2. 哪些错误码需要特定处理?其他所有错误都归入通用分支。
  3. 每个需处理的错误码对应什么面向用户的提示字符串?
使用下表对
PurchasesErrorCode
值进行分类,并选择对应的UX响应方式。
错误码含义处理方式
PurchaseCancelledError
用户退出了购买流程不做任何操作。此时
userCancelled
也为
true
ProductAlreadyPurchasedError
用户已拥有该产品的使用权刷新
CustomerInfo
并检查权益。
PaymentPendingError
购买进入待处理状态显示待处理提示信息,等待
UpdatedCustomerInfoListener
回调。
NetworkError
因网络连接问题导致请求失败提示用户重试。
StoreProblemError
Google Play出现问题提示用户重试或更新Google Play商店。
PurchaseNotAllowedError
设备或账户无法进行购买显示解释性提示信息。
IneligibleError
用户不符合该优惠的参与条件展示基础方案。
异常类型选择:
调用方法需要捕获的异常是否支持
userCancelled
awaitPurchase()
PurchasesTransactionException
awaitRestore()
PurchasesException
awaitOfferings()
PurchasesException
awaitGetProducts()
PurchasesException
awaitCustomerInfo()
PurchasesException

Phase 3: Execute

第三阶段:实施

Purchase errors

购买错误处理

Check
userCancelled
first and return silently. Then branch on
error.code
.
kotlin
try {
    val result = Purchases.sharedInstance.awaitPurchase(params)
    handleSuccess(result.customerInfo)
} catch (e: PurchasesTransactionException) {
    if (e.userCancelled) return
    when (e.error.code) {
        PurchasesErrorCode.PaymentPendingError -> showPendingMessage()
        PurchasesErrorCode.ProductAlreadyPurchasedError -> {
            val info = Purchases.sharedInstance.awaitCustomerInfo()
            handleSuccess(info)
        }
        PurchasesErrorCode.NetworkError -> showRetryDialog()
        else -> showGenericError(userFacingMessage(e.error))
    }
}
先检查
userCancelled
,如果为true则静默返回。然后根据
error.code
进行分支处理。
kotlin
try {
    val result = Purchases.sharedInstance.awaitPurchase(params)
    handleSuccess(result.customerInfo)
} catch (e: PurchasesTransactionException) {
    if (e.userCancelled) return
    when (e.error.code) {
        PurchasesErrorCode.PaymentPendingError -> showPendingMessage()
        PurchasesErrorCode.ProductAlreadyPurchasedError -> {
            val info = Purchases.sharedInstance.awaitCustomerInfo()
            handleSuccess(info)
        }
        PurchasesErrorCode.NetworkError -> showRetryDialog()
        else -> showGenericError(userFacingMessage(e.error))
    }
}

Non purchase errors

非购买错误处理

Catch
PurchasesException
and branch on the code. Use offline or cached fallbacks where you have them.
kotlin
try {
    val offerings = Purchases.sharedInstance.awaitOfferings()
    displayOfferings(offerings)
} catch (e: PurchasesException) {
    when (e.error.code) {
        PurchasesErrorCode.NetworkError -> showOfflineFallback()
        else -> logError(e.error)
    }
}
捕获
PurchasesException
并根据错误码分支处理,如有离线或缓存回退方案则使用。
kotlin
try {
    val offerings = Purchases.sharedInstance.awaitOfferings()
    displayOfferings(offerings)
} catch (e: PurchasesException) {
    when (e.error.code) {
        PurchasesErrorCode.NetworkError -> showOfflineFallback()
        else -> logError(e.error)
    }
}

Map codes to user facing strings

错误码转面向用户的提示字符串

Keep a single mapping function. Never pass
e.error.message
to the UI.
kotlin
fun userFacingMessage(error: PurchasesError): String = when (error.code) {
    PurchasesErrorCode.PurchaseCancelledError -> ""
    PurchasesErrorCode.NetworkError ->
        "Please check your internet connection and try again."
    PurchasesErrorCode.StoreProblemError ->
        "There was a problem with Google Play. Please try again."
    PurchasesErrorCode.ProductAlreadyPurchasedError ->
        "You already have this subscription."
    PurchasesErrorCode.PaymentPendingError ->
        "Your payment is being processed. We'll notify you when it completes."
    else -> "Something went wrong. Please try again."
}
保留一个统一的映射函数,切勿将
e.error.message
传递到UI中。
kotlin
fun userFacingMessage(error: PurchasesError): String = when (error.code) {
    PurchasesErrorCode.PurchaseCancelledError -> ""
    PurchasesErrorCode.NetworkError ->
        "请检查你的网络连接后重试。"
    PurchasesErrorCode.StoreProblemError ->
        "Google Play出现问题,请重试。"
    PurchasesErrorCode.ProductAlreadyPurchasedError ->
        "你已订阅该服务。"
    PurchasesErrorCode.PaymentPendingError ->
        "你的支付正在处理中,完成后我们会通知你。"
    else -> "出错了,请重试。"
}

Checklist

检查清单

  • You picked
    PurchasesTransactionException
    for
    awaitPurchase
    and
    PurchasesException
    elsewhere.
  • You checked
    userCancelled
    before any branching on
    error.code
    .
  • You handled
    PaymentPendingError
    ,
    ProductAlreadyPurchasedError
    , and
    NetworkError
    with their specific flows.
  • You logged
    error.message
    and showed a mapped string from
    userFacingMessage
    to the user.
  • You did not add retry loops around SDK calls. Retries are user initiated only.
  • 你为
    awaitPurchase
    选择了
    PurchasesTransactionException
    ,为其他调用选择了
    PurchasesException
  • 在根据
    error.code
    分支处理前,你先检查了
    userCancelled
  • 你针对
    PaymentPendingError
    ProductAlreadyPurchasedError
    NetworkError
    实现了特定处理流程。
  • 你记录了
    error.message
    ,并向用户展示了
    userFacingMessage
    映射后的提示字符串。
  • 你没有在SDK调用周围添加重试循环,仅允许用户主动触发重试。

References

参考资料