client_framework/docs/payment_flow.md

299 lines
7.8 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.

# 支付流程文档
## 1. 概述
本文档描述 Android 项目Flutter的完整支付流程包括商品获取、支付方式选择、订单创建、Google Play 内购以及补单机制。
---
## 2. 支付流程总览
```
用户点击 Buy
├─ enableThirdPartyPayment === true 且已登录
│ │
│ ├─ getPaymentMethods(activityId) 获取支付方式
│ ├─ 弹窗选择支付方式
│ ├─ createPayment 创建订单
│ │
│ ├─ 若选中的是 Google Pay
│ │ ├─ 调起 Google Play 内购
│ │ ├─ 拿到 purchaseData + signature
│ │ └─ googlepay 回调验证
│ │
│ └─ 否则(其他支付方式)
│ └─ 打开 payUrl 在外部浏览器完成支付
└─ enableThirdPartyPayment !== true 或未登录
└─ 仅 Android直接调起 Google Play 内购
```
---
## 3. 支付分支依据
| 条件 | 说明 |
|------|------|
| `UserState.enableThirdPartyPayment` | 登录后由 AuthService 从 `/v1/user/common_info` 响应写入 |
| `UserState.userId` | 用户登录后存储的用户 ID |
| **第三方支付** | `enableThirdPartyPayment == true``userId` 非空 |
| **直接谷歌支付** | 其他情况(未开第三方支付或未登录)|
---
## 4. 商品展示与获取
### 4.1 接口
```dart
// Androidapp 默认 backendAppTypeAndroid可传 client
final res = await PaymentApi.getGooglePayActivities(
country: '国家', // 可选
);
// iOSapp 默认 backendAppTypeIOS
final res = await PaymentApi.getApplePayActivities(
country: '国家', // 可选
);
```
### 4.2 返回实体
```dart
class PaymentProductsResponse {
List<PaymentProductItem>? productList;
}
class PaymentProductItem {
String? productId; // 商品 ID (对应 helm)
String? activityId; // 活动 ID (对应 warrior)
String? actualAmount; // 实际金额 (对应 guardian)
String? originAmount; // 原价 (对应 curriculum)
int? bonus; // 赠送积分 (对应 forge)
String? title; // 标题 (对应 glossary)
}
```
---
## 5. 第三方支付流程
### 5.1 获取支付方式
```dart
final res = await PaymentApi.getPaymentMethods(
activityId: 12345, // int
country: '国家', // 可选
);
if (res.isSuccess) {
final methods = res.data!.paymentMethods;
// methods 包含:
// - paymentMethod: 支付方式 (如 GOOGLEPAY)
// - subPaymentMethod: 子支付方式
// - name: 显示名称
// - icon: 图标 URL
// - recommend: 是否推荐
}
```
### 5.2 创建订单
```dart
final res = await PaymentApi.createPayment(
app: 'HAndroid', // HIOS / HAndroid
userId: '用户ID',
activityId: '活动ID',
paymentMethod: '支付方式',
paymentType: '支付子类型', // 可选
);
if (res.isSuccess) {
final order = res.data!;
final orderId = order.orderId; // 订单 ID
final payUrl = order.payUrl; // 支付链接
}
```
### 5.3 支付分支
- **Google Pay**: 调起内购 → 获取 purchaseData/signature → 调用 `googlepay` 回调
- **其他方式**: 打开 `payUrl` 在外部浏览器
---
## 6. 直接谷歌支付流程
仅 Android且不经过 `getPaymentMethods``createPayment`(三方支付关闭时):
```dart
// 1. 创建订单(直接走谷歌支付)
final createRes = await PaymentApi.createPayment(
app: 'HAndroid',
userId: '用户ID',
activityId: '活动ID',
paymentMethod: 'GooglePay',
paymentType: 'GooglePay',
);
// 2. 调起 Google Play 内购
final purchaseResult = await GooglePlayPurchaseService.launchPurchaseAndReturnData(
productId: '商品ID',
);
// 3. 回调验证
if (purchaseResult != null) {
final res = await PaymentApi.googlepay(
signature: purchaseResult.payload.signature,
purchaseData: purchaseResult.payload.purchaseData,
orderId: orderId ?? purchaseResult.orderId,
userId: userId,
);
}
```
---
## 7. Google Play 内购统一入口
### 7.1 调起内购
```dart
final result = await GooglePlayPurchaseService.launchPurchaseAndReturnData(
productId: '商品ID',
);
if (result != null) {
// result.orderId: Google 订单号
// result.payload.purchaseData: 用于 merchant
// result.payload.signature: 用于 signature
// result.purchaseDetails: PurchaseDetails 对象
}
```
### 7.2 回调验证
```dart
final res = await PaymentApi.googlepay(
signature: result.payload.signature,
purchaseData: result.payload.purchaseData,
orderId: orderId,
userId: userId,
);
if (res.isSuccess) {
// 核销订单
await GooglePlayPurchaseService.completeAndConsumePurchase(
result.purchaseDetails,
);
}
```
### 7.3 响应实体
```dart
class GooglePayCallbackResponse {
String? orderId;
String? status; // SUCCESS / FAILED
bool? creditsAdded; // 是否已加积分
}
```
---
## 8. 补单机制
### 8.1 触发时机
- 进入充值页时调用 `GooglePlayPurchaseService.runOrderRecovery()`
### 8.2 补单流程
1. 获取未核销订单: `getUnacknowledgedPurchases()`
- 合并 `queryPastPurchases``purchaseStream` 的待处理订单
2. 对每笔订单:
- 查询本地存储的 `federation` 映射
- 若存在 federation: 调用 `googlepay` 回调 → 成功后 consume
- 若无 federation: 仅执行 consume 解除「已拥有此内容」
3. 补单成功后刷新账户
---
## 9. API 汇总
| 接口 | 方法 | 返回实体 |
|------|------|----------|
| `getGooglePayActivities` | GET | `PaymentProductsResponse` |
| `getApplePayActivities` | GET | `PaymentProductsResponse` |
| `getPaymentMethods` | POST | `PaymentMethodsResponse` |
| `createPayment` | POST | `CreatePaymentResponse` |
| `getPaymentDetailList` | GET | `PaymentOrderListResponse` |
| `getOrderDetail` | GET | `OrderDetailResponse` |
| `googlepay` | POST | `GooglePayCallbackResponse` |
---
## 10. 字段映射说明
框架自动完成字段映射,调用层使用原始字段名。
### 请求 → 响应 字段对照
| 业务含义 | 请求字段 | 响应字段 |
|----------|----------|----------|
| 应用 ID | app | - |
| 用户 ID | userId | - |
| 活动 ID | activityId | - |
| 支付方式 | paymentMethod | - |
| 支付子类型 | paymentType | - |
| 订单 / 支付 ID详情 query | `id`Dart 仍用参数名 orderId | orderId |
| 购买签名 | signature | - |
| 购买数据 | purchaseData | - |
| 商品 ID | - | productId |
| 实际金额 | - | actualAmount |
| 原价 | - | originAmount |
| 赠送积分 | - | bonus |
| 支付链接 | - | payUrl |
| 订单状态 | - | status |
---
## 11. 代码文件位置
| 功能 | 文件路径 |
|------|----------|
| 充值页面 | `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` |
---
## 12. 常见问题
### 12.1 商品未找到
- 原因: 客户端 `productId` 与 Google Play 后台「产品 ID」不一致
- 排查: 检查 Play 后台产品 ID 配置
### 12.2 补单
- 未确认订单可能不会出现在 `queryPastPurchases`
- 应用启动时订阅 `purchaseStream` 接收重新下发
- 补单会合并两者的待处理订单
---
## 13. 注意事项
- 所有 Google Play 内购统一使用 `launchPurchaseAndReturnData()` 方法
- 回调验证成功后必须调用 `completePurchase` + `consumePurchase`
- 支付 URL 打开方式取决于 `createPayment` 返回的 `payUrl` 字段
- 订单状态轮询: 间隔 1/3/7/15/31/63 秒