rc-subscriptions
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSubscriptions on Android with RevenueCat
在Android上使用RevenueCat实现订阅功能
Google Play exposes subscriptions through a three tier hierarchy: Subscription, Base Plan, and Offer. RevenueCat wraps that hierarchy in a flatter model you configure from the dashboard: Offerings, Packages, and SubscriptionOptions. You fetch an Offering, pick a Package, and in most flows let the SDK choose the right SubscriptionOption for you.
Google Play 通过三层层级结构展示订阅:Subscription、Base Plan和Offer。RevenueCat 将该层级结构封装为一个更扁平化的模型,你可以在控制台中配置:Offerings、Packages和SubscriptionOptions。你只需获取一个Offering,选择一个Package,在大多数流程中让SDK为你选择合适的SubscriptionOption即可。
Phase 1: Understand
阶段1:理解概念
The mapping from Google Play to RevenueCat:
| Google Play | RevenueCat |
|---|---|
| Subscription (product ID) | |
| Base Plan | |
| Offer | |
| Group of base plans grouped in dashboard | |
Key types you will touch:
- : a dashboard configured group of
Offeringobjects.Packageis the one you show by default.offerings.current - : a purchasable slot (monthly, annual, weekly, custom). Exposes a
Package.product: StoreProduct - : the Google Play subscription product. Has
StoreProductand asubscriptionOptions: List<SubscriptionOption>?.defaultOption - : either a base plan or an offer. Has
SubscriptionOption,pricingPhases, and antags.id - : one billing segment (intro trial, intro price, or recurring). Has
PricingPhase,billingPeriod,price, andofferPaymentMode.recurrenceMode
See the Subscriptions chapter on revenuecat.com for the object model diagram showing the full Offerings hierarchy alongside the CustomerInfo hierarchy used for entitlement checks.
Google Play与RevenueCat的对象映射关系:
| Google Play | RevenueCat |
|---|---|
| Subscription(产品ID) | |
| Base Plan | |
| Offer | |
| 控制台中分组的基础方案集合 | |
你会接触到的核心类型:
- :在控制台配置的
Offering对象组。Package是默认展示的对象。offerings.current - :可购买的套餐(月度、年度、周度、自定义)。包含
Package属性。product: StoreProduct - :Google Play订阅产品。包含
StoreProduct和subscriptionOptions: List<SubscriptionOption>?属性。defaultOption - :既可以是基础方案,也可以是优惠活动。包含
SubscriptionOption、pricingPhases和tags属性。id - :单个计费阶段(试用期、入门价格或定期计费)。包含
PricingPhase、billingPeriod、price和offerPaymentMode属性。recurrenceMode
查看revenuecat.com上的订阅章节,其中的对象模型图展示了完整的Offerings层级结构,以及用于权限校验的CustomerInfo层级结构。
Phase 2: Plan
阶段2:规划实现
Before you write code, map your paywall to the object model. Answer these three questions.
在编写代码之前,将你的付费墙与对象模型进行映射。回答以下三个问题。
2.1 Which Offering drives the paywall?
2.1 哪个Offering驱动付费墙?
- Default paywall: use . This is the Offering marked current in the dashboard and is the standard choice.
offerings.current - Experiment or segment specific paywall: fetch . You keep the dashboard in charge of which products appear, so no app update ships when the catalog changes.
offerings.all["experiment-a"]
- 默认付费墙:使用。这是在控制台中标记为当前的Offering,是标准选择。
offerings.current - 实验或细分用户专属付费墙:获取。由控制台控制显示哪些产品,因此产品目录变更时无需发布应用更新。
offerings.all["experiment-a"]
2.2 Which Packages do you show?
2.2 你要展示哪些Packages?
Two access patterns, pick the one that matches your layout:
| Pattern | API | When to use |
|---|---|---|
| Named slots | | Fixed paywall with known durations |
| Iteration | | Dynamic layout, unknown durations, or custom package types |
Standard values: , , , , , , . Anything else is .
PackageTypeMONTHLYANNUALWEEKLYTWO_MONTHTHREE_MONTHSIX_MONTHLIFETIMEPackageType.CUSTOM有两种访问模式,选择与你的布局匹配的模式:
| 模式 | API | 使用场景 |
|---|---|---|
| 命名套餐 | | 固定付费墙,时长已知 |
| 迭代遍历 | | 动态布局、时长未知或自定义套餐类型 |
标准值包括:、、、、、、。其他类型均为。
PackageTypeMONTHLYANNUALWEEKLYTWO_MONTHTHREE_MONTHSIX_MONTHLIFETIMEPackageType.CUSTOM2.3 Does the paywall need a specific offer, or is the default fine?
2.3 付费墙是否需要特定优惠活动,还是默认即可?
| Situation | What to pass to |
|---|---|
| Standard paywall, user gets best eligible offer automatically | |
| You need a specific offer (win back, promo, tag selected) | |
The SDK's logic:
defaultOption- Filters out options tagged or
"rc-ignore-offer"."rc-customer-center" - Picks the option with the longest free trial or the cheapest first phase.
- Falls back to the base plan if no offer qualifies.
Trial eligibility is not filtered by the SDK. Google Play only returns offers the user is eligible for, so if a user already consumed a free trial, that option simply will not appear in and the base plan becomes the default.
subscriptionOptions| 场景 | 传递给 |
|---|---|
| 标准付费墙,自动为用户选择最佳符合条件的优惠 | |
| 需要特定优惠(赢回用户、促销、标签选中的优惠) | |
SDK的逻辑:
defaultOption- 过滤掉标记为或
"rc-ignore-offer"的选项。"rc-customer-center" - 选择免费试用时长最长或第一阶段价格最低的选项。
- 如果没有符合条件的优惠,则回退到基础方案。
SDK不会过滤试用资格。Google Play仅返回用户符合条件的优惠,因此如果用户已经使用过免费试用,该选项将不会出现在中,基础方案将成为默认选项。
subscriptionOptionsPhase 3: Execute
阶段3:代码实现
3.1 Pull Offerings and pick a Package
3.1 获取Offerings并选择Package
kotlin
val offerings = Purchases.sharedInstance.awaitOfferings()
val offering = offerings.current ?: return
val monthly = offering.monthly ?: return
val product = monthly.product
val price = product.price.formatted
val period = product.period?.iso8601 // "P1M", "P1Y", null for one timeFor a dynamic list:
kotlin
for (pkg in offering.availablePackages) {
render(pkg.product.title, pkg.product.price.formatted, pkg.packageType)
}kotlin
val offerings = Purchases.sharedInstance.awaitOfferings()
val offering = offerings.current ?: return
val monthly = offering.monthly ?: return
val product = monthly.product
val price = product.price.formatted
val period = product.period?.iso8601 // "P1M", "P1Y", null for one time对于动态列表:
kotlin
for (pkg in offering.availablePackages) {
render(pkg.product.title, pkg.product.price.formatted, pkg.packageType)
}3.2 Purchase with the default option
3.2 使用默认选项进行购买
When the paywall shows a Package and you want the SDK to pick the best offer, pass the Package directly.
kotlin
val params = PurchaseParams.Builder(activity, monthly).build()
val result = Purchases.sharedInstance.awaitPurchase(params)当付费墙展示Package,且你希望SDK选择最佳优惠时,直接传递Package即可。
kotlin
val params = PurchaseParams.Builder(activity, monthly).build()
val result = Purchases.sharedInstance.awaitPurchase(params)3.3 Drill into subscriptionOptions
for a specific offer
subscriptionOptions3.3 深入subscriptionOptions
获取特定优惠
subscriptionOptionsUse this when the paywall targets an offer by tag or offer ID, for example a win back offer.
kotlin
val product = offering.monthly?.product ?: return
val winBack = product.subscriptionOptions
?.firstOrNull { it.tags.contains("win-back") }
val option = winBack ?: product.defaultOption ?: return
val params = PurchaseParams.Builder(activity, option).build()Always fall back to so the paywall still works when the targeted offer is absent (for example, the user is not eligible).
defaultOption当付费墙通过标签或优惠ID定位特定优惠时(例如赢回用户的优惠),使用此方法。
kotlin
val product = offering.monthly?.product ?: return
val winBack = product.subscriptionOptions
?.firstOrNull { it.tags.contains("win-back") }
val option = winBack ?: product.defaultOption ?: return
val params = PurchaseParams.Builder(activity, option).build()始终回退到,以便当目标优惠不存在时(例如用户不符合资格),付费墙仍能正常工作。
defaultOption3.4 Render trial and intro pricing from pricingPhases
pricingPhases3.4 从pricingPhases
渲染试用和入门价格
pricingPhasesThe first is the trial or intro price when present. Use for trial detection.
PricingPhaseofferPaymentModekotlin
val option = pkg.product.defaultOption ?: return
val first = option.pricingPhases.first()
val isTrial = first.offerPaymentMode == OfferPaymentMode.FREE_TRIALbillingPeriod.valueP1Wvalue = 1unit = WEEKkotlin
val p = first.billingPeriod
val label = when (p.unit) {
Period.Unit.DAY -> "${p.value} day"
Period.Unit.WEEK -> "${p.value} week"
Period.Unit.MONTH -> "${p.value} month"
Period.Unit.YEAR -> "${p.value} year"
else -> p.iso8601
}第一个即为试用或入门价格(如果存在)。使用检测是否为试用。
PricingPhaseofferPaymentModekotlin
val option = pkg.product.defaultOption ?: return
val first = option.pricingPhases.first()
val isTrial = first.offerPaymentMode == OfferPaymentMode.FREE_TRIALbillingPeriod.valueP1Wvalue = 1unit = WEEKkotlin
val p = first.billingPeriod
val label = when (p.unit) {
Period.Unit.DAY -> "${p.value} day"
Period.Unit.WEEK -> "${p.value} week"
Period.Unit.MONTH -> "${p.value} month"
Period.Unit.YEAR -> "${p.value} year"
else -> p.iso8601
}3.5 Prepaid plans
3.5 预付费方案
Prepaid base plans use the same API. Their report . To accept pending purchases for prepaid plans, enable the flag at configuration time.
SubscriptionOptionpricingPhasesRecurrenceMode.NON_RECURRINGkotlin
PurchasesConfiguration.Builder(context, apiKey)
.pendingTransactionsForPrepaidPlansEnabled(true)
.build()预付费基础方案使用相同的 API。它们的会返回。要接受预付费方案的待处理购买,请在配置时启用该标志。
SubscriptionOptionpricingPhasesRecurrenceMode.NON_RECURRINGkotlin
PurchasesConfiguration.Builder(context, apiKey)
.pendingTransactionsForPrepaidPlansEnabled(true)
.build()3.6 Check access after purchase
3.6 购买后检查权限
Prefer entitlements. They reflect server computed access state including grace period, account hold, and cancellation with remaining time.
kotlin
val info = result.customerInfo
val isPro = info.entitlements["pro"]?.isActive == trueIf you need the raw product ID, use . It returns a of entries.
customerInfo.activeSubscriptionsSet<String>"subscriptionId:basePlanId"优先使用权益(entitlements)。它们反映了服务器计算的访问状态,包括宽限期、账户暂停和剩余时间内的取消状态。
kotlin
val info = result.customerInfo
val isPro = info.entitlements["pro"]?.isActive == true如果你需要原始产品ID,请使用。它返回一个,包含格式的条目。
customerInfo.activeSubscriptionsSet<String>"subscriptionId:basePlanId"Decision summary
决策总结
| Question | Answer |
|---|---|
| How do I fetch products? | |
| How do I present durations? | |
| How do I purchase? | Pass the |
| How do I target a specific offer? | Filter |
| How do I detect a free trial? | |
| How do I check access? | |
| 问题 | 答案 |
|---|---|
| 如何获取产品? | |
| 如何展示时长? | 使用 |
| 如何进行购买? | 将 |
| 如何定位特定优惠? | 通过标签或ID过滤 |
| 如何检测免费试用? | |
| 如何检查访问权限? | |