PWA App 在 Google Play 中如何选择订阅方案
结论
sku 仍应填写订阅商品的 product ID,例如 my_subscription_id。basePlanId 不是 SKU,因此把 plan ID 传给 sku 会报错。
PWA 通过 PaymentRequest 和 https://play.google.com/billing 接入支付时,请求通常不能指定 basePlanId 或 offer。Google Play 会在该订阅商品下选择用户当前可以购买且符合条件的方案。如果商品下只有一个可购买的 base plan,用户实际购买的就是这个方案。
方案由购买流程确定,后端无法替用户选择或修改。后端需要校验购买结果,确认用户实际购买的 base plan,授予权益,并完成 acknowledge。
为什么只传 product ID
Google Play 的订阅配置分为以下层级:
Subscription product
└── Base plan
└── Offer(可选)
例如:
Product ID: premium_subscription
Base plan ID: monthly
Offer ID: trial-7-days
PaymentRequest 中的 sku 对应最上层的 product ID:
data: {
sku: 'premium_subscription',
}
原生 Google Play Billing Library 可以通过 offerToken 选择具体的 base plan 或 offer。PWA 使用的 Payment Request 支付处理程序与原生 Billing Library 是不同的接口,不能默认它支持 basePlanId、offerId 或 offerToken。只有浏览器和 Google Play 的相关文档明确提供这些字段时,才能在请求中使用。
所以下面的写法无效:
data: {
sku: 'monthly', // basePlanId,不是 product ID
}
前端应该怎么写
前端继续传订阅商品的 product ID。取得支付结果后,再把 purchaseToken 发送给可信后端:
const paymentMethodData = [
{
supportedMethods: 'https://play.google.com/billing',
data: {
sku: 'my_subscription_id',
},
},
];
const request = new PaymentRequest(paymentMethodData);
try {
const paymentResponse = await request.show();
const { purchaseToken } = paymentResponse.details;
const response = await fetch('/api/google-play/subscriptions/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ purchaseToken }),
});
if (!response.ok) {
throw new Error('Subscription verification failed');
}
await paymentResponse.complete('success');
} catch (error) {
console.error(error);
}
paymentResponse.complete('success') 只会结束浏览器中的支付流程,并不会向 Google Play acknowledge 订阅。购买确认仍需由后端调用 Google Play Developer API 完成。
后端需要做什么
后端收到 purchaseToken 后,按以下顺序处理:
- 使用
purchases.subscriptionsv2.get查询购买记录。 - 检查 package name、订阅状态、product ID 和到期时间。
- 从
lineItems[].offerDetails.basePlanId读取实际购买的 base plan。 - 如果业务限制了可购买的方案,校验
basePlanId和offerId。 - 确认购买有效后再授予订阅权益。
- 如果购买尚未确认,调用
purchases.subscriptions.acknowledge。 - 保存 purchase token、到期时间和购买状态,后续结合 Real-time Developer Notifications 更新权益。
后端伪代码可以简化为:
async function verifySubscription({
packageName,
expectedProductId,
purchaseToken,
}) {
const purchase = await getSubscriptionPurchaseV2({
packageName,
token: purchaseToken,
});
if (purchase.subscriptionState !== 'SUBSCRIPTION_STATE_ACTIVE') {
throw new Error('Subscription is not active');
}
const lineItem = purchase.lineItems?.find(
(item) => item.productId === expectedProductId,
);
if (!lineItem) {
throw new Error('Unexpected subscription product');
}
const basePlanId = lineItem.offerDetails?.basePlanId;
const offerId = lineItem.offerDetails?.offerId;
if (
purchase.acknowledgementState ===
'ACKNOWLEDGEMENT_STATE_PENDING'
) {
await acknowledgeSubscription({
packageName,
subscriptionId: expectedProductId,
token: purchaseToken,
});
}
return {
productId: lineItem.productId,
basePlanId,
offerId,
expiryTime: lineItem.expiryTime,
};
}
Google Play Developer API 的字段结构可能随版本调整,应以项目实际使用版本的响应为准。不要直接信任前端传来的 product ID、base plan ID 或支付状态。
Confirm Plan 不一定表示缺少 plan ID
Google Play 订阅管理页面显示 Confirm Plan,不能直接说明购买请求遗漏了 basePlanId。这个提示也可能由以下情况引起:
- 后端只更新了自己的订单记录,没有调用 Google Play 的 acknowledge 接口。
- 订阅价格发生变化,需要用户确认新的续订价格。
- 订阅处于 pending、paused、grace period 等特殊状态。
- Google Play 要求用户确认续订或付款设置。
可以先通过 purchases.subscriptionsv2.get 检查这些字段:
subscriptionState
acknowledgementState
lineItems[].productId
lineItems[].offerDetails.basePlanId
lineItems[].offerDetails.offerId
lineItems[].autoRenewingPlan.priceChangeDetails
如果购买记录中的 product ID 和 base plan 正确,订阅状态为 active,并且已经 acknowledged,那么 Confirm Plan 更可能是 Google Play 的账户或续订确认界面,无需再由 PWA 传递 plan ID。
如果 acknowledgementState 仍为 pending,应先修正后端的确认流程。没有及时 acknowledge 的购买可能会被 Google Play 自动撤销并退款。
需要让用户选择多个方案时
如果一个订阅商品下有多个 base plan,业务又要求用户明确选择月付、年付或特定优惠,而当前使用的 PWA Payment Request 接口没有提供选择 plan 或 offer 的参数,那么修改 sku 无法解决这个问题。
可以根据业务情况选择以下方式:
- 为每个可选方案创建独立的订阅 product ID,由前端选择对应的 SKU。
- 将商品配置简化为一个适用于 PWA 的 active base plan。
- 使用原生 Android Billing Library,通过
offerToken发起指定方案的购买。 - 如果以后使用的 Web Billing 接口明确增加了 plan 或 offer 参数,再按照该接口版本接入。
basePlanId 不能当作 product ID 使用,后端也不能在支付完成后把购买记录改成另一个 plan。订阅方案必须在 Google Play 创建购买交易时确定。
备注:内容仅供参考。
