petsHero-AI/docs/googlepay.md
2026-03-12 22:16:36 +08:00

129 lines
7.0 KiB
Markdown
Raw 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. 流程总览
- **第三方支付开启**`enable_third_party_payment === 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()`,若有未核销订单,可逐条上报 `POST /v1/payment/googlepay`federation 可用该笔的 `orderId`,若服务端支持;否则需先 createPayment 拿到 federation 再回调),上报成功后对该笔购买调用 `InAppPurchase.instance.completePurchase(purchase)` 完成核销。
- **注意**`queryPastPurchases` 不包含已消耗consumed的商品未确认的消耗型商品会一直在列表中直到被确认或消耗。
---
## 6. 代码位置速查
| 步骤 | 位置 |
|------|------|
| 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 节 |
---
## 7. 小结
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` 的购买,可用于启动时补发回调或展示待处理订单。