PWA App 如何在 Google Play 中选择订阅计划
结论
sku 应填写订阅商品的 product ID,不能填写 base plan ID。PWA 通过 https://play.google.com/billing 发起订阅购买时,前端接口通常没有单独传入 base plan 或 offer 的参数。Google Play 会选择该订阅商品中配置为兼容旧版结算流程的 base plan。
各部分的职责如下:
- 前端传递订阅商品的 product ID。
- 在 Play Console 中配置并激活 base plan,并确认它兼容旧版结算流程。
- 后端验证 purchase token,并显式 acknowledge 这笔购买。
- 如果需要让用户选择多个计划,而当前 PWA Billing 接口又不能传入 base plan,可以将不同计划拆成不同的订阅商品,分别使用不同的 product ID。
为什么不能把 plan ID 放进 sku
Google Play 的新订阅模型大致分为三层:
Subscription product
└── Base plan
└── Offer
各层含义如下:
- product ID 标识订阅商品,例如
my_subscription_id。 - base plan ID 标识该商品下的计费方案,例如月付、年付或预付费方案。
- offer 标识试用期、首期优惠等促销条件。
当前代码中的 sku 对应最外层的 product ID,不是 base plan ID:
data: {
sku: 'my_subscription_id',
}
base plan ID 并不是独立的商品 SKU。把它填入 sku 后,接口返回“商品不存在”或类似错误,属于预期结果。
在 Play Console 中配置计划
进入对应应用的订阅管理页面,依次检查:
- 创建或打开订阅商品,例如
my_subscription_id。 - 在该订阅商品下创建 base plan。
- 配置计费周期、价格、续订方式和销售地区。
- 激活该 base plan。
- 确认该方案已设为兼容旧版结算流程的 base plan。
即使订阅商品下只有一个 active base plan,也要确认当前 PWA 使用的结算接口能够识别它,而不只是确认它已经激活。部分版本的 Play Console 会把这项设置称为“向后兼容”或类似名称,具体文字可能随控制台版本变化。
前端仍然只传 product ID:
const paymentMethodData = [
{
supportedMethods: 'https://play.google.com/billing',
data: {
sku: 'my_subscription_id',
},
},
];
const request = new PaymentRequest(paymentMethodData);
const paymentResponse = await request.show();
const purchaseToken = paymentResponse.details.purchaseToken;
如果只有一个符合条件的 base plan,Google Play 会自动选择它,用户和后端都不需要再次选择。
后端需要完成的处理
后端不负责选择 base plan,主要处理以下工作:
- 接收 purchase token。
- 通过 Google Play Developer API 查询购买状态。
- 核对应用包名、订阅商品、付款状态和到期时间。
- 向用户发放订阅权益。
- acknowledge 购买。
- 保存购买记录,并处理续订、取消、退款和撤销。
“验证购买”和“acknowledge 购买”是两个不同的操作。查询 token 并确认付款有效,不一定代表购买已经完成 acknowledge。
以下是示意代码,具体方法名取决于使用的 Google API 客户端版本:
const purchase = await androidPublisher.purchases.subscriptionsv2.get({
packageName: 'com.example.app',
token: purchaseToken,
});
if (purchase.data.subscriptionState === 'SUBSCRIPTION_STATE_ACTIVE') {
// 核对订阅商品、到期时间等信息,然后发放权益。
}
// 如购买尚未被 acknowledged,需要调用对应的 acknowledge 接口。
旧式 acknowledge 接口通常还需要 product ID:
await androidPublisher.purchases.subscriptions.acknowledge({
packageName: 'com.example.app',
subscriptionId: 'my_subscription_id',
token: purchaseToken,
requestBody: {},
});
应以当前使用的 Google Play Developer API 和客户端库文档为准,不能只根据示例中的字段判断购买是否已经确认。
后端处理成功后,前端还应结束 Payment Request:
try {
const paymentResponse = await request.show();
const purchaseToken = paymentResponse.details.purchaseToken;
const response = await fetch('/api/google-play/subscription', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ purchaseToken }),
});
if (!response.ok) {
await paymentResponse.complete('fail');
throw new Error('Subscription verification failed');
}
await paymentResponse.complete('success');
} catch (error) {
console.error(error);
}
paymentResponse.complete('success') 只负责结束前端支付流程,不能替代后端验证购买状态,也不应假定它必然可以替代 Google Play Developer API 的 acknowledge 操作。
多个计划如何让用户选择
如果一个订阅商品下配置了多个 base plan,而 PWA 的 Payment Request 接口仍然只能接受 sku,下面的调用就无法精确指定其中某个计划:
data: {
sku: 'my_subscription_id',
}
可以采用以下两种方式。
方式一:只销售兼容方案
在 Play Console 中选择一个兼容当前接口的 base plan。用户购买该 product ID 时,Google Play 会使用这个方案。
这种方式适合只提供一种计费周期或价格的订阅。
方式二:为不同计划创建不同商品
如果需要让用户选择月付或年付,可以创建两个订阅商品:
premium_monthly
premium_yearly
用户先在页面上选择计划,前端再将对应的 product ID 传给 sku:
async function subscribe(productId) {
const request = new PaymentRequest([
{
supportedMethods: 'https://play.google.com/billing',
data: {
sku: productId,
},
},
]);
const paymentResponse = await request.show();
const purchaseToken = paymentResponse.details.purchaseToken;
// 将 productId 和 purchaseToken 一起交给后端验证。
}
调用示例:
await subscribe('premium_monthly');
// 或
await subscribe('premium_yearly');
后端不能直接信任前端传来的 productId,仍需根据 Google Play 返回的购买信息,核对用户实际购买的商品和订阅状态。
关于“Confirm Plan”
“Confirm Plan”并不表示开发者需要把 base plan ID 填入 sku。出现该状态时,应优先检查:
- base plan 是否已经激活。
- 是否配置了兼容当前结算接口的 base plan。
- 后端是否已经 acknowledge 这笔购买。
- purchase token 是否对应正确的包名和订阅商品。
- Play Console 中是否有尚未生效的价格变更,或需要用户同意的订阅变更。
- 测试账号是否已经完成测试购买,还是仍处于待处理状态。
如果购买没有及时完成 acknowledge,Google Play 可能撤销购买并退款,因此需要重点检查这一项。
PWA 前端传递的是订阅商品的 product ID,base plan 则由 Play Console 中的兼容配置决定。只有所使用的 Web Billing 接口明确提供了 base plan 或 offer 的选择参数,前端才能在一次购买请求中直接指定它们。对于当前这种只支持 sku 的调用方式,不能把 base plan ID 当作 SKU 使用。
备注:内容仅供参考。
