关于 rc-plan-changes
面向 Android 开发者的 RevenueCat Google Play 活跃订阅变更指南,介绍替换模式、从 CustomerInfo 获取当前产品,以及变更后的状态验证。
主要功能
- 01选择 Google 替换模式
- 02从 CustomerInfo 获取产品 ID
- 03说明升级与降级的生效时机
- 04使用 RevenueCat 处理令牌链
- 05验证变更后的权益状态
适用场景
- 01将月度计划升级为年度计划
- 02安排在续费时降级
- 03将免费试用转换为付费计划
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ revenuecat/ ai-toolkit --skill rc-plan-changes安装后在 Claude Code 中输入/rc-plan-changes确认已加载。
详细介绍
在 Android 上使用 RevenueCat 更改套餐
当用户已有有效的 Google Play 订阅,而你需要将其切换到其他 SKU(升级、降级、跨级切换或试用转化)时,请使用此技能。RevenueCat 通过单个 PurchaseParams 构建器提供 Google 的替换模式,并在服务器端解析 linkedPurchaseToken 链,因此你无需编写令牌链代码。
阶段 1:前提条件
调用套餐更改前,请确认以下事项:
- 用户恰好有一个你打算替换的有效 Google Play 订阅。
- 你有从
Purchases.sharedInstance.awaitCustomerInfo()获取的最新CustomerInfo,或来自近期监听器回调的缓存值。 - 你已从
offerings.current解析出目标Package(参见fetch-offerings技能)。 - RevenueCat SDK 的 classpath 中包含 Google Play Billing Library 7+。
如果用户没有有效订阅,请跳过此技能。对于新购买,请改用 make-purchase。
阶段 2:规划(选择替换模式)
GoogleReplacementMode 与 Google 的结算模式一一对应。根据用户意图选择:
| 场景 | 模式 | 结算效果 |
|---|---|---|
| 常规升级(月付转年付) | WITH_TIME_PRORATION | 立即切换,剩余时间折抵 |
| 升级并保留现有结算日期 | CHARGE_PRORATED_PRICE | 立即切换,立即收取按比例计算的费用 |
| 切换至或切换自预付套餐 | CHARGE_FULL_PRICE | 立即切换,立即收取全额费用 |
| 有效免费试用期间升级 | CHARGE_PRORATED_PRICE | 立即切换,立即收取按比例计算的费用 |
| 降级(年付转月付) | DEFERRED | 在下次续订时切换 |
试用期间升级时,不要默认使用 WITHOUT_PRORATION。WITHOUT_PRORATION 会立即应用新套餐,但直到下次续订才收费,这会让用户免费获得未付费的高级权限。请使用 CHARGE_PRORATED_PRICE,当场收取升级费用。
如果不设置模式,PurchaseParams 默认使用 WITHOUT_PRORATION。每次都要明确设置模式。
DEFERRED 仅适用于降级。Google 会拒绝延迟升级。
阶段 3:执行
从 CustomerInfo 推导 currentProductId。用户切换套餐后,硬编码的 SKU 会出错。
val customerInfo = Purchases.sharedInstance.awaitCustomerInfo()
// activeSubscriptions entries are "productId:basePlanId", strip the base plan suffix
val currentProductId = customerInfo.activeSubscriptions
.firstOrNull()
?.substringBefore(":")
?: return // nothing active, route to make-purchase instead
val newPackage = offerings.current
?.availablePackages
?.firstOrNull { it.identifier == "premium_annual_package" }
?: return
val params = PurchaseParams.Builder(activity, newPackage)
.googleProductChangeInfo(
GoogleProductChangeInfo(
oldProductId = currentProductId,
replacementMode = GoogleReplacementMode.WITH_TIME_PRORATION,
)
)
.build()
try {
val result = Purchases.sharedInstance.awaitPurchase(params)
// result.customerInfo reflects the new subscription
} catch (e: PurchasesTransactionException) {
if (!e.userCancelled) showError(e.error.message)
}
关于 oldProductId 的说明:
- 只传入订阅产品 ID。如果传入
"basic_monthly:monthly_plan",SDK 会为你去掉:monthly_plan,但自行截取会让意图更清晰。 CustomerInfo.activeSubscriptions使用productId:basePlanId的格式。substringBefore(":")可获得正确的值。
阶段 4:验证
挂起调用返回后,读取更新后的 CustomerInfo:
customerInfo.activeSubscriptions现在包含新的productId:basePlanId。- 切换期间,
customerInfo.entitlements["pro"]?.isActive仍为true;不要根据 SKU 字符串控制 UI。 - 对于
DEFERRED模式,activeSubscriptions会继续报告旧产品,直到下次续订。RevenueCat 会在服务器端跟踪待处理的切换,并在 Google 发送续订 RTDN 后切换权益。
你无需编写遍历 linkedPurchaseToken 的代码。RevenueCat 会解析令牌链,将旧令牌标记为已替换,并将两个令牌都关联到同一个 App User ID。客户端代码读取权益并以其为准。
常见错误
| 错误 | 修正方法 |
|---|---|
将 oldProductId 硬编码为常量 | 从 customerInfo.activeSubscriptions.firstOrNull()?.substringBefore(":") 推导 |
将 "productId:basePlanId" 作为 oldProductId 传入 | 使用 substringBefore(":") 去掉基础方案 |
为试用期升级使用 WITHOUT_PRORATION | 使用 CHARGE_PRORATED_PRICE,让用户立即付费 |
为升级使用 DEFERRED | DEFERRED 仅用于降级;Google 会拒绝延期升级 |
编写后端代码来跟踪 linkedPurchaseToken | RevenueCat 会在服务端处理,删除这段代码 |
| 读取 SKU 来判断 UI 状态 | 改为读取 entitlements[...]?.isActive |