221 lines
6.7 KiB
Markdown
221 lines
6.7 KiB
Markdown
# 支付流程文档
|
||
|
||
## 1. 概述
|
||
|
||
本文档描述 Android 项目(Flutter)的完整支付流程,包括商品获取、支付方式选择、订单创建、Google Play 内购以及补单机制。
|
||
|
||
---
|
||
|
||
## 2. 支付流程总览
|
||
|
||
```
|
||
用户点击 Buy
|
||
│
|
||
├─ enableThirdPartyPayment === true 且已登录
|
||
│ │
|
||
│ ├─ getPaymentMethods(activityId) 获取支付方式
|
||
│ ├─ 弹窗选择支付方式(_PaymentMethodDialog)
|
||
│ ├─ createPayment 创建订单
|
||
│ │
|
||
│ ├─ 若选中的是 Google Pay(resource/ceremony == "GooglePay")
|
||
│ │ ├─ 调起 Google Play 内购
|
||
│ │ ├─ 拿到 serverVerificationData
|
||
│ │ └─ POST /v1/payment/googlepay 回调验证
|
||
│ │
|
||
│ └─ 否则(其他支付方式)
|
||
│ └─ 打开 payUrl 在外部浏览器完成支付
|
||
│
|
||
└─ enableThirdPartyPayment !== true 或未登录
|
||
└─ 仅 Android:直接调起 Google Play 内购
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 支付分支依据
|
||
|
||
| 条件 | 说明 |
|
||
|------|------|
|
||
| `UserState.enableThirdPartyPayment` | 登录后由 AuthService 从 `/v1/user/common_info` 响应写入 |
|
||
| `UserState.userId` | 用户登录后存储的用户 ID |
|
||
| **第三方支付** | `enableThirdPartyPayment == true` 且 `userId` 非空 |
|
||
| **直接谷歌支付** | 其他情况(未开第三方支付或未登录)|
|
||
|
||
---
|
||
|
||
## 4. 商品展示与获取
|
||
|
||
### 4.1 接口
|
||
|
||
- **Android**: `GET /v1/payment/getGooglePayActivities`
|
||
- **iOS**: `GET /v1/payment/getApplePayActivities`
|
||
|
||
### 4.2 商品字段映射
|
||
|
||
| 字段(API) | 字段(客户端映射) | 说明 |
|
||
|-------------|-------------------|------|
|
||
| helm | code / productId | Google Play 商品 ID |
|
||
| warrior | activityId | 活动 ID,用于创建订单 |
|
||
| guardian | actualAmount | 实际金额 |
|
||
| curriculum | originAmount | 原价(带划线)|
|
||
| forge | bonus | 赠送积分 |
|
||
| glossary | title | 标题 |
|
||
|
||
### 4.3 代码入口
|
||
|
||
文件:`lib/features/recharge/recharge_screen.dart`
|
||
- `_fetchActivities()`: 获取商品列表
|
||
- `_onBuy()`: 用户点击购买入口
|
||
|
||
---
|
||
|
||
## 5. 第三方支付流程
|
||
|
||
### 5.1 步骤
|
||
|
||
1. **获取支付方式**: `POST /v1/payment/get-payment-methods`
|
||
- 参数: `warrior` (activityId), `vambrace` (可选,国家)
|
||
|
||
2. **弹窗选择**: 展示支付方式列表(`_PaymentMethodSheet`),包含:
|
||
- `resource`: 支付方式(如 GOOGLEPAY)
|
||
- `ceremony`: 子支付方式
|
||
- `name`: 显示名称
|
||
- `icon`: 图标 URL
|
||
- `recommend`: 是否推荐
|
||
|
||
3. **创建订单**: `POST /v1/payment/createPayment`
|
||
- 参数: `sentinel`, `asset`(userId), `warrior`(activityId), `resource`, `ceremony`
|
||
- 返回: `federation`(订单ID), `convert`(支付URL)
|
||
|
||
4. **支付方式分支**:
|
||
- **Google Pay**: 调用 `GooglePlayPurchaseService.launchPurchaseAndReturnData()` → 调起内购 → 调用 `PaymentApi.googlepay()` 回调验证
|
||
- **其他方式**: 使用 `url_launcher` 打开 `convert` 支付链接
|
||
|
||
### 5.2 代码位置
|
||
|
||
- 入口: `recharge_screen.dart` → `_runThirdPartyPayment()`
|
||
- 创建订单: `_createOrderAndOpenUrl()`
|
||
- Google Pay 判断: `_isGooglePay()`
|
||
|
||
---
|
||
|
||
## 6. 直接谷歌支付流程
|
||
|
||
仅 Android,且不经过 `getPaymentMethods` 和 `createPayment`(三方支付关闭时):
|
||
|
||
1. 调用 `createPayment`(resource=GooglePay, ceremony=GooglePay)
|
||
2. 调起 Google Play 内购
|
||
3. 回调验证
|
||
|
||
### 代码位置
|
||
|
||
- `recharge_screen.dart` → `_runGooglePay()`
|
||
|
||
---
|
||
|
||
## 7. Google Play 内购统一入口
|
||
|
||
### 7.1 核心方法
|
||
|
||
`GooglePlayPurchaseService.launchPurchaseAndReturnData(productId)`
|
||
|
||
- 调起 Google Play 内购
|
||
- 返回 `GooglePayPurchaseResult` 包含:
|
||
- `orderId`: Google 订单号
|
||
- `payload.purchaseData`: purchaseData(用于 merchant)
|
||
- `payload.signature`: 签名(用于 sample)
|
||
- `purchaseDetails`: PurchaseDetails 对象
|
||
|
||
### 7.2 回调验证
|
||
|
||
`PaymentApi.googlepay(sample, merchant, federation, asset)`
|
||
|
||
- `sample`: 签名
|
||
- `merchant`: purchaseData
|
||
- `federation`: 订单ID
|
||
- `asset`: userId
|
||
|
||
### 7.3 核销
|
||
|
||
`GooglePlayPurchaseService.completeAndConsumePurchase(purchaseDetails)`
|
||
|
||
- 执行 `completePurchase`
|
||
- 执行 `consumePurchase`(Android)
|
||
|
||
---
|
||
|
||
## 8. 补单机制
|
||
|
||
### 8.1 触发时机
|
||
|
||
- 进入充值页时调用 `GooglePlayPurchaseService.runOrderRecovery()`
|
||
|
||
### 8.2 补单流程
|
||
|
||
1. 获取未核销订单: `getUnacknowledgedPurchases()`
|
||
- 合并 `queryPastPurchases` 和 `purchaseStream` 的待处理订单
|
||
|
||
2. 对每笔订单:
|
||
- 查询本地存储的 `federation` 映射
|
||
- 若存在 federation: 调用 `googlepay` 回调 → 成功后 consume
|
||
- 若无 federation: 仅执行 consume 解除「已拥有此内容」
|
||
|
||
3. 补单成功后刷新账户
|
||
|
||
### 8.3 存储映射
|
||
|
||
使用 `SharedPreferences` 存储 `googleOrderId → federation` 映射:
|
||
- `saveFederationForGoogleOrderId()`
|
||
- `getFederationForGoogleOrderId()`
|
||
- `removeFederationForGoogleOrderId()`
|
||
|
||
---
|
||
|
||
## 9. API 汇总
|
||
|
||
| 接口 | 方法 | 说明 |
|
||
|------|------|------|
|
||
| `/v1/payment/getGooglePayActivities` | GET | 获取 Android 商品列表 |
|
||
| `/v1/payment/getApplePayActivities` | GET | 获取 iOS 商品列表 |
|
||
| `/v1/payment/get-payment-methods` | POST | 获取支付方式列表 |
|
||
| `/v1/payment/createPayment` | POST | 创建支付订单 |
|
||
| `/v1/payment/getOrderDetail` | GET | 查询订单状态(轮询)|
|
||
| `/v1/payment/googlepay` | POST | Google Pay 回调验证 |
|
||
|
||
---
|
||
|
||
## 10. 代码文件位置
|
||
|
||
| 功能 | 文件路径 |
|
||
|------|----------|
|
||
| 充值页面 | `lib/features/recharge/recharge_screen.dart` |
|
||
| 支付 API | `lib/core/api/services/payment_api.dart` |
|
||
| Google Play 内购服务 | `lib/features/recharge/google_play_purchase_service.dart` |
|
||
| 支付方式模型 | `lib/features/recharge/models/payment_method_item.dart` |
|
||
| 商品模型 | `lib/features/recharge/models/activity_item.dart` |
|
||
| 购买结果模型 | `lib/features/recharge/models/google_pay_purchase_result.dart` |
|
||
| WebView 支付页 | `lib/features/recharge/payment_webview_screen.dart` |
|
||
|
||
---
|
||
|
||
## 11. 常见问题
|
||
|
||
### 11.1 商品未找到
|
||
|
||
- 原因: 客户端 `helm` (productId) 与 Google Play 后台「产品 ID」不一致
|
||
- 排查: 检查 `docs/google_pay_product_not_found.md`
|
||
|
||
### 11.2 补单
|
||
|
||
- 未确认订单可能不会出现在 `queryPastPurchases` 中
|
||
- 应用启动时订阅 `purchaseStream` 接收重新下发
|
||
- 补单会合并两者的待处理订单
|
||
|
||
---
|
||
|
||
## 12. 注意事项
|
||
|
||
- 所有 Google Play 内购统一使用 `launchPurchaseAndReturnData()` 方法
|
||
- 回调验证成功后必须调用 `completePurchase` + `consumePurchase`
|
||
- 支付 URL 打开方式取决于 `createPayment` 返回的 `convert` 字段
|
||
- 订单状态轮询: 间隔 1/3/7/15/31/63 秒
|