129 lines
7.0 KiB
Markdown
129 lines
7.0 KiB
Markdown
# 谷歌支付流程
|
||
|
||
本文档描述 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** | 购买凭据 JSON(purchaseData) | 同上 **billingClientPurchase** 的 **originalJson** |
|
||
| **federation** | 支付/订单 id(id) | 创建订单接口 `createPayment` 返回的 **federation** |
|
||
| **asset** | 用户 id(userId) | 当前登录用户 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 固定为当前商品的 **code(helm)**,与 Play 后台产品 ID 一致。
|
||
3. **回调 googlepay**:body 为四字段 **sample**(signature)、**merchant**(purchaseData/originalJson)、**federation**(订单 id)、**asset**(userId);federation 为空则不回调,可按策略重试创建订单或提示失败。
|
||
4. **未核销订单**:通过 `getUnacknowledgedPurchases()` 获取 `isAcknowledged == false` 的购买,可用于启动时补发回调或展示待处理订单。
|