rc-plan-changes
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePlan Changes on Android with RevenueCat
在Android上使用RevenueCat进行套餐变更
You use this skill when a user already has an active Google Play subscription and you need to move them to a different SKU (upgrade, downgrade, cross-grade, or trial conversion). RevenueCat exposes Google's replacement modes through a single builder and resolves the chain server side so you do not write token chaining code.
PurchaseParamslinkedPurchaseToken当用户已有活跃的Google Play订阅,且你需要将其转移至不同SKU(升级、降级、跨级或试用转换)时,可使用此技能。RevenueCat通过单一的构建器暴露Google的替换模式,并在服务器端解析链,因此你无需编写令牌链相关代码。
PurchaseParamslinkedPurchaseTokenPhase 1: Preconditions
阶段1:前提条件
Confirm the following before invoking a plan change:
- The user has exactly one active Google Play subscription you intend to replace.
- You have a fresh from
CustomerInfoor a cached value from a recent listener callback.Purchases.sharedInstance.awaitCustomerInfo() - You have the target resolved from
Package(seeofferings.currentskill).fetch-offerings - Google Play Billing Library 7+ is on the classpath via the RevenueCat SDK.
Skip this skill if the user has no active subscription. For a fresh purchase, use instead.
make-purchase在发起套餐变更前,请确认以下事项:
- 用户恰好拥有一个你打算替换的活跃Google Play订阅。
- 你已从获取最新的
Purchases.sharedInstance.awaitCustomerInfo(),或从最近的监听器回调中获取缓存值。CustomerInfo - 你已从解析出目标
offerings.current(参考Package技能)。fetch-offerings - 项目类路径中已通过RevenueCat SDK引入Google Play Billing Library 7+版本。
若用户无活跃订阅,请跳过此技能。对于首次购买,请使用技能。
make-purchasePhase 2: Plan (pick a replacement mode)
阶段2:规划(选择替换模式)
GoogleReplacementMode| Scenario | Mode | Billing effect |
|---|---|---|
| Standard upgrade (monthly to annual) | | Immediate switch, remaining time credited |
| Upgrade, keep the existing billing date | | Immediate switch, prorated charge now |
| Switch to or from a prepaid plan | | Immediate switch, full charge now |
| Upgrade during an active free trial | | Immediate switch, prorated charge now |
| Downgrade (annual to monthly) | | Switch applies at next renewal |
Do not default to for trial upgrades. applies the new plan immediately but charges nothing until the next renewal, which gives the user free premium access they did not pay for. Use to charge the upgrade price on the spot.
WITHOUT_PRORATIONWITHOUT_PRORATIONCHARGE_PRORATED_PRICEIf you set no mode, defaults to . Set the mode explicitly every time.
PurchaseParamsWITHOUT_PRORATIONDEFERREDGoogleReplacementMode| 场景 | 模式 | 计费效果 |
|---|---|---|
| 标准升级(月度转年度) | | 立即切换,剩余时长按比例折算 |
| 升级,保留现有计费日期 | | 立即切换,按比例收取当前费用 |
| 切换至或切换出自预付费套餐 | | 立即切换,全额收取当前费用 |
| 免费试用期间升级 | | 立即切换,按比例收取当前费用 |
| 降级(年度转月度) | | 切换将在下一次续订时生效 |
请勿在试用升级时默认使用。会立即应用新套餐,但直到下一次续订才会收费,这会让用户免费获得未付费的高级权限。请使用当场收取升级费用。
WITHOUT_PRORATIONWITHOUT_PRORATIONCHARGE_PRORATED_PRICE若未设置模式,默认使用。请每次都显式设置模式。
PurchaseParamsWITHOUT_PRORATIONDEFERREDPhase 3: Execute
阶段3:执行
Derive from . Hardcoded SKUs break when a user has migrated between plans.
currentProductIdCustomerInfokotlin
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)
}Notes on :
oldProductId- Pass the subscription product ID only. If you pass , the SDK strips
"basic_monthly:monthly_plan"for you, but the intent is clearer when you slice it yourself.:monthly_plan - uses the
CustomerInfo.activeSubscriptionsshape.productId:basePlanIdgives you the correct value.substringBefore(":")
从中获取。硬编码SKU会在用户切换套餐后失效。
CustomerInfocurrentProductIdkotlin
val customerInfo = Purchases.sharedInstance.awaitCustomerInfo()
// activeSubscriptions条目格式为"productId:basePlanId",移除基础套餐后缀
val currentProductId = customerInfo.activeSubscriptions
.firstOrNull()
?.substringBefore(":")
?: return // 无活跃订阅,跳转至make-purchase流程
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会反映新的订阅信息
} catch (e: PurchasesTransactionException) {
if (!e.userCancelled) showError(e.error.message)
}关于的注意事项:
oldProductId- 仅传入订阅产品ID。若你传入,SDK会自动移除
"basic_monthly:monthly_plan",但自行截取会让意图更清晰。:monthly_plan - 使用
CustomerInfo.activeSubscriptions格式。productId:basePlanId可帮你获取正确的值。substringBefore(":")
Phase 4: Verify
阶段4:验证
After the suspending call returns, read the updated :
CustomerInfo- now contains the new
customerInfo.activeSubscriptions.productId:basePlanId - stays
customerInfo.entitlements["pro"]?.isActiveacross the switch; do not gate UI on the SKU string.true - For mode,
DEFERREDstill reports the old product until the next renewal. RevenueCat tracks the pending switch server side and flips the entitlement after Google sends the renewal RTDN.activeSubscriptions
You do not write traversal code. RevenueCat resolves the chain, marks the old token as replaced, and attributes both tokens to the same App User ID. Client code reads entitlements and trusts them.
linkedPurchaseToken挂起调用返回后,读取更新后的:
CustomerInfo- 现在包含新的
customerInfo.activeSubscriptions。productId:basePlanId - 在切换过程中始终为
customerInfo.entitlements["pro"]?.isActive;请勿根据SKU字符串控制UI权限。true - 对于模式,
DEFERRED在下一次续订前仍会显示旧产品。RevenueCat会在服务器端跟踪待处理的切换,并在Google发送续订RTDN后更新权益状态。activeSubscriptions
你无需编写遍历代码。RevenueCat会在服务器端处理链解析,标记旧令牌为已替换,并将两个令牌关联到同一个应用用户ID。客户端代码只需读取并信任权益信息即可。
linkedPurchaseTokenCommon mistakes
常见错误
| Mistake | Fix |
|---|---|
Hardcoding | Derive it from |
Passing | Slice off the base plan with |
Using | Use |
Using | |
Writing backend code to follow | RevenueCat does this server side, delete the code |
| Reading the SKU to decide UI state | Read |
| 错误 | 修复方案 |
|---|---|
将 | 从 |
将 | 使用 |
在试用升级时使用 | 使用 |
在升级时使用 | |
编写后端代码处理 | RevenueCat已在服务器端完成此操作,请删除相关代码 |
| 通过读取SKU来决定UI状态 | 改为读取 |