Mobile Chrome Apps payments API 如何跨平台验证购买?
结论
Mobile Chrome Apps Payment Plug-in 在 Android 上实际调用 Google Play In-app Billing API,在 iOS 上调用 iOS In-App Purchase / StoreKit。Desktop Chrome App 使用的则是 Google Wallet for Digital Goods API。
插件所说的“desktop 和 mobile 使用相同的 API”,是指它尽量提供相似的 JavaScript 调用接口,并不是说两者共用支付平台、商户账号、商品目录或购买记录。
Desktop Chrome App 也无法通过用户的 Google 账号直接查询其 Google Play 购买状态。要共享跨平台权益,需要建立自己的账号体系和服务端。服务端负责验证 Google Play 购买凭证,再向 desktop 和 mobile 客户端统一返回用户权益。
为什么看起来像同一个 API
Mobile Chrome Apps Payment Plug-in 会适配各平台底层的支付系统:
| 运行平台 | 实际支付系统 |
|---|---|
| Desktop Chrome App | Google Wallet for Digital Goods API |
| Android Mobile Chrome App | Google Play In-app Billing API |
| iOS Mobile Chrome App | Apple In-App Purchase / StoreKit |
插件提供的 JavaScript 方法可能与 desktop 的 Digital Goods API 相似,所以应用层可以复用部分代码,例如获取商品、发起购买和读取购买记录。
不过,以下内容仍由各平台独立管理:
- 商户注册和收款账户
- 商品配置及商品 ID
- 交易记录
- 退款和撤销机制
- 服务端验证接口
- 地区支持范围
- 税务及结算规则
因此,Google Play 是否支持 Belarus 地区的开发者收款,应根据 Google Play 对开发者和商户所在地的规则判断。移动插件即使采用了相似的 JavaScript 接口,也不会改变 Digital Goods API 或 Google Play 原有的地区限制。
推荐的跨平台验证架构
Desktop 客户端不应直接验证 Google Play 交易。可以采用下面的流程:
- 用户在 mobile 和 desktop 版本中登录同一个应用账号。
- Android 客户端完成 Google Play 购买。
- 客户端将
packageName、productId和purchaseToken发送到自己的服务器。 - 服务器调用 Google Play Developer API 验证购买。
- 验证成功后,服务器将商品权益绑定到应用账号。
- Desktop Chrome App 登录后向服务器查询权益,无需直接查询 Google Play。
- 如果 desktop 端也支持购买,服务器可以用相同思路验证 Digital Goods 的交易凭证,并将结果写入同一套权益记录。
整体关系如下:
Android purchase
↓
Google Play Billing
↓ purchaseToken
Application backend
↓ verify with Google Play Developer API
Unified entitlement database
↑
Desktop Chrome App / Android App / iOS App
各客户端共享的是应用自己的“用户权益”,不是 Google Play 和 Digital Goods 的原始订单。
Android 客户端上报购买凭证
购买成功后,客户端应将购买凭证发送给服务端。下面的代码只用于展示数据流。插件返回对象的具体字段名取决于插件版本,应以对应版本的文档为准。
async function registerAndroidPurchase(purchase) {
const response = await fetch("https://api.example.com/purchases/google-play", {
method: "POST",
headers: {
"Authorization": `Bearer ${getApplicationAccessToken()}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
packageName: "com.example.app",
productId: purchase.productId,
purchaseToken: purchase.purchaseToken
})
});
if (!response.ok) {
throw new Error("Purchase verification failed");
}
return response.json();
}
purchaseToken 不能只保存在本地。无论用户重新安装应用、更换设备,还是在 desktop 端登录,都应以服务端保存的权益记录为准。
服务端验证 Google Play 一次性商品
服务端可以通过 Google Play Developer API 验证非订阅商品。下面是使用 Node.js googleapis 的示意代码:
import { google } from "googleapis";
const auth = new google.auth.GoogleAuth({
scopes: ["https://www.googleapis.com/auth/androidpublisher"]
});
const androidPublisher = google.androidpublisher({
version: "v3",
auth
});
async function verifyOneTimeProduct({
packageName,
productId,
purchaseToken
}) {
const response = await androidPublisher.purchases.products.get({
packageName,
productId,
token: purchaseToken
});
const purchase = response.data;
if (purchase.purchaseState !== 0) {
throw new Error("Purchase is not in purchased state");
}
return {
valid: true,
orderId: purchase.orderId,
purchaseTimeMillis: purchase.purchaseTimeMillis,
acknowledgementState: purchase.acknowledgementState,
consumptionState: purchase.consumptionState
};
}
验证成功后,数据库中至少应保存以下信息:
applicationUserId
provider = "google_play"
packageName
productId
purchaseToken
orderId
purchaseState
entitlement
verifiedAt
服务端还应限制允许提交的 packageName 和 productId,不能直接信任客户端传来的值。
Desktop Chrome App 查询权益
Desktop 端只需查询自己的业务服务器:
async function hasPremiumEntitlement() {
const response = await fetch(
"https://api.example.com/me/entitlements/premium",
{
headers: {
"Authorization": `Bearer ${getApplicationAccessToken()}`
}
}
);
if (!response.ok) {
throw new Error("Unable to load entitlement");
}
const result = await response.json();
return result.active === true;
}
服务端响应可以采用与支付渠道无关的格式:
{
"entitlement": "premium",
"active": true,
"source": "google_play"
}
这样一来,desktop 客户端不必解析 Google Play 的订单格式。用户无论通过 Google Play、Digital Goods 还是其他受支持的渠道购买,都可以获得同一种应用内权益。
不能通过 Google 账号自动关联购买
Google Play Billing 不会向应用提供可供 desktop 端查询的通用 Google Play 用户标识。应用也不能提交一个邮箱地址,再查询该邮箱购买过哪些商品。
购买与应用账号之间的关联必须在购买时完成:
已登录的应用账号 + 已验证的 purchaseToken → 用户权益
如果 mobile 版本允许匿名购买,之后再将购买记录自动恢复到 desktop 账号会比较困难。较稳妥的方式是要求用户在购买前登录,或者在购买完成后立即提示用户绑定应用账号。
订阅、消耗品和退款需要单独处理
不同类型的商品需要采用不同的权益规则:
- 永久型非消耗品验证成功后,可以授予长期权益,但仍需处理退款、撤销和欺诈交易。
- 消耗品的一次购买通常只能兑换一次。服务端必须防止同一个
purchaseToken或订单被重复兑换。 - 订阅不能只在首次购买时验证一次,还要检查续订、暂停、过期和取消状态。
- 发生退款或撤销后,应结合 Google Play 提供的实时开发者通知、Voided Purchases API 或定期校验机制更新权益。
所有验证密钥和 Google Play Developer API 凭据都必须保存在服务器端,不能打包进 Chrome App 或 Android 客户端。
需要注意的历史兼容性
Chrome Apps、Google Wallet for Digital Goods 和相关移动打包工具都属于历史技术栈,其可用性和支持状态可能已经发生变化。旧插件文档中所说的“相同 API”,应理解为 JavaScript 接口兼容层,而不是统一的支付后端。
如果仍在维护现有应用,应以实际使用版本的插件文档和当前平台政策为准。新项目更适合直接采用 Android、iOS 和 Web 当前支持的支付方案,再通过自己的服务端建立统一的权益系统。
备注:内容仅供参考。
