All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m2s
10 KiB
10 KiB
强充系统和代购订单功能总结
功能概述
本次实现包含三个核心功能模块:
- 钱包充值系统:个人客户可通过微信/支付宝为钱包充值
- 强充要求机制:套餐购买前强制要求充值指定金额
- 代购订单支持:平台可代客户购买套餐并跳过佣金计算
业务规则
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 元才能购买特定套餐
验证时机
- 充值预检接口:
GET /api/h5/wallets/recharge-check- 返回是否需要强充、触发类型、所需金额
- 套餐购买预检接口:
POST /api/admin/orders/purchase-check- 返回套餐总价、强充要求、实际支付金额
- 订单创建:自动验证强充要求,不满足则拒绝
豁免规则
- 已发放过一次性佣金的卡/设备,无需强充
- 代购订单无需强充验证
3. 代购订单
适用场景
平台使用线下支付代客户购买套餐,绕过钱包和在线支付流程。
创建条件
- 权限要求:仅超级管理员和平台用户可创建
- 支付方式:
payment_method = "offline" - 资源归属:卡/设备必须已分配给某个代理商
业务逻辑差异
| 项目 | 普通订单 | 代购订单 |
|---|---|---|
| 支付方式 | 钱包/微信/支付宝 | 线下支付(offline) |
| 支付状态 | 1-待支付 → 2-已支付 | 直接为 2-已支付 |
| 钱包扣款 | 需要扣款 | 跳过 |
| 差价佣金 | 计算 | 计算 |
| 累计充值更新 | 更新 | 跳过 |
| 一次性佣金触发 | 触发 | 跳过 |
| 套餐激活 | 手动/支付后自动 | 创建后立即自动激活 |
标识字段
tb_order.is_purchase_on_behalf = true(代购订单标识)
API 接口
充值相关接口(H5)
1. 创建充值订单
POST /api/h5/wallets/recharge
请求参数:
{
"resource_type": "iot_card", // 资源类型: iot_card | device
"resource_id": 123, // 资源ID
"amount": 20000, // 充值金额(分),200元
"payment_method": "wechat" // 支付方式: wechat | alipay
}
响应数据:
{
"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
响应数据:
{
"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
请求参数:
{
"order_type": "iot_card",
"resource_id": 123,
"package_ids": [1, 2, 3]
}
响应数据:
{
"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 表新增字段
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 表新增字段
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 表(新增)
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:个人客户充值购买套餐
- 查询充值要求
GET /api/h5/wallets/recharge-check?resource_type=iot_card&resource_id=123
# 响应:需要强充 200 元
- 创建充值订单
POST /api/h5/wallets/recharge
{
"resource_type": "iot_card",
"resource_id": 123,
"amount": 20000,
"payment_method": "wechat"
}
# 响应:充值订单号 RCH17698320001234567890
- 发起支付
POST /api/h5/orders/:id/wechat-pay/jsapi
# 获取微信支付参数,跳转支付
- 支付成功后自动触发
- 钱包余额增加 200 元
- 累计充值更新
- 满足阈值时触发一次性佣金
- 创建套餐订单
POST /api/h5/orders
{
"order_type": "iot_card",
"resource_id": 123,
"package_ids": [1, 2, 3]
}
# 强充验证通过,订单创建成功
场景 2:平台代购订单
- 预检套餐价格
POST /api/admin/orders/purchase-check
{
"order_type": "iot_card",
"resource_id": 456,
"package_ids": [10]
}
# 响应:总价 399 元(代购订单无需强充)
- 创建代购订单
POST /api/admin/orders
{
"order_type": "iot_card",
"resource_id": 456,
"package_ids": [10],
"payment_method": "offline"
}
# 响应:订单创建成功,状态直接为"已支付",套餐已激活
- 自动处理
- 订单状态:已支付
- 套餐激活:立即生效
- 差价佣金:正常计算
- 累计充值:不更新
- 一次性佣金:不触发
注意事项
-
充值订单与套餐订单隔离
- 不同的订单表(tb_recharge_record vs tb_order)
- 不同的订单号前缀(RCH vs 其他)
- 不同的支付回调处理逻辑
-
强充验证时机
- 充值预检:提前告知用户
- 购买预检:计算实际支付金额
- 订单创建:最终验证拦截
-
代购订单限制
- 仅平台账号可创建
- 必须使用 offline 支付方式
- 资源必须已分配给代理商
-
佣金计算规则
- 充值订单:触发一次性佣金(满足阈值)
- 普通套餐订单:触发差价佣金 + 一次性佣金
- 代购订单:仅触发差价佣金
-
测试环境配置
- 需要加载
.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