299 lines
7.8 KiB
Markdown
299 lines
7.8 KiB
Markdown
# 支付流程文档
|
||
|
||
## 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
|
||
// Android(app 默认 backendAppTypeAndroid,可传 client)
|
||
final res = await PaymentApi.getGooglePayActivities(
|
||
country: '国家', // 可选
|
||
);
|
||
|
||
// iOS(app 默认 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 秒
|