Files
junhong_cmp_fiber/openspec/changes/add-order-payment/design.md
huang 79c061b6fa
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m24s
feat: 实现套餐管理模块,包含套餐系列、双状态管理、废弃模型清理
- 新增套餐系列管理 (CRUD + 状态切换)
- 新增套餐管理 (CRUD + 启用/禁用 + 上架/下架双状态)
- 清理 8 个废弃分佣模型及对应数据库表
- Package 模型新增建议成本价、建议售价、上架状态字段
- 完整的 Store/Service/Handler 三层实现
- 包含单元测试和集成测试
- 归档 add-package-module change
- 新增多个 OpenSpec changes (订单支付、店铺套餐分配、一次性分佣、卡设备系列绑定)
2026-01-27 19:55:47 +08:00

238 lines
6.7 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.
## Context
Phase 3 完成了卡/设备的套餐系列关联,现在需要实现订单和支付流程。核心是"强充"机制:用户必须通过购买套餐来充值,不能直接给钱包充值。这确保每笔资金流入都有对应的套餐购买记录。
**三类买家**
1. 个人客户:通过 H5/小程序购买,使用卡/设备钱包或第三方支付
2. 代理商:通过后台购买,使用店铺钱包
3. 企业客户:后台直接分配套餐,不走订单流程(本期不做)
## Goals / Non-Goals
**Goals:**
- 设计订单和订单明细模型
- 实现套餐购买订单创建流程
- 实现钱包支付和第三方支付回调
- 验证购买权限(卡/设备的套餐系列关联)
- 套餐生效后更新流量额度
**Non-Goals:**
- 不实现企业客户的套餐分配(后台直接操作)
- 不实现第三方支付发起(仅处理回调)
- 不实现佣金计算Phase 5
- 不实现退款流程
## Decisions
### 1. 订单模型设计
**决策**Order + OrderItem 两级结构
```go
// Order 订单模型
type Order struct {
gorm.Model
BaseModel
OrderNo string // 订单号(唯一)
OrderType string // 订单类型: single_card-单卡购买 device-设备购买
BuyerType string // 买家类型: personal-个人客户 agent-代理商
BuyerID uint // 买家ID个人客户ID或店铺ID
IotCardID uint // IoT卡ID单卡购买时
DeviceID uint // 设备ID设备购买时
TotalAmount int64 // 订单总金额(分)
PaymentMethod string // 支付方式: wallet-钱包 wechat-微信 alipay-支付宝
PaymentStatus int // 支付状态: 1-待支付 2-已支付 3-已取消 4-已退款
PaidAt *time.Time // 支付时间
CommissionStatus int // 佣金状态: 1-待计算 2-已计算
}
// OrderItem 订单明细模型
type OrderItem struct {
gorm.Model
BaseModel
OrderID uint // 订单ID
PackageID uint // 套餐ID
PackageName string // 套餐名称(快照)
Quantity int // 数量通常为1
UnitPrice int64 // 单价(分)
Amount int64 // 小计(分)
}
```
**理由**
- 支持一个订单购买多个套餐(虽然初期可能只买一个)
- 快照套餐名称,避免套餐修改影响历史订单
- 佣金状态用于 Phase 5 的异步佣金计算
### 2. 订单号生成规则
**决策**:时间戳 + 随机数
```
格式ORD{YYYYMMDDHHMMSS}{6位随机数}
示例ORD20260127143052123456
```
**理由**
- 可读性好,包含时间信息
- 随机数避免并发冲突
- 长度固定,便于存储和展示
### 3. 购买价格确定
**决策**:使用 Package.suggested_retail_price 作为统一售价
```
个人客户购买:支付金额 = Package.suggested_retail_price
代理为店铺购买:支付金额 = 代理的成本价(用于囤货/测试)
```
**理由**
- 简化首期实现,所有终端用户统一售价
- 代理的利润 = suggested_retail_price - 成本价
- 后续如需支持代理自定义售价,可扩展 ShopPackageAllocation 增加 retail_price 字段
**非首期功能**
- 代理自定义售价
- 促销折扣价
---
### 4. 购买权限验证
**决策**:多层验证
```go
func ValidatePurchase(card/device, packageID) error {
// 1. 获取卡/设备的 series_allocation_id
allocationID := card.SeriesAllocationID
if allocationID == 0 {
return "该卡未关联套餐系列"
}
// 2. 获取套餐信息
pkg := GetPackage(packageID)
// 3. 验证套餐属于该系列
allocation := GetAllocation(allocationID)
if pkg.SeriesID != allocation.SeriesID {
return "该套餐不在可购买范围内"
}
// 4. 验证套餐状态
if pkg.Status != 1 || pkg.ShelfStatus != 1 {
return "该套餐已下架"
}
return nil
}
```
### 5. 支付流程
**决策**:同步钱包支付 + 异步第三方支付
```
钱包支付流程:
1. 创建订单(待支付)
2. 检查钱包余额
3. 扣减钱包余额(事务)
4. 更新订单状态(已支付)
5. 套餐生效
6. 触发佣金计算(异步)
第三方支付流程:
1. 创建订单(待支付)
2. 返回订单信息,前端发起支付
3. 支付回调更新订单状态
4. 套餐生效
5. 触发佣金计算(异步)
```
### 6. 套餐生效逻辑
**决策**:创建 PackageUsage 记录
```go
func ActivatePackage(order *Order) {
for _, item := range order.Items {
pkg := GetPackage(item.PackageID)
usage := &PackageUsage{
OrderID: order.ID,
PackageID: item.PackageID,
UsageType: order.OrderType, // single_card 或 device
IotCardID: order.IotCardID,
DeviceID: order.DeviceID,
DataLimitMB: pkg.DataAmountMB,
ActivatedAt: time.Now(),
ExpiresAt: time.Now().AddDate(0, pkg.DurationMonths, 0),
Status: 1, // 生效中
}
CreatePackageUsage(usage)
}
}
```
### 7. API 设计
```
# 订单管理(后台)
POST /api/admin/orders 代理创建订单
GET /api/admin/orders 订单列表
GET /api/admin/orders/:id 订单详情
POST /api/admin/orders/:id/cancel 取消订单
# 订单操作H5/个人客户)
POST /api/h5/orders 个人客户创建订单
GET /api/h5/orders 我的订单列表
GET /api/h5/orders/:id 订单详情
POST /api/h5/orders/:id/pay 钱包支付
# 支付回调
POST /api/callback/wechat-pay 微信支付回调
POST /api/callback/alipay 支付宝回调
```
## Risks / Trade-offs
### 风险 1并发支付
**风险**:同一订单被重复支付
**缓解**
- 支付前检查订单状态
- 使用数据库乐观锁或 Redis 分布式锁
- 支付回调幂等处理
### 风险 2套餐生效失败
**风险**:支付成功但套餐生效失败
**缓解**
- 使用事务保证支付和套餐生效原子性
- 失败时自动退款或人工处理
- 记录详细日志便于排查
### 风险 3价格不一致
**风险**:下单时和支付时套餐价格变化
**缓解**
- 订单中存储下单时的价格快照
- 支付时使用订单金额,不重新查询套餐价格
## Open Questions
1. **订单超时取消?**
- 当前设计:不自动取消
- 待确认:是否需要定时任务取消超时未支付订单?
2. **部分支付?**
- 当前设计:不支持
- 待确认:是否需要支持钱包余额不足时组合支付?
3. **代理为终端用户购买?**
- 当前设计:代理只能为自己店铺购买
- 待确认:是否需要代理帮终端用户购买的场景?