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

165 lines
9.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 谷歌支付流程
本文档描述 Android 上 Google Play 内购的完整流程,与 `recharge_screen.dart``GooglePlayPurchaseService``PaymentApi` 实现对应。
---
## 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.billingClientPurchase`**signature** |
| **merchant** | 购买凭据 JSONpurchaseData | 同上 **billingClientPurchase****originalJson** |
| **federation** | 支付/订单 idid | 创建订单接口 `createPayment` 返回的 **federation** |
| **asset** | 用户 iduserId | 当前登录用户 id与 createPayment 的 asset 一致) |
### 4.2 示例 body 结构
```json
{
"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.billingClientPurchase``PurchaseWrapper`)取 **originalJson**、**signature**,分别填入请求体的 **merchant**、**sample**。
- createPayment 返回的 **federation** 填入 **federation**,当前用户 id 填入 **asset**
---
## 5. 获取未核销订单
「未核销」指 Google Play 侧尚未被确认(`isAcknowledged == false`)的购买,通常出现在:上次支付成功后未完成 `completePurchase`、或未成功回调服务端即退出应用。
### 5.1 客户端如何获取
- **方法**`GooglePlayPurchaseService.getUnacknowledgedPurchases()`
- **实现**:仅 Android通过 `InAppPurchaseAndroidPlatformAddition.queryPastPurchases()` 查询本地/缓存的购买,再筛选 `billingClientPurchase.isAcknowledged == false` 的项。
- **返回**`List<UnacknowledgedGooglePayPurchase>`,每项包含 `orderId`Google 订单号)、`productId``payload`purchaseData + 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())`<br>② 进入充值页:`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` 内、内购成功后调用 |
| 凭据数据结构 | Android`GooglePlayPurchaseDetails.billingClientPurchase`orderId, originalJson, signature |
| 获取未核销订单 | `GooglePlayPurchaseService.getUnacknowledgedPurchases()`,见第 5 节 |
| 补单流程 | `GooglePlayPurchaseService.runOrderRecovery()`,见第 6 节 |
---
## 8. 小结
1. **创建订单**仅在选择「Google Pay」且第三方支付开启时调用 createPayment拿到 **federation** 后才会调起谷歌支付并回调 googlepay。
2. **调起谷歌支付**productId 固定为当前商品的 **codehelm**,与 Play 后台产品 ID 一致。
3. **回调 googlepay**body 为四字段 **sample**signature、**merchant**purchaseData/originalJson、**federation**(订单 id、**asset**userIdfederation 为空则不回调,可按策略重试创建订单或提示失败。
4. **未核销订单**:通过 `getUnacknowledgedPurchases()` 获取 `isAcknowledged == false` 的购买;每项含 `purchaseDetails` 用于补单成功后 `completePurchase`
5. **补单**`runOrderRecovery()` 在应用启动(登录完成后)与进入充值页时执行;补单使用的 **federation** 为创建订单时的订单 id内购前会持久化 Google orderId→federation补单时按 Google orderId 取回);无保存 federation 的未核销订单会跳过。