PAYATHON 2026

Android TWA 中 DigitalGoods API 订阅升级卡住

支付阿杰

明确结论

订阅升级并非仅支持 ChromeOS。Android TWA 同样可以通过 Digital Goods API 发起订阅替换。相关文档放在 chromeos.dev,并不意味着这项能力只适用于 ChromeOS。

普通购买可以打开,但加入 oldSku、purchaseToken 和 prorationMode 后一直停在 loading,往往是因为 Google Play 无法根据这些参数建立有效的订阅替换流程。常见原因包括:

  • purchaseToken 不是当前有效订阅的 Play purchase token。
  • oldSku 与该 token 对应的商品不一致。
  • 当前用户、Play 账号或应用包名与旧订阅不匹配。
  • prorationMode 不适用于这次价格或周期变更。
  • TWA 使用的 Chrome、WebAPK 或 Android Browser Helper 计费集成未正确支持订阅替换参数。
  • Play Console 中的新旧订阅、基础方案或优惠配置不允许当前迁移路径。

正确获取旧订阅信息

purchaseToken 不能是订单号、SKU、服务端生成的 ID,也不能是上一次支付请求中的其他标识。它必须是 Google Play 为当前有效订阅签发的 purchase token。

建议先通过 Digital Goods API 查询当前购买记录,再从同一条记录中取得 itemId 和 purchaseToken,用于订阅升级:

const service = await window.getDigitalGoodsService(
  "https://play.google.com/billing"
);

const purchases = await service.listPurchases();

const currentPurchase = purchases.find(
  purchase => purchase.itemId === "standard_subscription_id"
);

if (!currentPurchase) {
  throw new Error("未找到 standard_subscription_id 的有效购买记录");
}

const oldSku = currentPurchase.itemId;
const purchaseToken = currentPurchase.purchaseToken;

不同 Chrome 或 Digital Goods API 实现返回的字段可能略有不同,请以实际运行环境和相应接口文档为准。最重要的是,SKU 和 token 必须来自同一笔仍然有效的订阅。

发起订阅升级

确认旧订阅后,再构造支付请求:

const paymentMethods = [
  {
    supportedMethods: "https://play.google.com/billing",
    data: {
      sku: "premium_subscription_id",
      oldSku: currentPurchase.itemId,
      purchaseToken: currentPurchase.purchaseToken,
      prorationMode: "immediateAndChargeProratedPrice",
    },
  },
];

const paymentDetails = {
  total: {
    label: "Premium subscription",
    amount: {
      currency: "USD",
      value: "0.00",
    },
  },
};

const request = new PaymentRequest(paymentMethods, paymentDetails);

try {
  const response = await request.show();

  // 将 response.details 中的购买信息交给服务端验证。
  await verifyPurchaseOnServer(response.details);

  await response.complete("success");
} catch (error) {
  console.error("Subscription upgrade failed:", error);
}

paymentDetails 中填写的金额不是 Google Play 最终扣款的依据。实际价格、税费和按比例计费由 Play Console 配置及 Google Play 结算页决定,但这里仍需提供符合 Payment Request API 要求的合法结构。

检查 prorationMode

immediateAndChargeProratedPrice 并不适用于所有订阅替换场景。它通常用于立即升级并收取按比例计算的差价,而且新方案按单位时间计算的价格应高于旧方案。

如果本次操作是降级、同价切换或周期变更,应按照业务规则选择其他受支持的模式。例如:

prorationMode: "immediateWithTimeProration"

如果需要等到下一个续费周期再生效,则应使用延迟替换模式。具体可用值及限制取决于当前 Digital Goods API 和 Google Play Billing 集成。不要将 Android 原生 Billing Library 的数字常量直接传给 JavaScript API。

排查时可以先改用 immediateWithTimeProration。如果结算页能够正常打开,而使用 immediateAndChargeProratedPrice 时无法打开,通常可以判断为计费模式与本次迁移条件不匹配。

Play Console 中需要确认的配置

请逐项确认:

  1. standard_subscription_id 和 premium_subscription_id 都属于当前 Android 应用。
  2. 新旧订阅及其基础方案均已启用,并且当前测试账号可以购买。
  3. 当前 Play 账号确实持有 standard_subscription_id 的有效订阅。
  4. 旧订阅没有过期、撤销或退款,也未处于不允许替换的异常状态。
  5. 测试账号所在国家或地区可以购买目标方案。
  6. 新旧方案的价格、结算周期和优惠条件允许使用所选的 prorationMode。
  7. 应用包名、签名与 Play Console 中发布或测试的应用一致。
  8. 测试安装来自 Google Play 测试轨道,而非身份与 Play 版本不一致的本地安装包。

如果使用新的订阅基础方案和 offer 模型,还要确认当前 Digital Goods API 实现会如何选择相应的 base plan 或 offer。运行环境支持的接口版本不同,仅传入产品 ID 是否足够也会有所不同。不能假定旧版 sku 参数能够自动解析所有原生 Play Billing 配置。

检查 TWA 计费集成

普通购买成功,只能说明基础的 Payment Request 和 Play Billing 通路可用,无法证明当前集成已经正确处理订阅替换字段。

需要确认以下事项:

  • Android 应用使用的 Trusted Web Activity 或 Android Browser Helper 计费委托组件支持订阅升级参数。
  • 设备上的 Chrome 和生成的 WebAPK 已更新。
  • 更新 TWA 宿主应用后,重新从 Play 测试轨道安装,避免继续使用旧 WebAPK 或旧计费服务。
  • Manifest、Digital Asset Links、包名和 Billing 权限仍与当前发布版本一致。

不要凭空指定最低版本号。应根据项目实际使用的 Android Browser Helper、Chrome 和 Play Billing 集成文档,核对其对订阅替换的支持情况。

建议的定位顺序

先使用 listPurchases() 打印排查所需的字段,但不要将完整的 purchase token 写入公开日志:

console.table(
  purchases.map(purchase => ({
    itemId: purchase.itemId,
    hasPurchaseToken: Boolean(purchase.purchaseToken),
    purchaseState: purchase.purchaseState,
    willAutoRenew: purchase.willAutoRenew,
  }))
);

然后按以下顺序检查:

  1. 是否能够找到旧 SKU。
  2. token 是否存在,且与旧 SKU 来自同一条购买记录。
  3. 服务端通过 Google Play Developer API 查询时,该 token 是否仍然有效。
  4. 改用适合本次迁移的 prorationMode 后,页面是否仍然卡住。
  5. 使用 Play Console 的许可测试账号,并从测试轨道重新安装后,问题是否仍能复现。
  6. 更新 Chrome、TWA 宿主和计费委托依赖后,问题是否仍能复现。

如果参数和 Play Console 配置都已确认无误,但窗口仍然无限 loading,应按运行时计费集成问题处理。记录 Chrome 版本、Android 版本、TWA 宿主版本、计费委托依赖版本、新旧商品配置和完整复现步骤,再向 Chromium 或相关 TWA 计费组件提交最小复现。

支付窗口处于 loading 状态时,不要反复创建新的 PaymentRequest,否则可能产生并发支付请求,也会让状态更难判断。

备注:内容仅供参考。