在 PWA 中实现 Play 结算服务

如果您的 PWA 在 Google Play 上架,并且您想通过销售应用内商品或订阅内容来创收,那么根据 Play 政策,您需要实现 Play 结算服务。您需要在 PWA 中实现两个 API:Digital Goods APIPayment Request API

Digital Goods API

数字商品 API 是您的应用与 Google Play 之间的接口。借助该库,您可以检索在 Play 管理中心内为应用内商品和订阅内容输入的数字商品和详情,还可以检索用户已进行的购买交易。如果您尚未在 Play 管理中心内添加应用内商品或订阅,请务必按照 Play 管理中心内有关 Play 结算的设置进行操作。

2021 年 11 月 30 日,ChromeOS 96 发布,其中包含数字商品 API 2.0 实现。

数字商品 API 第一个版本的源试用已于 2022 年 1 月 30 日结束。因此,我们现在已弃用该 API,仅提供 v2 版 API。

2022 年 6 月 23 日,ChromeOS 103 发布,其中包含数字商品 API 2.1 实现。此版本没有任何重大变更,仅包含新方法和附加字段:listPurchaseHistory()itemType

注册参加源试用

注意:数字商品 API 目前通过源试用(一种允许开发者提前访问新 Web API 的机制)提供。您需要注册 Digital Goods API v2 源试用并申请令牌,然后在源中的任何网页上提供该令牌

注册源试用后,您会看到“有效期限”日期,该日期表示您的令牌保证可正常运行的截止日期。请记得在临近该日期时续订令牌,以便继续参与试用。作为源试用提供的 API 可能会发生变化,因此请务必及时了解您参与的任何源试用的最新变化。如有任何问题,请参阅 Digital Goods API 文档

Payment Request API

当用户进行购买交易时,Payment Request API 会处理实际的付款交易。它会利用 Digital Goods API 提供的商品详情,使用适当的付款方式(在本例中为 Google Play 结算)进行应用内购。

检测 Digital Goods API 的功能

您可以通过检查 window 对象中是否存在 getDigitalGoodsService 方法,来检测是否已通过源试用正确启用网站上的 API。

if ('getDigitalGoodsService' in window) {
  // Digital Goods API is supported!
} else {
  console.log('DigitalGoodsService is not available.');
  // Use another payment method
}

连接到 Google Play 结算服务

数字商品 API 的设计旨在与各种浏览器和数字商店兼容,类似于 Payment Request API 不受浏览器限制,可与不同的支付服务提供商搭配使用。如需获取与 Google Play 结算服务关联的服务实例,请将字符串 "https://play.google.com/billing" 作为支付方式传递给 getDigitalGoodsService()

如果该方法抛出错误,则表示 Google Play 结算付款方式不可用(例如,用户是通过浏览器访问您的 PWA)。您应为交易提供其他付款方式。

if ('getDigitalGoodsService' in window) {
  // Digital Goods API is supported!
  try {
    const service = await window.getDigitalGoodsService('https://play.google.com/billing');
    // Google Play Billing service is available
  } catch (error) {
    // Google Play Billing service is not available. Use another payment flow.
  }
}

获取商品详情

将数字商品服务连接到 Google Play 后,您可以使用该 API 检索有关商品和购买交易的信息。

借助 getDetails() 方法,您可以获取有关自己在 Play 管理中心内设置的商品的信息。商品名、说明和价格等信息应显示在应用界面中,以便用户了解可购买的商品以及价格。

getDetails() 方法需要一个商品 ID 列表,其中包含您在 Play 管理中心内创建的应用内商品和订阅的商品 ID。

const itemDetails = await service.getDetails(['product_1', 'product_2', 'product_3']);
for (const item of itemDetails) {
  // Display item information to user
  displayItem(item.title, item.description, item.price);
}

为了获得适合用户所在语言区域的价格,您需要进行一些额外的格式设置:

const localePrice = new Intl.NumberFormat(navigator.language, {
  style: 'currency',
  currency: item.price.currency,
}).format(item.price.value);

注意:Digital Goods API 不提供用于获取商品 ID 列表的方法。您需要将它们硬编码到客户端中,或者从后端服务器中提取它们。不过,Google Play Developer API 可让您从后端查询商品 ID 列表。(详细了解如何在后端服务器中实现关键的 Play 结算服务组件。无论您选择哪种解决方案,都请务必确保商品 ID 与您在 Play 管理中心内设置的商品 ID 保持一致。

在 API 的 v2.1 中,getDetails() 返回的字段之一是 itemType。这是一个枚举,其中的值为 ”product””subscription”,分别表示相应商品是应用内商品还是订阅。如果您需要对每种商品类型应用不同的处理方式,那么能够区分这两种商品类型会很有用。例如,您可能有一个专门供用户订阅的页面,以及另一个用于展示其他非订阅产品的页面。此外,它还有助于了解要在后端中使用的适当的 Google Play Developer API REST 资源purchases.productspurchases.subscriptions)。

购买商品

向用户显示商品和详细信息后,您可以使用 Payment Request API 构建购买流程。与 Digital Goods API 搭配使用时,只需要一个输入参数:methodData

Play 结算仅允许一次购买一件商品;Play 服务器已知道商品的价格和详细信息,因此无需 details 参数。如需更详细的说明,请参阅解说

使用 PaymentRequestmethodData 参数的 supportedMethods 成员,通过字符串 "https://play.google.com/billing" 将 Google Play 结算服务标识为付款方式。然后在 data 成员中,将商品 ID 作为 sku 传递。

const paymentMethodData = [
  {
    supportedMethods: 'https://play.google.com/billing',
    data: {
      sku: item.itemId,
    },
  },
];

然后,创建付款请求并调用 show() 以启动付款流程:

const request = new PaymentRequest(paymentMethodData);
const paymentResponse = await request.show();

这会向用户显示 Play 购买界面,其中包含用户尝试购买的商品的详细信息。他们可以选择放弃交易或继续付款。如果用户取消付款,show() 返回的 promise 将被拒绝并报错。如果用户成功付款并完成购买,相应 promise 将解析为 PaymentResponse。在付款响应的 details 属性中,系统会返回购买交易令牌。

为防范欺诈行为,请务必在后端服务器上验证购买交易和购买令牌。最好还要跟踪用户及其关联的购买令牌。了解如何在后端服务器上实现验证

验证购买交易后,对付款响应调用 complete() 以完成付款流程并关闭结算界面。您还可以传入一个可选的 result 字符串来指示付款流程的状态。浏览器是否向用户提供此结果的任何指示取决于浏览器。Chrome 不会创建任何用户可见的提示,因此建议您在 PWA 中显示自己的错误或成功消息。

/* Changes were recently made so that the PaymentResponse `details` property returns the purchase token as `purchaseToken` instead of `token`. Note that `token` will be deprecated at some point in the future. To ensure that your app won't be affected by this, make the change to `purchaseToken` in your client code and use the latest version of Bubblewrap (v1.13.5 and later) to update and generate a new app package to upload to the Play Console. */
const { purchaseToken } = paymentResponse.details;

let paymentComplete;
if (validatePurchaseOnBackend(purchaseToken)) {
  paymentComplete = await paymentResponse.complete('success');
  // Let user know their purchase transaction has successfully completed and been verified
} else {
  paymentComplete = await paymentResponse.complete('fail');
  // Let user know their purchase transaction failed to verify
}

订阅升级和降级

此购买流程适用于应用内商品和订阅购买交易。不过,对于订阅,Google Play 还提供了您可以实现的额外购买选项:升级和降级。在为付款方式构建 data 时,您需要传入以下内容来启动升级或降级流程:

  • sku:这是要升级或降级到的新订阅的商品 ID。
  • oldSku:这是用户当前订阅的商品 ID。
  • purchaseToken:这是用户当前订阅的购买交易令牌。如前所述,最好在后端跟踪购买交易令牌。对于此场景和其他场景,您还应将用户与其当前的购买交易和购买交易令牌相关联。
  • prorationMode:新订阅取代用户当前订阅后,系统将按此方式收取费用。
按比例计费模式 说明

immediateAndChargeProratedPrice

订阅会立即升级,结算周期保持不变。用户随后需要补足剩余订阅期的差价。

immediateAndChargeFullPrice

订阅会立即升级或降级,并且系统会即刻按全价向您收取新订阅权利的费用。系统会将之前订阅的剩余价值按比例折算成时间,计入新订阅。此按比例分摊模式最近已在 Google Play 结算库 4.0 版中添加。现在,从 1.13.5 版开始,可以通过 Bubblewrap 使用此功能。

immediateWithoutProration

暂时停用:此按比例分摊模式存在潜在的欺诈途径,用户可以在一个结算周期内免费升级订阅。请注意,我们已暂时停用此模式,以便着手解决相关问题。

immediateWithTimeProration

订阅会立即升级或降级。系统会根据差价调整任何剩余时长,并将下一个结算日期往后推延,将剩余时长计入新的订阅。这是默认行为。

deferred

只有在订阅续订时,订阅才会升级或降级。这对于降级尤其有用。

unknownSubscriptionUpgradeDowngradePolicy

未设置政策。不建议这样做。

如需详细了解 Google Play 结算库中的不同按比例分摊模式,请参阅参考文档。如需详细了解订阅升级和降级以及按比例分配模式建议,请参阅 Android 开发者文档。

这些额外字段的用法如下所示:

const paymentMethod = [
  {
    supportedMethods: 'https://play.google.com/billing',
    data: {
      sku: item.itemId,
      oldSku: oldPurchase.itemId,
      purchaseToken: oldPurchase.purchaseToken,
      prorationMode: 'immediateAndChargeProratedPrice',
    },
  },
];

其中,item 是用户尝试升级或降级到的新订阅的 ItemDetailsoldPurchase 是用户当前订阅的 PurchaseDetails

确认购买交易

用户购买商品后,您应向其授予适当的授权(访问其刚刚购买的商品或内容的权限)。然后,确认购买交易。确认购买交易可让 Google Play 知道您已收到并妥善处理相应购买交易。

注意:如果购买交易未在购买时间后的 72 小时内得到确认,系统会向用户退款并撤消该购买交易。购买令牌将不再有效,因此当您查询现有购买交易时,系统不会返回已撤消的购买交易。这样可确保,如果因网络错误而导致用户无法获得商品的授权,系统不会向用户收取不当费用。

您应使用 Google Play Developer API 从后端服务器确认购买交易。我们建议您在后端服务器中同时授予使用权并确认购买交易。

  1. 用户在客户端完成购买后,请在请求中将购买令牌和商品 ID 发送到后端服务器。
  2. 在后端,如需获取购买交易的详细信息以进行验证,请调用:
  3. 在后端数据库中授予相应授权。
  4. 然后,通过调用以下方法确认购买交易:

消耗购买交易

确认购买交易后,Google Play 便会知道用户现在拥有相应商品,不应允许用户再次购买该商品。如果用户只需购买一次即可永久拥有某商品(例如游戏角色皮肤),则该商品不是消耗型商品。

或者,该商品可能是您限制用户一次只能购买一件的商品。然后,用户需要先使用该商品,才能购买另一件。当用户“使用”商品时,为了让 Google Play 知道用户已消耗该商品,您应调用 consume() 方法。然后,Google Play 会使该商品可供用户再次购买。

对于您允许用户拥有多件的商品,用户必须能够重复购买,而无需先使用(我们称之为可重复购买的商品)。同样,这些商品需要先“消耗”,然后 Google Play 才会允许用户再次购买。因此,即使用户尚未使用该商品,您也需要调用 consume() 方法将该商品标记为已消耗。

// After the user purchases the item, send the purchase token and item ID to your backend to grant the entitlement and acknowledge it right away

. . .
// When the user uses the item or if it is a repeatable item, consume it so it’s available for purchase again.
service.consume(purchaseToken);
}

检查现有购买交易

最后一个关键用户流程是检查现有购买交易(尚未消耗的应用内商品和正在进行的订阅),以便让用户了解他们目前拥有哪些订阅或商品。这些现有购买交易是指之前在任何设备上通过应用内购买或在 Play 商店中进行的 Google Play 购买交易。在 Play 商店中从应用外部进行的购买交易称为应用外购买交易

在检索现有购买交易时,您还应检查确认状态,并确认之前已完成但未正确确认的所有购买交易。建议尽快确认购买交易,以便用户的授权保持最新状态并正确反映在应用中。

Digital Goods API 的 listPurchases() 方法将返回一个 PurchaseDetails 列表,其中包含每次购买交易的 itemIdpurchaseToken。您需要在后端服务器上使用 Google Play Developer API 来检查购买交易的状态并进行适当的确认。您应该:

  1. 在客户端调用 Digital Goods API listPurchases() 方法,以检索用户的购买交易列表。
  2. 对于每笔购买交易,请将 purchaseTokenitemId 传递给您的后端。
  3. 如果适用,请在后端数据库中授予使用权。
  4. 然后调用:并检查 acknowledgementState
  5. 如果值为 0(尚未确认),则调用:

详细了解如何在授予权利前在后端服务器上验证购买交易

交易记录

虽然 listPurchases 会返回有关用户现有购买交易的信息,但 listPurchaseHistory() 方法(在 API 的 v2.1 中)会返回用户针对每个商品发起的最近一笔购买交易,无论该购买交易是否已过期、已取消或所购商品已被使用。listPurchaseHistory() 方法会返回一个 PurchaseDetails 列表,其中包含每次购买交易的 itemIdpurchaseToken,您需要在后端服务器上使用 Google Play Developer API 来检索更多信息。

应用外购买

应用外购买是指未通过正常的应用内购买流程完成的购买交易。这些交易通常会在 Play 商店中进行,而不是在您的应用中进行。用户可以通过两种主要方式进行应用外购买交易:

  • 兑换促销代码:在 Play 商店用户菜单中,依次前往“优惠和通知” ->“兑换促销代码”“付款和订阅” ->“兑换礼品代码”
  • 重新订阅:在 Play 商店用户菜单中,依次前往“付款和订阅” -> “订阅”。用户可以在此处管理其在不同应用中的所有订阅。对于已过期或已取消的订阅,用户可以选择“重新订阅”。

当用户从 Play 商店重新订阅时,系统不会自动确认其购买交易,这可能会导致用户收到退款。此行为是故意的,因为只有当用户打开应用使用时,才应向其收取订阅费用。用户可能会看到“确认订阅”之类的消息,提醒他们打开应用。

系统会提示用户打开应用以确认购买交易,从而确认订阅。

作为开发者,您需要负责在用户启动应用后实现对这些购买交易的确认。因此,我们建议您检查是否存在未确认的现有购买交易(通常在应用首次启动时),并确认所有尚未确认的购买交易。

让用户管理订阅

为了提供良好的用户体验,请务必为用户提供一种在应用内管理和取消订阅的方式。我们建议您在设置页面或菜单中创建一个深层链接,将用户重定向到 Play 商店中您应用的订阅管理页面。请将以下网址替换为相应的“sub-product-id”和“app-package-name”:

https://play.google.com/store/account/subscriptions?sku=sub-product-id&package=app-package-name

后续步骤

这些用户流程和代码段是一个基本实现,用于演示如何在 PWA 中使用 Digital Goods API 和 Payment Request API 来实现 Play Billing。您应根据应用的上下文和使用情形来合理利用这些 API。如需查看端到端实现示例,请查看我们的开源示例

然后,了解如何在后端服务器中实现关键的 Play 结算组件,以确保应用安全无虞,并始终根据用户的授权情况进行更新。