petsHero-AI/docs/googlepay.md
2026-03-13 22:04:57 +08:00

9.1 KiB
Raw Blame History

谷歌支付流程

本文档描述 Android 上 Google Play 内购的完整流程,与 recharge_screen.dartGooglePlayPurchaseServicePaymentApi 实现对应。


1. 流程总览

  • 第三方支付开启lucky === true 且已登录):先创建订单 → 调起谷歌支付 → 支付成功后回调 /v1/payment/googlepay
  • 第三方支付关闭或未登录:仅 Android 直接调起谷歌支付,不创建订单、不回调 googlepay。
用户点击 Buy某商品
    │
    ├─ 第三方支付开 + 已登录
    │       ├─ getPaymentMethods(activityId)
    │       ├─ 弹窗选择支付方式
    │       ├─ 若选「Google Pay」
    │       │       ├─ POST /v1/payment/createPayment → 得到 federation订单 id
    │       │       ├─ 调起 Google Play 内购productId = 商品 code/helm
    │       │       └─ 支付成功后 POST /v1/payment/googlepay见下文
    │       └─ 若选其他方式 → 打开 createPayment 返回的 payUrl
    │
    └─ 第三方支付关或未登录
            └─ 仅 Android直接调起 Google Play 内购productId = 商品 code无 createPayment、无 googlepay 回调

2. 创建订单(仅第三方 + 选 Google Pay 时)

项目 说明
接口 POST /v1/payment/createPayment
入参 sentinel, asset(userId), warrior(activityId), resource, ceremony选 Google Pay 时 resource/ceremony 含 "GooglePay"
关键响应 federation:订单 id后续 googlepay 回调必传;convert:其他支付方式的 payUrl选 Google Pay 时不使用
  • 当返回的 federation订单 id不为空时,继续调起谷歌支付并可在成功后回调 googlepay。
  • 当返回的 federation 为空时:可按业务要求重试创建订单(如最多 3 次),仍失败则提示失败。

3. 调起谷歌支付

项目 说明
代码 GooglePlayPurchaseService.launchPurchaseAndReturnData(productId)(所有内购统一使用)
productId 当前商品的 code(即接口里的 helm),须与 Google Play 后台「产品 ID」完全一致
成功结果 返回的凭据用于构造 googlepay 回调 body见下节

Android 执行;非 Android 提示 "Google Pay is only available on Android"。


4. 支付成功后回调:POST /v1/payment/googlepay

仅在第三方支付开 + 选 Google Pay + 已先创建订单时调用。请求体为 JSON服务端用于校验并落单。

4.1 请求体字段body

请求体为四个顶层字段语义与取值来源对应关系如下id / purchaseData / signature / userId 分别对应 federation / merchant / sample / asset

请求体字段 含义(填入的值) 客户端取值来源
sample 签名signature 谷歌支付成功后,GooglePlayPurchaseDetails.billingClientPurchasesignature
merchant 购买凭据 JSONpurchaseData 同上 billingClientPurchaseoriginalJson
federation 支付/订单 idid 创建订单接口 createPayment 返回的 federation
asset 用户 iduserId 当前登录用户 id与 createPayment 的 asset 一致)

4.2 示例 body 结构

{
  "sample": "YbOntv0sVOsZ5d4F8hIYdPNSMy9a4+5oAsV/...",
  "merchant": "{\"orderId\":\"GPA.3327-0087-2324-9960\",\"packageName\":\"com.xxx.xxxx\",\"productId\":\"com.xxx.xxxx599\",\"purchaseTime\":1773305500428,\"purchaseState\":0,\"purchaseToken\":\"...\",\"quantity\":1,\"acknowledged\":false}",
  "federation": "1315538320560683421235",
  "asset": "135303839048"
}
  • federation:来自 createPayment 的 federation(服务端订单 id
  • merchant:来自 Google Play 的 originalJson(整段购买凭据 JSON 字符串)。
  • sample:来自 Google Play 的 signature,服务端用其校验 merchant。
  • asset:当前用户 id。

4.3 与客户端实现对应

  • 内购成功后,从 GooglePlayPurchaseDetails.billingClientPurchasePurchaseWrapper)取 originalJsonsignature,分别填入请求体的 merchantsample
  • createPayment 返回的 federation 填入 federation,当前用户 id 填入 asset

5. 获取未核销订单

「未核销」指 Google Play 侧尚未被确认(isAcknowledged == false)的购买,通常出现在:上次支付成功后未完成 completePurchase、或未成功回调服务端即退出应用。

5.1 客户端如何获取

  • 方法GooglePlayPurchaseService.getUnacknowledgedPurchases()
  • 实现:仅 Android通过 InAppPurchaseAndroidPlatformAddition.queryPastPurchases() 查询本地/缓存的购买,再筛选 billingClientPurchase.isAcknowledged == false 的项。
  • 返回List<UnacknowledgedGooglePayPurchase>,每项包含 orderIdGoogle 订单号)、productIdpayloadpurchaseData + signature用于回调 body 的 merchant/sample

5.2 典型用法

  • 应用启动时:调用 getUnacknowledgedPurchases(),若有未核销订单,补单流程会使用本地保存的「创建订单时的 federation」逐笔回调无保存 federation 的订单会跳过。
  • 注意queryPastPurchases 不包含已消耗consumed的商品未确认的消耗型商品会一直在列表中直到被确认或消耗。

6. 补单流程(自动)

客户端已实现完整补单:拉取未核销订单 → 用本地保存的创建订单时的 federation 逐笔回调 googlepay → 服务端返回 line == 'SUCCESS'completePurchase 并刷新用户信息。补单必须使用创建订单时的订单 id不能使用 Google 的 orderId。

6.1 创建订单 id 的持久化

  • 每次调起内购前都会先 createPayment 拿到 federation(服务端订单 id
  • 在发起 POST /v1/payment/googlepay 前,将 Google orderId → federation 写入本地SharedPreferences供补单时使用。
  • 回调成功或补单成功后删除对应映射。

6.2 入口与触发时机

项目 说明
方法 GooglePlayPurchaseService.runOrderRecovery()
触发时机 ① 应用启动:main()AuthService.loginComplete.then((_) => runOrderRecovery())
② 进入充值页:RechargeScreen.initState 中调用
前置条件 仅 Android已登录UserState.userId 非空)

6.3 流程步骤

  1. 若非 Android 或未登录,直接返回。
  2. 调用 getUnacknowledgedPurchases() 获取未核销列表。
  3. 对每笔订单:用 Google orderId 查本地保存的 federation(创建订单时的订单 id若无则跳过该笔并打日志。
  4. 用查到的 federation 调用 POST /v1/payment/googlepay
  5. 若响应成功且 line == 'SUCCESS'completePurchase、删除该笔映射、标记需要刷新。
  6. 若有任意一笔补单成功,最后调用 refreshAccount()

6.4 服务端约定

  • 补单请求的 federation 与正常回调一致,均为 createPayment 返回的订单 id;服务端按订单 id 落单/去重。
  • 校验逻辑与正常回调一致sample=签名、merchant=购买凭据),成功时返回 line: 'SUCCESS'

7. 代码位置速查

步骤 位置
Buy 分支(第三方 vs 直接谷歌) recharge_screen.dart_onBuy_runThirdPartyPayment / _runGooglePay
创建订单 PaymentApi.createPayment;调用处在 _createOrderAndOpenUrl
调起内购并拿凭据 GooglePlayPurchaseService.launchPurchaseAndReturnData(productId)
回调 googlepay PaymentApi.googlepay(...),在 _createOrderAndOpenUrl 内、内购成功后调用
凭据数据结构 AndroidGooglePlayPurchaseDetails.billingClientPurchaseorderId, originalJson, signature
获取未核销订单 GooglePlayPurchaseService.getUnacknowledgedPurchases(),见第 5 节
补单流程 GooglePlayPurchaseService.runOrderRecovery(),见第 6 节

8. 小结

  1. 创建订单仅在选择「Google Pay」且第三方支付开启时调用 createPayment拿到 federation 后才会调起谷歌支付并回调 googlepay。
  2. 调起谷歌支付productId 固定为当前商品的 codehelm,与 Play 后台产品 ID 一致。
  3. 回调 googlepaybody 为四字段 samplesignaturemerchantpurchaseData/originalJsonfederation(订单 idassetuserIdfederation 为空则不回调,可按策略重试创建订单或提示失败。
  4. 未核销订单:通过 getUnacknowledgedPurchases() 获取 isAcknowledged == false 的购买;每项含 purchaseDetails 用于补单成功后 completePurchase
  5. 补单runOrderRecovery() 在应用启动(登录完成后)与进入充值页时执行;补单使用的 federation 为创建订单时的订单 id内购前会持久化 Google orderId→federation补单时按 Google orderId 取回);无保存 federation 的未核销订单会跳过。