PAYATHON 2026

Android Google Play Subscription 未自动续订

支付小周

结论

这次续订失败并不是 react-native-iap 引起的。订阅的自动续订由 Google Play Billing 管理,客户端库只负责发起购买并返回 purchaseToken。

从时间戳来看,这笔订阅的有效期约为 5 分钟:

startTimeMillis  = 1627031259058
expiryTimeMillis = 1627031557423
相差约 298 秒

这个时长符合 Google Play 测试订阅的加速周期,不是正常的生产订阅周期。测试订阅经过有限次数的加速续订后,Google Play 会自动将其取消。如果使用了模拟付款被拒的测试支付方式,续订也会因为扣款失败而终止。两种情况都可能返回:

{
  "autoRenewing": false,
  "cancelReason": 1
}

应先检查测试账号、测试支付方式,以及订阅是否已经走完 Google Play 规定的测试生命周期。

返回字段说明

响应中的几个字段含义如下:

  • acknowledgementState: 1:购买已经确认,不会因为没有调用 acknowledge 而被退款。
  • autoRenewing: false:Google Play 不会再为这笔订阅发起下一次自动扣款。
  • cancelReason: 1:订阅由系统取消。常见原因包括续订扣款失败,测试订阅被系统终止时也可能出现这个值。
  • expiryTimeMillis:当前已支付或已授予的订阅权益截止时间。
  • paymentState: null:查询时订阅已经过期或取消,该字段可能为空,不能仅凭它判断首次购买是否成功。
  • userCancellationTimeMillis: null:没有用户主动取消的时间记录,因此更可能是系统终止。

cancelReason: 1 只能说明订阅由系统取消,无法单独确定具体是哪一种付款问题。

优先检查测试环境

1. 确认是否使用了 License Tester

检查发起购买的 Google 账号是否已加入 Google Play Console 的 License Testing 列表。

测试订阅采用加速后的续订周期,而且不会无限续订。达到测试续订次数上限后,Google Play 会自动结束订阅。加速周期和续订次数可能随着时间或 Play Billing 测试机制的变化而调整,应以当前 Google Play 官方测试文档和控制台配置为准。

如果订阅经历了若干次短周期续订后才返回这个结果,通常只是测试生命周期正常结束。重新发起一笔测试购买即可继续验证流程。

2. 检查测试支付方式

购买时,注意 Google Play 支付页面中选择的测试支付方式。测试环境通常可以模拟以下结果:

  • 支付始终成功;
  • 支付始终被拒绝;
  • 延迟支付;
  • 续订扣款失败。

如果选择了会拒绝付款的测试方式,首次购买可能成功,但下一次续订会因扣款失败而被系统取消。

测试自动续订时,应选择能够持续成功扣款的测试支付方式,再重新购买订阅。

3. 检查订阅商品类型

确认 Google Play Console 中配置的是自动续订订阅,而不是预付费方案。预付费方案到期后不会自动续订,只能由用户延期或重新购买。

还需要检查:

  • 对应 base plan 是否处于有效状态;
  • base plan 是否启用了 auto-renewing;
  • 测试版本是否包含正确的商品配置;
  • 应用包名、subscriptionId 和购买账号是否一致。

如果生产环境也发生同样的问题

真实用户的订阅可能因以下原因被 Google Play 取消:

  • 信用卡过期;
  • 银行拒绝扣款;
  • 余额不足;
  • 支付资料异常;
  • Google Payments 风控;
  • 用户所在地区或付款方式不再可用;
  • 订阅进入宽限期和账号保留期后,扣款仍未完成。

应用无法强制 Google Play 再次扣款,也不应在客户端修改订阅状态。通常可以引导用户打开 Play 商店的订阅管理页面,更新付款方式或重新订阅:

import { Linking } from 'react-native';

const packageName = 'com.example.app';
const subscriptionId = 'premium_monthly';

const url =
  `https://play.google.com/store/account/subscriptions` +
  `?sku=${encodeURIComponent(subscriptionId)}` +
  `&package=${encodeURIComponent(packageName)}`;

await Linking.openURL(url);

如果 Google Play 已经彻底取消订阅,用户通常需要重新购买。应用重复查询并不能让原来的 purchaseToken 恢复续订。

服务端应如何验证

服务端不能只读取 autoRenewing,至少还要判断:

  • expiryTimeMillis 是否晚于当前时间;
  • 当前订阅状态;
  • 是否处于 grace period 或 account hold;
  • 是否已经取消;
  • 是否发生商品升级、降级或重新购买;
  • 是否出现新的或关联的 purchase token。

使用旧版 purchases.subscriptions.get 时,可以按下面的方式判断:

const response = await androidPublisher.purchases.subscriptions.get({
  packageName: 'com.example.app',
  subscriptionId: 'premium_monthly',
  token: purchaseToken,
});

const subscription = response.data;
const expiryTime = Number(subscription.expiryTimeMillis);
const isEntitled = expiryTime > Date.now();

const result = {
  isEntitled,
  autoRenewing: subscription.autoRenewing === true,
  cancelReason: subscription.cancelReason ?? null,
  acknowledgementState: subscription.acknowledgementState,
};

判断时要区分”当前是否拥有权益”和”到期后是否继续续订”。

expiryTimeMillis > 当前时间

表示用户当前仍可能拥有订阅权益,而:

autoRenewing === false

只表示订阅到期后不会继续自动扣款。用户主动取消订阅后,在已付款周期结束前,也可能处于”仍有权益但不再续订”的状态。

新实现建议使用 Google Play Developer API 提供的订阅查询接口,并按照订阅生命周期状态进行判断,不要只依赖旧接口中的 cancelReason。具体接口版本和状态枚举应以项目当前使用的 Google API 客户端及官方文档为准。

建议的完整排查流程

  1. 确认购买账号是否为 License Tester。
  2. 根据 startTimeMillis 和 expiryTimeMillis 判断是否为加速测试订阅。
  3. 检查购买时选择的测试支付方式是否会模拟扣款失败。
  4. 查看这笔测试订阅此前是否已经发生过多次短周期续订。
  5. 确认商品对应的是自动续订 base plan。
  6. 在 Google Play Console 中检查订单和订阅状态。
  7. 重新购买一笔测试订阅,并记录每次服务端查询的结果。
  8. 接入 Real-time Developer Notifications,在续订、取消、宽限期和账号保留状态变化时更新服务端数据。
  9. 对生产订单检查付款失败、宽限期和 account hold,不要把所有 cancelReason: 1 都归因于客户端错误。

这份响应中的 acknowledgementState 已经是 1,无需重点排查购买确认逻辑。更应该检查的是测试订阅生命周期和测试付款方式。如果订阅在经历若干个约 5 分钟的周期后被取消,通常属于 Google Play 测试环境的预期行为。如果第一次续订就失败,则应重点检查所选的测试支付方式和订阅 base plan 配置。

备注:内容仅供参考。