Files
junhong_cmp_fiber/openspec/specs/iot-order/spec.md
huang b9733c4913
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m12s
fix: 修正零售价架构错误 + 清理旧微信配置 + 归档提案 + 前端接口文档
1. 修正 retail_price 架构:
   - 删除 batch-pricing 接口的 pricing_target 字段和 retail_price 分支
     (上级只能改下级成本价,不能改零售价)
   - 新增 PATCH /api/admin/packages/:id/retail-price 接口
     (代理自己改自己的零售价,校验 retail_price >= cost_price)

2. 清理旧微信 YAML 配置(已全部迁移到数据库 tb_wechat_config):
   - 删除 config.yaml 中 wechat.official_account 配置节
   - 删除 NewOfficialAccountApp() 旧工厂函数
   - 清理 personal_customer service 中的死代码(旧登录/绑定微信方法)
   - 清理 docker-compose.prod.yml 中旧微信环境变量和证书挂载注释

3. 归档四个已完成提案到 openspec/changes/archive/

4. 新增前端接口变更说明文档(docs/前端接口变更说明.md)

5. 修正归档提案和 specs 中关于 pricing_target 的错误描述
2026-03-19 17:39:43 +08:00

313 lines
14 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): 用户手动取消订单或订单超时30 分钟)
- 已支付(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` 记录完成时间
#### Scenario: 用户手动取消订单
- **WHEN** 用户手动取消待支付订单(ID 为 10003)
- **THEN** 系统将订单状态从 1(待支付) 变更为 4(已取消),`expires_at` 设置为 NULL如有钱包预扣则解冻余额
#### Scenario: 订单超时自动取消
- **WHEN** 订单创建后 30 分钟未支付,定时任务扫描到该订单
- **THEN** 系统自动将订单状态从 1(待支付) 变更为 4(已取消),`expires_at` 设置为 NULL如有钱包预扣则解冻余额
#### Scenario: 订单超时自动取消(混合支付)
- **WHEN** 混合支付订单创建后 30 分钟未完成在线支付,钱包已预扣 2000 分
- **THEN** 系统自动取消订单,解冻钱包余额 2000 分
#### Scenario: 订单超时自动取消(纯在线支付)
- **WHEN** 纯在线支付订单创建后 30 分钟未支付
- **THEN** 系统自动取消订单,无需钱包解冻操作
---
### 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订单状态变更为"已取消"
---
### Requirement: 订单来源与代际字段
系统 SHALL 在订单Order实体新增来源与代际字段
- `source varchar(20) NOT NULL DEFAULT 'admin'`,取值 `admin/client`
- `generation int NOT NULL DEFAULT 1`
#### Scenario: 新建订单默认后台来源
- **WHEN** 系统创建订单且未显式指定来源
- **THEN** `source` MUST 默认为 `admin`
#### Scenario: 客户端下单写入客户端来源
- **WHEN** 客户端入口创建订单
- **THEN** `source` MUST 写入为 `client`
#### Scenario: 新建订单默认代际为 1
- **WHEN** 系统创建订单且未显式指定代际
- **THEN** `generation` MUST 默认为 `1`