PAYATHON 2026

RevenueCat 升级订阅时报 DEVELOPER_ERROR

支付阿杰

结论

这里的 DEVELOPER_ERROR 通常与 RevenueCat SDK 版本或 Android 签名无关,而是 Google Play 拒绝了这次订阅替换请求。

“The subscription can’t have the first charge for free.” 的意思是:Google Play 根据目标订阅的优惠阶段、价格和升级时使用的 proration mode 计算后,判定新订阅的首次收费为 0,但当前升级方式不允许首次扣款为零。

订阅商品、基础方案和优惠资格都由 Google Play 服务端决定。因此,即使应用没有发布新版本,Play Console 中的配置变化也可能导致之前测试正常的旧版本突然失败。RevenueCat 3.1.1、4.1.0 和 4.2.0 都出现了相同结果,这更可能是商品配置或升级参数的问题,而不是某个 SDK 版本的回归。

优先检查 Google Play 订阅配置

先检查升级目标对应的 Google Play 订阅,不要只看 RevenueCat Offering。

需要确认:

  • 目标商品、base plan 和 offer 是否仍然有效。
  • 目标方案是否包含 free trial、首期免费或价格为 0 的优惠阶段。
  • 该优惠是否被错误地提供给已有订阅用户。
  • 各个国家或地区的价格是否完整,某个地区的首期价格是否异常。
  • 最近是否修改过价格、base plan、offer、兼容旧订阅的设置或订阅组。
  • RevenueCat 中的 Package 是否仍指向预期的 Google Play 商品和方案。
  • 测试账号是否因历史购买、取消、退款或反复测试,进入了特殊的优惠资格状态。

如果升级目标包含免费试用,可以先创建一个不含免费阶段、专供已有订阅用户升级的方案进行验证。新用户促销和已有用户升级最好不要共用同一个零价 offer。

检查 UpgradeInfo 中的旧商品 ID

oldSKU 必须是用户当前持有的 Google Play subscription product ID。它不是 RevenueCat entitlement ID、offering ID 或 package identifier,也不能直接填写准备购买的新商品 ID。

例如:

val upgradeInfo = UpgradeInfo(
    oldSKU = currentSubscriptionProductId
)

Purchases.sharedInstance.purchasePackageWith(
    requireActivity(),
    packageToBuy,
    upgradeInfo,
    onError = { error, userCancelled ->
        viewModel.onUpgradeError(error)
    },
    onSuccess = { _, purchaserInfo ->
        viewModel.onUpgradeSuccess(purchaserInfo)
    }
)

currentSubscriptionProductId 应根据用户当前有效的订阅信息确定,不要仅凭当前页面、entitlement 名称或本地缓存来猜测。

同时还要确认:

  • 当前订阅仍然有效。
  • oldSKU 与当前购买记录中的商品完全一致。
  • 目标 packageToBuy 与当前方案不同。
  • 没有把降级当成升级,并使用只适合价格上涨场景的模式。
  • 账号没有其他相关订阅处于 pending、paused、grace period 或 account hold 状态。

检查 proration mode

如果显式传入了 proration mode,需要确认它符合新旧方案的价格关系和预期扣费时间。

例如,立即按差价收费的模式通常只适用于新订阅按单位时间计算后价格更高的情况:

val upgradeInfo = UpgradeInfo(
    oldSKU = currentSubscriptionProductId,
    prorationMode = BillingFlowParams.ProrationMode
        .IMMEDIATE_AND_CHARGE_PRORATED_PRICE
)

如果目标方案更便宜,或者剩余订阅价值使本次应收金额变为零,Google Play 可能会拒绝这种模式。

可以先去掉显式指定的 proration mode,让当前 RevenueCat SDK 按默认方式处理:

val upgradeInfo = UpgradeInfo(
    oldSKU = currentSubscriptionProductId
)

也可以按照业务规则测试时间折算模式:

val upgradeInfo = UpgradeInfo(
    oldSKU = currentSubscriptionProductId,
    prorationMode = BillingFlowParams.ProrationMode
        .IMMEDIATE_WITH_TIME_PRORATION
)

可用的具体常量取决于项目所使用的 Google Play Billing Library 和 RevenueCat SDK 版本。不要为了消除错误随意更换模式,因为不同模式会影响生效时间、下次续费日期和实际扣款金额。

建议的排查顺序

  1. 在 Google Play Console 中确认目标订阅没有意外设置零价首期、free trial 或不适用于已有订阅者的 offer。
  2. 确认 RevenueCat Package 映射到了正确的 product、base plan 和 offer。
  3. 记录用户当前订阅的真实 product ID,确认它与 UpgradeInfo.oldSKU 完全一致。
  4. 暂时移除自定义 proration mode,再重新测试。
  5. 分别使用”无优惠目标方案”和”新测试账号”测试,以判断问题来自优惠配置还是账号资格。
  6. 检查失败是否只出现在特定升级路径中,例如从月订阅升级到年订阅,而其他组合均正常。
  7. 通过 RevenueCat 日志和 Google Play Console,核对最终提交的旧商品、目标商品及 offer。

注意事项

单纯升级或降级 RevenueCat SDK 通常不能解决这个错误,因为订阅替换请求最终由 Google Play Billing 决定是否接受。

如果移除目标方案的免费阶段,或者改用无优惠方案后能够成功购买,基本可以确认问题出在首个计费阶段或 offer eligibility。若所有配置均正确,但 Google Play 仍返回相同错误,需要整理当前 product ID、目标 product/base plan/offer、proration mode、测试账号状态和完整的 RevenueCat debug 日志,再提交给 RevenueCat 支持或 Google Play Billing 支持进一步核查。

备注:内容仅供参考。