Files
junhong_cmp_fiber/openspec/specs/iot-order/spec.md
huang 6e2dc325d7 新增钱包、换卡、标签系统的数据模型和规范
本次提交完成 add-wallet-transfer-tag-models 提案的实施和归档:

## 新增功能模块
- 钱包系统:用户/代理钱包管理,支持充值、扣款、退款、乐观锁防并发
- 换卡记录:物联卡更换历史追溯,包含套餐快照(JSONB)
- 标签系统:设备/IoT卡/号卡的统一标签管理
- 运营商渠道:四大运营商(CMCC/CUCC/CTCC/CBN)的渠道管理

## 数据库变更
- 新增 6 张表:tb_wallet, tb_wallet_transaction, tb_recharge_record, tb_card_replacement_record, tb_tag, tb_resource_tag
- 修改 2 张表:tb_carrier(新增渠道字段), tb_order(新增混合支付字段)
- 迁移版本:v6 → v7(执行时间 282.5ms)

## 代码变更
- 新增 8 个 Go 模型(符合统一规范:gorm.Model + BaseModel)
- 新增 40+ 个常量定义(含完整中文注释)
- 新增 7 个 Redis Key 生成函数
- 修复模型规范:移除重复字段,统一使用 gorm.Model 嵌入

## 文档变更
- 新增 3 个业务文档:数据模型设计、字段说明、迁移验证报告
- 更新 AGENTS.md:新增 Model 模型规范和常量注释规范
- 新增 4 个 OpenSpec 规范:wallet, carrier, card-replacement, tag
- 更新 1 个 OpenSpec 规范:iot-order(支持混合支付)

## 验证通过
-  LSP 诊断:所有模型和常量文件无错误
-  OpenSpec 验证:openspec validate --strict 通过
-  迁移执行:表结构创建成功,索引正确
-  提案归档:2026-01-13-add-wallet-transfer-tag-models

变更文件统计:29 个文件,新增 3682 行
2026-01-13 15:47:32 +08:00

276 lines
12 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.
# IoT Order Management
## Purpose
Manage orders for IoT card packages and number card products, including order creation, payment processing, status tracking, commission triggering, and support for single-card orders, device-level orders, and carrier number card orders.
This capability supports:
- Unified order entity for package orders and number card orders
- Order status lifecycle management
- Multiple payment methods (wallet, online payment, carrier direct payment)
- Commission triggering on order completion
- Device-level order commission (counted once regardless of bound card count)
- Multi-dimensional order querying and filtering
## Requirements
### Requirement: 订单实体定义
系统 SHALL 定义订单(Order)实体,统一管理两种订单类型:套餐订单、号卡订单,并支持混合支付方式(钱包 + 在线支付)。
**修改说明**
- 增加 `wallet_payment_amount` 字段:钱包支付金额
- 增加 `online_payment_amount` 字段:在线支付金额
- 支持用户在购买套餐时选择支付方式(全部钱包支付、全部在线支付、混合支付)
**实体字段**(只列出新增字段):
- `wallet_payment_amount`钱包支付金额BIGINT单位默认 0**【新增】**
- `online_payment_amount`在线支付金额BIGINT单位默认 0**【新增】**
**支付规则**
- `wallet_payment_amount` + `online_payment_amount` = `amount`(订单总金额)
-`payment_method` 为 "wallet" 时,`wallet_payment_amount` = `amount``online_payment_amount` = 0
-`payment_method` 为 "online" 时,`online_payment_amount` = `amount``wallet_payment_amount` = 0
- 混合支付时,`payment_method` 为 "mixed",两个字段都 > 0
#### Scenario: 全额钱包支付
- **WHEN** 用户购买套餐,订单金额为 30 00 分30 元),选择钱包支付,钱包余额为 10000 分
- **THEN** 系统创建订单,`amount` 为 3000`payment_method` 为 "wallet"`wallet_payment_amount` 为 3000`online_payment_amount` 为 0
#### Scenario: 全额在线支付
- **WHEN** 用户购买套餐,订单金额为 3000 分30 元),选择在线支付
- **THEN** 系统创建订单,`amount` 为 3000`payment_method` 为 "online"`wallet_payment_amount` 为 0`online_payment_amount` 为 3000
#### Scenario: 混合支付
- **WHEN** 用户购买套餐,订单金额为 5000 分50 元),钱包余额为 3000 分,用户选择钱包支付 3000 分 + 在线支付 2000 分
- **THEN** 系统创建订单,`amount` 为 5000`payment_method` 为 "mixed"`wallet_payment_amount` 为 3000`online_payment_amount` 为 2000
#### Scenario: 钱包余额不足,部分钱包支付
- **WHEN** 用户购买套餐,订单金额为 5000 分50 元),钱包余额为 2000 分,用户选择钱包支付 2000 分 + 在线支付 3000 分
- **THEN** 系统先冻结钱包余额 2000 分,创建订单,`wallet_payment_amount` 为 2000`online_payment_amount` 为 3000等待用户完成在线支付
#### Scenario: 钱包余额不足,无法全额钱包支付
- **WHEN** 用户购买套餐,订单金额为 5000 分50 元),钱包余额为 3000 分,用户选择钱包支付
- **THEN** 系统拒绝创建订单,返回错误信息"钱包余额不足",建议用户选择混合支付或在线支付
---
### Requirement: 订单状态流转
系统 SHALL 管理订单的状态流转,确保状态变更符合业务规则。
**状态定义**:
- **1-待支付**: 订单已创建,等待用户支付
- **2-已支付**: 用户已支付,等待系统处理
- **3-已完成**: 订单已完成(激活/发货等)
- **4-已取消**: 订单已取消
- **5-已退款**: 订单已退款
**状态流转规则**:
- 待支付(1) → 已支付(2): 用户完成支付
- 待支付(1) → 已取消(4): 用户取消订单或订单超时
- 已支付(2) → 已完成(3): 系统完成订单处理(激活/发货)
- 已支付(2) → 已退款(5): 用户申请退款且审核通过
- 已完成(3) → 已退款(5): 用户申请退款且审核通过(特殊情况)
#### Scenario: 用户支付订单
- **WHEN** 用户支付待支付订单(ID 为 10001),支付金额为 30.00 元
- **THEN** 系统将订单状态从 1(待支付) 变更为 2(已支付),`paid_at` 记录支付时间
#### Scenario: 单卡套餐订单完成
- **WHEN** 系统处理完单卡套餐订单(ID 为 10001),激活 IoT 卡并分配套餐
- **THEN** 系统将订单状态从 2(已支付) 变更为 3(已完成),`completed_at` 记录完成时间
#### Scenario: 设备级套餐订单完成
- **WHEN** 系统处理完设备级套餐订单(ID 为 10002),为设备绑定的所有 IoT 卡分配套餐
- **THEN** 系统将订单状态从 2(已支付) 变更为 3(已完成),`completed_at` 记录完成时间
---
### Requirement: 订单支付方式
系统 SHALL 支持三种支付方式:钱包支付、在线支付、运营商直付。
**支付方式**:
- **钱包支付(wallet)**: 从用户钱包余额扣款
- **在线支付(online)**: 通过第三方支付(微信/支付宝等)
- **运营商直付(carrier)**: 用户直接支付给运营商(仅号卡订单)
**支付规则**:
- 一次性分佣订单必须使用钱包支付
- 套餐购买订单可以使用钱包或在线支付
- 号卡订单必须使用运营商直付
#### Scenario: 钱包支付订单
- **WHEN** 用户使用钱包支付订单(金额为 30.00 元),钱包余额为 50.00 元
- **THEN** 系统从钱包扣除 30.00 元,订单状态变更为 2(已支付),`payment_method` 为 "wallet"
#### Scenario: 钱包余额不足
- **WHEN** 用户使用钱包支付订单(金额为 30.00 元),钱包余额为 20.00 元
- **THEN** 系统拒绝支付,返回错误信息"钱包余额不足"
#### Scenario: 一次性分佣订单强制钱包支付
- **WHEN** 用户购买配置了一次性分佣的套餐,尝试使用在线支付
- **THEN** 系统拒绝支付,返回错误信息"一次性分佣订单必须使用钱包支付"
---
### Requirement: 订单分佣触发
系统 SHALL 在订单完成时触发分佣计算,根据代理分佣规则创建分佣记录。
**触发条件**:
- 订单状态变更为 3(已完成)
- 订单有 `agent_id`(通过代理销售)
- 代理配置了分佣规则
**分佣计算规则**:
- **单卡套餐订单**: 根据 IoT 卡关联的代理分佣规则计算分佣
- **设备级套餐订单**: 分佣只计算一次(不按设备绑定的 IoT 卡数量倍增)
- **号卡订单**: 下单即冻结分佣,次月通过 Excel 导入解冻
#### Scenario: 单卡套餐购买订单触发分佣
- **WHEN** 代理(ID 为 123)的单卡套餐订单(ID 为 10001)完成,订单金额为 30.00 元,代理配置了 5.00 元一次性分佣
- **THEN** 系统创建分佣记录,`agent_id` 为 123,`order_id` 为 10001,`amount` 为 5.00,状态为 1(冻结)
#### Scenario: 设备级套餐订单触发分佣(只计算一次)
- **WHEN** 代理(ID 为 123)的设备级套餐订单(ID 为 10002)完成,设备绑定 3 张 IoT 卡,订单金额为 399.00 元,代理配置了 100.00 元长期分佣
- **THEN** 系统创建一条分佣记录,`agent_id` 为 123,`order_id` 为 10002,`amount` 为 100.00,状态为 1(冻结),不是 3 × 100.00
#### Scenario: 号卡订单触发分佣
- **WHEN** 代理(ID 为 123)的号卡订单(ID 为 10003)创建,订单金额为 30.00 元,代理配置了长期分佣
- **THEN** 系统创建分佣记录,`agent_id` 为 123,`order_id` 为 10003,状态为 1(冻结),等待次月通过 Excel 导入解冻
---
### Requirement: 订单查询和筛选
系统 SHALL 支持多维度查询和筛选订单。
**查询条件**:
- 订单编号(精确匹配)
- 订单类型(1-套餐订单 2-号卡订单)
- 订单状态(单选或多选)
- IoT 卡 ID(精确匹配)
- 设备 ID(精确匹配)
- 号卡 ID(精确匹配)
- 用户 ID(精确匹配)
- 代理 ID(精确匹配)
- 支付方式(单选或多选)
- 创建时间范围(开始时间 - 结束时间)
- 支付时间范围(开始时间 - 结束时间)
- 完成时间范围(开始时间 - 结束时间)
**分页**:
- 默认每页 20 条,最大每页 100 条
- 返回总记录数和总页数
#### Scenario: 查询用户的所有订单
- **WHEN** 用户(ID 为 2001)查询自己的所有订单
- **THEN** 系统返回 `user_id` 为 2001 的所有订单列表,按创建时间倒序排列
#### Scenario: 查询代理的订单
- **WHEN** 代理(ID 为 123)查询自己的订单,筛选已完成的套餐订单
- **THEN** 系统返回 `agent_id` 为 123 且 `order_type` 为 1 且 `status` 为 3(已完成) 的订单列表
#### Scenario: 查询 IoT 卡的订单历史
- **WHEN** 运营人员查询 IoT 卡(ID 为 1001)的所有订单
- **THEN** 系统返回 `iot_card_id` 为 1001 的所有订单列表,包含套餐购买记录
#### Scenario: 查询设备的订单历史
- **WHEN** 运营人员查询设备(ID 为 5001)的所有订单
- **THEN** 系统返回 `device_id` 为 5001 的所有设备级套餐订单列表
---
### Requirement: 订单数据校验
系统 SHALL 对订单数据进行校验,确保数据完整性和一致性,特别是支付金额的一致性。
**新增校验规则**
- `wallet_payment_amount`:必填,≥ 0最多精确到分
- `online_payment_amount`:必填,≥ 0最多精确到分
- `wallet_payment_amount` + `online_payment_amount` = `amount`(订单总金额)
-`payment_method` 为 "wallet" 时,`wallet_payment_amount` 必须 = `amount`
-`payment_method` 为 "online" 时,`online_payment_amount` 必须 = `amount`
-`payment_method` 为 "mixed" 时,两个字段都必须 > 0
#### Scenario: 支付金额不一致
- **WHEN** 创建订单,`amount` 为 5000`wallet_payment_amount` 为 2000`online_payment_amount` 为 2000
- **THEN** 系统拒绝创建,返回错误信息"支付金额总和与订单金额不一致"
#### Scenario: 钱包支付时在线支付金额不为 0
- **WHEN** 创建订单,`payment_method` 为 "wallet"`wallet_payment_amount` 为 3000`online_payment_amount` 为 0正确但用户错误地设置 `online_payment_amount` 为 100
- **THEN** 系统拒绝创建,返回错误信息"钱包支付时在线支付金额必须为 0"
#### Scenario: 混合支付时钱包支付金额为 0
- **WHEN** 创建订单,`payment_method` 为 "mixed"`wallet_payment_amount` 为 0`online_payment_amount` 为 5000
- **THEN** 系统拒绝创建,返回错误信息"混合支付时钱包支付金额和在线支付金额都必须大于 0"
### Requirement: 订单支付处理
系统 SHALL 根据支付方式正确处理订单支付,包括钱包扣款、在线支付、混合支付等。
**钱包支付流程**
1. 检查钱包可用余额是否充足
2. 冻结钱包余额(`frozen_balance` 增加)
3. 创建订单,状态为"待支付"
4. 订单完成后,扣减钱包余额(`balance` 减少,`frozen_balance` 减少),创建钱包明细记录
5. 订单取消时,解冻钱包余额(`frozen_balance` 减少)
**在线支付流程**
1. 创建订单,状态为"待支付"
2. 调用第三方支付接口
3. 用户完成支付后,订单状态变更为"已支付"
4. 订单完成后,订单状态变更为"已完成"
**混合支付流程**
1. 检查钱包可用余额是否充足(钱包支付部分)
2. 冻结钱包余额
3. 创建订单,状态为"待支付"
4. 调用第三方支付接口(在线支付部分)
5. 用户完成在线支付后,扣减钱包余额,订单状态变更为"已支付"
6. 订单完成后,订单状态变更为"已完成"
#### Scenario: 钱包支付订单完成
- **WHEN** 用户使用钱包支付购买套餐,订单金额为 3000 分
- **THEN** 系统:
1. 创建订单,状态为"待支付",冻结钱包余额 3000 分
2. 订单处理完成后,扣减钱包余额 3000 分,解冻 3000 分,创建钱包明细记录(类型为"扣款"),订单状态变更为"已完成"
#### Scenario: 混合支付订单完成
- **WHEN** 用户使用混合支付购买套餐,钱包支付 2000 分 + 在线支付 3000 分
- **THEN** 系统:
1. 创建订单,状态为"待支付",冻结钱包余额 2000 分
2. 用户完成在线支付 3000 分后,扣减钱包余额 2000 分,解冻 2000 分,创建钱包明细记录,订单状态变更为"已支付"
3. 订单处理完成后,订单状态变更为"已完成"
#### Scenario: 订单取消,解冻钱包余额
- **WHEN** 用户使用钱包支付创建订单,订单金额为 3000 分,然后取消订单
- **THEN** 系统解冻钱包余额 3000 分(`frozen_balance` 减少 3000订单状态变更为"已取消"
---