rc-one-time-products

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

One Time Products with RevenueCat

基于RevenueCat的Android一次性产品销售

Sell one time products on Android without writing BillingClient glue. The RevenueCat SDK fetches products, launches the purchase, verifies the receipt, and acknowledges or consumes the token for you.
无需编写BillingClient相关粘合代码,即可在Android平台销售一次性产品。RevenueCat SDK会为你完成产品获取、发起购买、验证收据以及确认或消耗订单令牌的操作。

Phase 1: Scope

阶段1:范围界定

Answer these before touching code.
QuestionDecision
Is the product consumable or non consumable?Mark consumables in the RevenueCat dashboard so the SDK calls
consumeAsync
for you.
Do you read access through entitlements or transactions?Prefer entitlements so access logic stays decoupled from product IDs.
Do you need server notification of the purchase?Use RevenueCat webhooks. The
INITIAL_PURCHASE
event fires for every new non subscription purchase.
Do not call
BillingClient.queryProductDetailsAsync
,
acknowledgePurchase
, or
consumeAsync
yourself once RevenueCat is integrated. The SDK owns the
BillingClient
instance.
在编写代码前先回答以下问题。
问题决策
产品是消耗型还是非消耗型?在RevenueCat控制台标记消耗型产品,SDK会自动为你调用
consumeAsync
你通过权益(entitlements)还是交易记录来判断用户权限?优先使用权益,这样权限逻辑与产品ID解耦。
是否需要服务器接收购买通知?使用RevenueCat webhooks。
INITIAL_PURCHASE
事件会在每笔新的非订阅购买时触发。
集成RevenueCat后,请勿自行调用
BillingClient.queryProductDetailsAsync
acknowledgePurchase
consumeAsync
。SDK会管理
BillingClient
实例。

Phase 2: Prepare

阶段2:准备工作

  • Configure the product in Google Play Console as a one time product.
  • In the RevenueCat dashboard, attach the product to an offering and package, and flag it as consumable if it should be consumed on purchase.
  • Confirm
    Purchases.configure
    runs once on app start with your Android API key and the current App User ID.
  • 在Google Play控制台中将产品配置为一次性产品。
  • 在RevenueCat控制台中,将产品关联到一个套餐组(offering)和套餐(package),如果产品需要在购买后被消耗,则标记为消耗型。
  • 确认
    Purchases.configure
    在应用启动时仅运行一次,传入你的Android API密钥和当前应用用户ID。

Phase 3: Execute

阶段3:执行流程

Fetch offerings, launch the purchase, then read the result off
CustomerInfo
.
kotlin
val offerings = Purchases.sharedInstance.awaitOfferings()
val pkg = offerings.current?.availablePackages?.first() ?: return
Launch the purchase with
awaitPurchase
. It suspends until the RevenueCat backend verifies the token.
kotlin
val result = Purchases.sharedInstance.awaitPurchase(
    PurchaseParams.Builder(activity, pkg).build()
)
val info = result.customerInfo
Read access through entitlements, or fall back to the non subscription transactions list.
kotlin
val hasAccess = info.entitlements["lifetime_access"]?.isActive == true
val owned = info.nonSubscriptionTransactions
    .any { it.productIdentifier == "lifetime_product_id" }
The SDK acknowledges non consumables and consumes consumables automatically once the backend verifies the purchase. You do not call
acknowledgePurchase
or
consumeAsync
.
获取产品套餐,发起购买,然后从
CustomerInfo
中读取结果。
kotlin
val offerings = Purchases.sharedInstance.awaitOfferings()
val pkg = offerings.current?.availablePackages?.first() ?: return
调用
awaitPurchase
发起购买,该方法会挂起直到RevenueCat后端验证令牌完成。
kotlin
val result = Purchases.sharedInstance.awaitPurchase(
    PurchaseParams.Builder(activity, pkg).build()
)
val info = result.customerInfo
通过权益判断用户权限,或者退而求其次查看非订阅交易列表。
kotlin
val hasAccess = info.entitlements["lifetime_access"]?.isActive == true
val owned = info.nonSubscriptionTransactions
    .any { it.productIdentifier == "lifetime_product_id" }
一旦后端验证完成购买,SDK会自动确认非消耗型产品并消耗消耗型产品。你无需调用
acknowledgePurchase
consumeAsync

Phase 4: Handle Edge Cases

阶段4:处理边缘情况

Wrap
awaitPurchase
to separate cancellation, pending payments, and real errors.
kotlin
try { /* awaitPurchase */ } catch (e: PurchasesTransactionException) {
    when {
        e.error.code == PurchasesErrorCode.PaymentPendingError -> showPending()
        e.userCancelled -> Unit
        else -> showError(e.error.message)
    }
}
OutcomeSDK signalAction
Success
awaitPurchase
returns with updated
CustomerInfo
Grant access from
entitlements
or
nonSubscriptionTransactions
.
User cancel
PurchasesTransactionException
,
userCancelled == true
Swallow silently.
Pending payment
PurchasesTransactionException
,
PaymentPendingError
Show a pending message. The SDK updates the entitlement later through
UpdatedCustomerInfoListener
.
Other error
PurchasesTransactionException
, other codes
Surface
e.error.message
to you.
awaitPurchase
进行包装,区分取消、待处理支付和真实错误。
kotlin
try { /* awaitPurchase */ } catch (e: PurchasesTransactionException) {
    when {
        e.error.code == PurchasesErrorCode.PaymentPendingError -> showPending()
        e.userCancelled -> Unit
        else -> showError(e.error.message)
    }
}
结果SDK信号操作
成功
awaitPurchase
返回更新后的
CustomerInfo
通过
entitlements
nonSubscriptionTransactions
授予权限。
用户取消
PurchasesTransactionException
userCancelled == true
静默处理。
待处理支付
PurchasesTransactionException
PaymentPendingError
显示待处理提示。后续SDK会通过
UpdatedCustomerInfoListener
更新权益。
其他错误
PurchasesTransactionException
,其他错误码
显示
e.error.message
内容。

Phase 5: Verify

阶段5:验证

  • Trust
    result.customerInfo
    from
    awaitPurchase
    . Do not grant access from optimistic local state.
  • Pending purchases do not grant entitlements. Register an
    UpdatedCustomerInfoListener
    so you react when the payment completes.
  • For server side provisioning, subscribe to the RevenueCat
    INITIAL_PURCHASE
    webhook. Do not reimplement Google Play Developer API verification.
  • 信任
    awaitPurchase
    返回的
    result.customerInfo
    。请勿基于本地乐观状态授予权限。
  • 待处理的购买不会授予权益。注册
    UpdatedCustomerInfoListener
    以便在支付完成时做出响应。
  • 如需服务器端配置,请订阅RevenueCat的
    INITIAL_PURCHASE
    webhook。请勿重新实现Google Play开发者API验证逻辑。

References

参考资料