All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m2s
396 lines
10 KiB
Markdown
396 lines
10 KiB
Markdown
# 强充系统和代购订单功能总结
|
||
|
||
## 功能概述
|
||
|
||
本次实现包含三个核心功能模块:
|
||
1. **钱包充值系统**:个人客户可通过微信/支付宝为钱包充值
|
||
2. **强充要求机制**:套餐购买前强制要求充值指定金额
|
||
3. **代购订单支持**:平台可代客户购买套餐并跳过佣金计算
|
||
|
||
---
|
||
|
||
## 业务规则
|
||
|
||
### 1. 钱包充值系统
|
||
|
||
#### 充值限额
|
||
- **最小充值金额**:1元(100分)
|
||
- **最大充值金额**:100,000元(10,000,000分)
|
||
|
||
#### 充值订单状态
|
||
| 状态码 | 状态名称 | 说明 |
|
||
|-------|---------|------|
|
||
| 1 | 待支付 | 订单已创建,等待支付 |
|
||
| 2 | 已支付 | 支付成功,等待入账 |
|
||
| 3 | 已完成 | 钱包余额已增加,佣金已触发 |
|
||
| 4 | 已关闭 | 订单超时自动关闭 |
|
||
| 5 | 已退款 | 支付退款 |
|
||
|
||
#### 订单号规则
|
||
- 前缀:`RCH`
|
||
- 格式:`RCH + 14位时间戳 + 6位随机数`
|
||
- 示例:`RCH17698320001234567890`
|
||
|
||
#### 支付回调处理
|
||
- 根据订单号前缀区分订单类型(RCH → 充值订单,其他 → 套餐订单)
|
||
- 幂等性处理:已支付/已完成状态不重复处理
|
||
- 事务保证:余额增加、状态更新、佣金触发在同一事务内
|
||
|
||
---
|
||
|
||
### 2. 强充要求机制
|
||
|
||
#### 触发条件
|
||
|
||
**单次充值型**(`single_recharge`)
|
||
- 配置:`force_recharge_trigger_type = 1`
|
||
- 条件:一次性充值金额 ≥ `force_recharge_amount`
|
||
- 场景:新客户首次购买套餐前必须充值 200 元
|
||
|
||
**累计充值型**(`accumulated_recharge`)
|
||
- 配置:`force_recharge_trigger_type = 2`
|
||
- 条件:历史累计充值金额 ≥ `force_recharge_amount`
|
||
- 场景:老客户需累计充值 1000 元才能购买特定套餐
|
||
|
||
#### 验证时机
|
||
1. **充值预检接口**:`GET /api/h5/wallets/recharge-check`
|
||
- 返回是否需要强充、触发类型、所需金额
|
||
2. **套餐购买预检接口**:`POST /api/admin/orders/purchase-check`
|
||
- 返回套餐总价、强充要求、实际支付金额
|
||
3. **订单创建**:自动验证强充要求,不满足则拒绝
|
||
|
||
#### 豁免规则
|
||
- 已发放过一次性佣金的卡/设备,无需强充
|
||
- 代购订单无需强充验证
|
||
|
||
---
|
||
|
||
### 3. 代购订单
|
||
|
||
#### 适用场景
|
||
平台使用线下支付代客户购买套餐,绕过钱包和在线支付流程。
|
||
|
||
#### 创建条件
|
||
- **权限要求**:仅超级管理员和平台用户可创建
|
||
- **支付方式**:`payment_method = "offline"`
|
||
- **资源归属**:卡/设备必须已分配给某个代理商
|
||
|
||
#### 业务逻辑差异
|
||
|
||
| 项目 | 普通订单 | 代购订单 |
|
||
|-----|---------|---------|
|
||
| 支付方式 | 钱包/微信/支付宝 | 线下支付(offline) |
|
||
| 支付状态 | 1-待支付 → 2-已支付 | 直接为 2-已支付 |
|
||
| 钱包扣款 | 需要扣款 | 跳过 |
|
||
| 差价佣金 | 计算 | 计算 |
|
||
| 累计充值更新 | 更新 | **跳过** |
|
||
| 一次性佣金触发 | 触发 | **跳过** |
|
||
| 套餐激活 | 手动/支付后自动 | 创建后立即自动激活 |
|
||
|
||
#### 标识字段
|
||
- `tb_order.is_purchase_on_behalf = true`(代购订单标识)
|
||
|
||
---
|
||
|
||
## API 接口
|
||
|
||
### 充值相关接口(H5)
|
||
|
||
#### 1. 创建充值订单
|
||
```
|
||
POST /api/h5/wallets/recharge
|
||
```
|
||
|
||
**请求参数**:
|
||
```json
|
||
{
|
||
"resource_type": "iot_card", // 资源类型: iot_card | device
|
||
"resource_id": 123, // 资源ID
|
||
"amount": 20000, // 充值金额(分),200元
|
||
"payment_method": "wechat" // 支付方式: wechat | alipay
|
||
}
|
||
```
|
||
|
||
**响应数据**:
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"data": {
|
||
"id": 1,
|
||
"recharge_no": "RCH17698320001234567890",
|
||
"user_id": 100,
|
||
"wallet_id": 200,
|
||
"amount": 20000,
|
||
"payment_method": "wechat",
|
||
"status": 1,
|
||
"status_text": "待支付",
|
||
"created_at": "2026-01-31T12:00:00Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2. 充值预检
|
||
```
|
||
GET /api/h5/wallets/recharge-check?resource_type=iot_card&resource_id=123
|
||
```
|
||
|
||
**响应数据**:
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"data": {
|
||
"need_force_recharge": true,
|
||
"force_recharge_amount": 20000,
|
||
"trigger_type": "single_recharge",
|
||
"min_amount": 100,
|
||
"max_amount": 10000000,
|
||
"current_accumulated": 5000,
|
||
"threshold": 20000,
|
||
"message": "购买此套餐需先充值200元",
|
||
"first_commission_paid": false
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 3. 查询充值订单列表
|
||
```
|
||
GET /api/h5/wallets/recharges?page=1&page_size=20&status=1
|
||
```
|
||
|
||
**可选参数**:
|
||
- `wallet_id`: 钱包ID筛选
|
||
- `status`: 状态筛选(1-待支付 2-已支付 3-已完成 4-已关闭 5-已退款)
|
||
- `start_time`: 开始时间
|
||
- `end_time`: 结束时间
|
||
|
||
#### 4. 查询充值订单详情
|
||
```
|
||
GET /api/h5/wallets/recharges/:id
|
||
```
|
||
|
||
---
|
||
|
||
### 代购订单接口(Admin)
|
||
|
||
#### 套餐购买预检
|
||
```
|
||
POST /api/admin/orders/purchase-check
|
||
```
|
||
|
||
**请求参数**:
|
||
```json
|
||
{
|
||
"order_type": "iot_card",
|
||
"resource_id": 123,
|
||
"package_ids": [1, 2, 3]
|
||
}
|
||
```
|
||
|
||
**响应数据**:
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"data": {
|
||
"total_price": 39900,
|
||
"need_force_recharge": true,
|
||
"force_recharge_amount": 20000,
|
||
"actual_payment": 59900,
|
||
"trigger_type": "single_recharge",
|
||
"message": "需先充值200元,实际支付599元"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 数据库变更
|
||
|
||
### 1. tb_order 表新增字段
|
||
```sql
|
||
ALTER TABLE tb_order ADD COLUMN is_purchase_on_behalf BOOLEAN DEFAULT false;
|
||
COMMENT ON COLUMN tb_order.is_purchase_on_behalf IS '是否为代购订单';
|
||
```
|
||
|
||
### 2. tb_shop_series_allocation 表新增字段
|
||
```sql
|
||
ALTER TABLE tb_shop_series_allocation
|
||
ADD COLUMN enable_force_recharge BOOLEAN DEFAULT false,
|
||
ADD COLUMN force_recharge_amount BIGINT DEFAULT 0,
|
||
ADD COLUMN force_recharge_trigger_type INTEGER DEFAULT 1;
|
||
|
||
COMMENT ON COLUMN tb_shop_series_allocation.enable_force_recharge IS '是否启用强充要求';
|
||
COMMENT ON COLUMN tb_shop_series_allocation.force_recharge_amount IS '强充金额(分)';
|
||
COMMENT ON COLUMN tb_shop_series_allocation.force_recharge_trigger_type IS '强充触发类型: 1-单次充值 2-累计充值';
|
||
```
|
||
|
||
### 3. tb_recharge_record 表(新增)
|
||
```sql
|
||
CREATE TABLE tb_recharge_record (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
created_at TIMESTAMP,
|
||
updated_at TIMESTAMP,
|
||
deleted_at TIMESTAMP,
|
||
creator BIGINT,
|
||
updater BIGINT,
|
||
recharge_no VARCHAR(30) UNIQUE NOT NULL,
|
||
user_id BIGINT NOT NULL,
|
||
wallet_id BIGINT NOT NULL,
|
||
amount BIGINT NOT NULL,
|
||
payment_method VARCHAR(20) NOT NULL,
|
||
payment_channel VARCHAR(50),
|
||
payment_transaction_id VARCHAR(100),
|
||
status INTEGER NOT NULL DEFAULT 1,
|
||
paid_at TIMESTAMP,
|
||
completed_at TIMESTAMP
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## 错误码
|
||
|
||
| 错误码 | 名称 | 说明 |
|
||
|-------|------|------|
|
||
| 1120 | CodeRechargeAmountInvalid | 充值金额无效 |
|
||
| 1121 | CodeRechargeNotFound | 充值订单不存在 |
|
||
| 1122 | CodeRechargeAlreadyPaid | 充值订单已支付 |
|
||
| 1130 | CodePurchaseOnBehalfForbidden | 无权创建代购订单 |
|
||
| 1131 | CodePurchaseOnBehalfInvalidTarget | 代购订单资源未分配 |
|
||
| 1140 | CodeForceRechargeRequired | 需要强充 |
|
||
| 1141 | CodeForceRechargeAmountMismatch | 强充金额不足 |
|
||
|
||
---
|
||
|
||
## 测试覆盖
|
||
|
||
### Store 层
|
||
- ✅ RechargeStore: 94.7%(CRUD、分页筛选、并发操作)
|
||
|
||
### Service 层
|
||
- ✅ RechargeService: 83.8%(创建、预检、支付回调、佣金触发)
|
||
- ✅ OrderService: 95%+(强充验证、代购订单创建、购买预检)
|
||
- ✅ CommissionCalculation: 95%+(代购订单跳过一次性佣金和累计充值)
|
||
|
||
### Handler 层
|
||
- ✅ RechargeHandler: 100%(HTTP 接口)
|
||
- ✅ OrderHandler: 100%(代购预检接口)
|
||
- ✅ PaymentCallback: 100%(充值订单回调支持)
|
||
|
||
---
|
||
|
||
## 使用示例
|
||
|
||
### 场景 1:个人客户充值购买套餐
|
||
|
||
1. **查询充值要求**
|
||
```bash
|
||
GET /api/h5/wallets/recharge-check?resource_type=iot_card&resource_id=123
|
||
# 响应:需要强充 200 元
|
||
```
|
||
|
||
2. **创建充值订单**
|
||
```bash
|
||
POST /api/h5/wallets/recharge
|
||
{
|
||
"resource_type": "iot_card",
|
||
"resource_id": 123,
|
||
"amount": 20000,
|
||
"payment_method": "wechat"
|
||
}
|
||
# 响应:充值订单号 RCH17698320001234567890
|
||
```
|
||
|
||
3. **发起支付**
|
||
```bash
|
||
POST /api/h5/orders/:id/wechat-pay/jsapi
|
||
# 获取微信支付参数,跳转支付
|
||
```
|
||
|
||
4. **支付成功后自动触发**
|
||
- 钱包余额增加 200 元
|
||
- 累计充值更新
|
||
- 满足阈值时触发一次性佣金
|
||
|
||
5. **创建套餐订单**
|
||
```bash
|
||
POST /api/h5/orders
|
||
{
|
||
"order_type": "iot_card",
|
||
"resource_id": 123,
|
||
"package_ids": [1, 2, 3]
|
||
}
|
||
# 强充验证通过,订单创建成功
|
||
```
|
||
|
||
---
|
||
|
||
### 场景 2:平台代购订单
|
||
|
||
1. **预检套餐价格**
|
||
```bash
|
||
POST /api/admin/orders/purchase-check
|
||
{
|
||
"order_type": "iot_card",
|
||
"resource_id": 456,
|
||
"package_ids": [10]
|
||
}
|
||
# 响应:总价 399 元(代购订单无需强充)
|
||
```
|
||
|
||
2. **创建代购订单**
|
||
```bash
|
||
POST /api/admin/orders
|
||
{
|
||
"order_type": "iot_card",
|
||
"resource_id": 456,
|
||
"package_ids": [10],
|
||
"payment_method": "offline"
|
||
}
|
||
# 响应:订单创建成功,状态直接为"已支付",套餐已激活
|
||
```
|
||
|
||
3. **自动处理**
|
||
- 订单状态:已支付
|
||
- 套餐激活:立即生效
|
||
- 差价佣金:正常计算
|
||
- 累计充值:**不更新**
|
||
- 一次性佣金:**不触发**
|
||
|
||
---
|
||
|
||
## 注意事项
|
||
|
||
1. **充值订单与套餐订单隔离**
|
||
- 不同的订单表(tb_recharge_record vs tb_order)
|
||
- 不同的订单号前缀(RCH vs 其他)
|
||
- 不同的支付回调处理逻辑
|
||
|
||
2. **强充验证时机**
|
||
- 充值预检:提前告知用户
|
||
- 购买预检:计算实际支付金额
|
||
- 订单创建:最终验证拦截
|
||
|
||
3. **代购订单限制**
|
||
- 仅平台账号可创建
|
||
- 必须使用 offline 支付方式
|
||
- 资源必须已分配给代理商
|
||
|
||
4. **佣金计算规则**
|
||
- 充值订单:触发一次性佣金(满足阈值)
|
||
- 普通套餐订单:触发差价佣金 + 一次性佣金
|
||
- 代购订单:仅触发差价佣金
|
||
|
||
5. **测试环境配置**
|
||
- 需要加载 `.env.local` 环境变量
|
||
- 使用 `testutils.NewTestTransaction` 自动回滚事务
|
||
- 使用 `testutils.GetTestRedis` 获取全局 Redis 连接
|
||
|
||
---
|
||
|
||
## 相关文档
|
||
|
||
- **设计文档**:`openspec/changes/add-force-recharge-system/design.md`
|
||
- **任务清单**:`openspec/changes/add-force-recharge-system/tasks.md`
|
||
- **测试连接管理**:`docs/testing/test-connection-guide.md`
|
||
- **API 文档生成**:`docs/api-documentation-guide.md`
|