PAYATHON 2026

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 AppGoogle Wallet for Digital Goods API
Android Mobile Chrome AppGoogle Play In-app Billing API
iOS Mobile Chrome AppApple In-App Purchase / StoreKit

插件提供的 JavaScript 方法可能与 desktop 的 Digital Goods API 相似,所以应用层可以复用部分代码,例如获取商品、发起购买和读取购买记录。

不过,以下内容仍由各平台独立管理:

  • 商户注册和收款账户
  • 商品配置及商品 ID
  • 交易记录
  • 退款和撤销机制
  • 服务端验证接口
  • 地区支持范围
  • 税务及结算规则

因此,Google Play 是否支持 Belarus 地区的开发者收款,应根据 Google Play 对开发者和商户所在地的规则判断。移动插件即使采用了相似的 JavaScript 接口,也不会改变 Digital Goods API 或 Google Play 原有的地区限制。

推荐的跨平台验证架构

Desktop 客户端不应直接验证 Google Play 交易。可以采用下面的流程:

  1. 用户在 mobile 和 desktop 版本中登录同一个应用账号。
  2. Android 客户端完成 Google Play 购买。
  3. 客户端将 packageName、productId 和 purchaseToken 发送到自己的服务器。
  4. 服务器调用 Google Play Developer API 验证购买。
  5. 验证成功后,服务器将商品权益绑定到应用账号。
  6. Desktop Chrome App 登录后向服务器查询权益,无需直接查询 Google Play。
  7. 如果 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 当前支持的支付方案,再通过自己的服务端建立统一的权益系统。

备注:内容仅供参考。