用途
该技能面向在 Android 应用中使用 RevenueCat 对象模型实现订阅功能的场景。Google Play 以 Subscription、Base Plan、Offer 三层结构暴露订阅,RevenueCat 则用更扁平、可在控制台配置的模型包装它:Offerings、Packages 和 SubscriptionOptions。典型流程是拉取一个 Offering、选择一个 Package,并在多数流程中由 SDK 自动选择合适的 SubscriptionOption。
主要能力
Google Play 与 RevenueCat 的对象映射
| Google Play | RevenueCat |
|---|---|
| Subscription(产品 ID) | StoreProduct |
| Base Plan | SubscriptionOption(基础方案) |
| Offer | SubscriptionOption(优惠) |
| 控制台分组的一组基础方案 | Offering 内的 Package |
会接触到的关键类型:
- Offering:控制台配置的一组 Package 对象,offerings.current 是默认展示的那个。
- Package:一个可购买槽位(月付、年付、周付、自定义),通过 product 暴露 StoreProduct。
- StoreProduct:Google Play 订阅产品,包含 subscriptionOptions: List<SubscriptionOption>? 与 defaultOption。
- SubscriptionOption:基础方案或优惠,包含 pricingPhases、tags 与 id。
- PricingPhase:一个计费时段(引导试用、引导价格或周期续费),包含 billingPeriod、price、offerPaymentMode 与 recurrenceMode。
规划阶段
在编写代码前,先把付费墙映射到对象模型,回答三个问题。
- 哪个 Offering 驱动付费墙:默认付费墙使用 offerings.current(控制台中标记为 current 的 Offering,是标准选择);实验或分群付费墙使用 offerings.all["experiment-a"],由控制台决定展示哪些产品,目录变化无需发布应用更新。
- 展示哪些 Package:有两种访问模式。命名槽位用 offering.monthly、offering.annual、offering.weekly,适合时长固定的付费墙;迭代用 offering.availablePackages,适合动态布局、时长未知或自定义包类型。标准 PackageType 值为 MONTHLY、ANNUAL、WEEKLY、TWO_MONTH、THREE_MONTH、SIX_MONTH、LIFETIME,其余为 PackageType.CUSTOM。
- 付费墙是否需要特定优惠:标准付费墙、用户自动获得最优可用优惠时,向 PurchaseParams 传 Package;需要特定优惠(挽回、促销、按标签选择)时传 SubscriptionOption。
SDK 的 defaultOption 逻辑:过滤掉带 "rc-ignore-offer" 或 "rc-customer-center" 标签的选项;选择免费试用最长或首个价格阶段最便宜的选项;若无符合条件的优惠则回退到基础方案。试用资格不由 SDK 过滤,Google Play 只返回用户有资格的优惠,因此如果用户已用掉免费试用,该选项不会出现在 subscriptionOptions 中,基础方案会成为默认。
执行阶段
拉取 Offerings 并选择 Package:
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
动态列表:
for (pkg in offering.availablePackages) {
render(pkg.product.title, pkg.product.price.formatted, pkg.packageType)
}
使用默认选项购买(付费墙展示 Package 并希望 SDK 挑选最优优惠时,直接传 Package):
val params = PurchaseParams.Builder(activity, monthly).build()
val result = Purchases.sharedInstance.awaitPurchase(params)
下钻 subscriptionOptions 以定位特定优惠(例如按标签或优惠 ID 定位挽回优惠):
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()
始终回退到 defaultOption,以便目标优惠不存在(例如用户无资格)时付费墙仍可用。
从 pricingPhases 渲染试用与引导定价,第一个 PricingPhase 即试用或引导价格(存在时),用 offerPaymentMode 判断试用:
val option = pkg.product.defaultOption ?: return
val first = option.pricingPhases.first()
val isTrial = first.offerPaymentMode == OfferPaymentMode.FREE_TRIAL
billingPeriod.value 是该周期单位的数量而非天数,P1W 周期给出 value = 1、unit = WEEK,应基于两个字段构建标签:
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
}
预付费方案使用相同的 SubscriptionOption API,其 pricingPhases 报告 RecurrenceMode.NON_RECURRING。要接受预付费方案的待处理购买,需在配置时启用标志:
PurchasesConfiguration.Builder(context, apiKey)
.pendingTransactionsForPrepaidPlansEnabled(true)
.build()
购买后检查访问权限,优先使用权益,它反映服务器计算出的访问状态,包括宽限期、账户保留和取消后剩余时间:
val info = result.customerInfo
val isPro = info.entitlements["pro"]?.isActive == true
若需要原始产品 ID,使用 customerInfo.activeSubscriptions,它返回 "subscriptionId:basePlanId" 形式的 Set<String>。
使用方式速查
| 问题 | 答案 |
|---|---|
| 如何拉取产品 | Purchases.sharedInstance.awaitOfferings() 然后 offerings.current |
| 如何展示时长 | offering.monthly/annual/weekly 或迭代 availablePackages |
| 如何购买 | 把 Package 传给 PurchaseParams,让 defaultOption 生效 |
| 如何定位特定优惠 | 按标签或 ID 过滤 product.subscriptionOptions,传入 SubscriptionOption |
| 如何检测免费试用 | pricingPhases.first().offerPaymentMode == OfferPaymentMode.FREE_TRIAL |
| 如何检查访问 | customerInfo.entitlements["<id>"]?.isActive |
适用场景
在 Android 应用中基于 RevenueCat 实现订阅付费墙、展示套餐与价格、处理试用与引导价、定位特定优惠以及校验订阅权益时使用。
限制
资料未提供该技能的收费、价格、平台限制、性能效果或完整参考资料内容。