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 中需要确认的配置
请逐项确认:
standard_subscription_id和premium_subscription_id都属于当前 Android 应用。- 新旧订阅及其基础方案均已启用,并且当前测试账号可以购买。
- 当前 Play 账号确实持有
standard_subscription_id的有效订阅。 - 旧订阅没有过期、撤销或退款,也未处于不允许替换的异常状态。
- 测试账号所在国家或地区可以购买目标方案。
- 新旧方案的价格、结算周期和优惠条件允许使用所选的
prorationMode。 - 应用包名、签名与 Play Console 中发布或测试的应用一致。
- 测试安装来自 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,
}))
);
然后按以下顺序检查:
- 是否能够找到旧 SKU。
- token 是否存在,且与旧 SKU 来自同一条购买记录。
- 服务端通过 Google Play Developer API 查询时,该 token 是否仍然有效。
- 改用适合本次迁移的
prorationMode后,页面是否仍然卡住。 - 使用 Play Console 的许可测试账号,并从测试轨道重新安装后,问题是否仍能复现。
- 更新 Chrome、TWA 宿主和计费委托依赖后,问题是否仍能复现。
如果参数和 Play Console 配置都已确认无误,但窗口仍然无限 loading,应按运行时计费集成问题处理。记录 Chrome 版本、Android 版本、TWA 宿主版本、计费委托依赖版本、新旧商品配置和完整复现步骤,再向 Chromium 或相关 TWA 计费组件提交最小复现。
支付窗口处于 loading 状态时,不要反复创建新的 PaymentRequest,否则可能产生并发支付请求,也会让状态更难判断。
备注:内容仅供参考。
