新增钱包、换卡、标签系统的数据模型和规范
本次提交完成 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 行
This commit is contained in:
@@ -11,56 +11,50 @@ This capability supports:
|
||||
- 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)实体,统一管理两种订单类型:套餐订单、号卡订单。
|
||||
系统 SHALL 定义订单(Order)实体,统一管理两种订单类型:套餐订单、号卡订单,并支持混合支付方式(钱包 + 在线支付)。
|
||||
|
||||
**核心概念**:
|
||||
- **套餐订单**: 用户为 IoT 卡或设备购买套餐的订单,包括单卡套餐订单和设备级套餐订单
|
||||
- **号卡订单**: 运营商回传的号卡订单,用户直接在上游平台下单,系统只接收订单状态更新
|
||||
**修改说明**:
|
||||
- 增加 `wallet_payment_amount` 字段:钱包支付金额
|
||||
- 增加 `online_payment_amount` 字段:在线支付金额
|
||||
- 支持用户在购买套餐时选择支付方式(全部钱包支付、全部在线支付、混合支付)
|
||||
|
||||
**实体字段**:
|
||||
- `id`: 订单 ID(主键,BIGINT)
|
||||
- `order_no`: 订单编号(VARCHAR(50),唯一)
|
||||
- `order_type`: 订单类型(INT,1-套餐订单 2-号卡订单)
|
||||
- `iot_card_id`: IoT 卡 ID(BIGINT,可空,单卡套餐订单时有值)
|
||||
- `device_id`: 设备 ID(BIGINT,可空,设备级套餐订单时有值)
|
||||
- `number_card_id`: 号卡 ID(BIGINT,可空,号卡订单时有值)
|
||||
- `package_id`: 套餐 ID(BIGINT,可空,仅当 order_type 为 1 时有值)
|
||||
- `user_id`: 用户 ID(BIGINT,购买用户)
|
||||
- `agent_id`: 代理 ID(BIGINT,可空,通过代理购买时有值)
|
||||
- `amount`: 订单金额(DECIMAL(10,2),元)
|
||||
- `payment_method`: 支付方式(VARCHAR(20),"wallet"-钱包 | "online"-在线支付 | "carrier"-运营商直付)
|
||||
- `status`: 订单状态(INT,1-待支付 2-已支付 3-已完成 4-已取消 5-已退款)
|
||||
- `carrier_order_id`: 运营商订单 ID(VARCHAR(255),可空,仅号卡订单有值)
|
||||
- `carrier_order_data`: 运营商订单原始数据(JSONB,可空)
|
||||
- `paid_at`: 支付时间(TIMESTAMP,可空)
|
||||
- `completed_at`: 完成时间(TIMESTAMP,可空)
|
||||
- `created_at`: 创建时间(TIMESTAMP,自动填充)
|
||||
- `updated_at`: 更新时间(TIMESTAMP,自动填充)
|
||||
**实体字段**(只列出新增字段):
|
||||
- `wallet_payment_amount`:钱包支付金额(BIGINT,单位:分,默认 0)**【新增】**
|
||||
- `online_payment_amount`:在线支付金额(BIGINT,单位:分,默认 0)**【新增】**
|
||||
|
||||
**订单类型说明**:
|
||||
- **单卡套餐订单**: `order_type` 为 1,`iot_card_id` 有值,`device_id` 为 NULL
|
||||
- **设备级套餐订单**: `order_type` 为 1,`device_id` 有值,`iot_card_id` 为 NULL
|
||||
- **号卡订单**: `order_type` 为 2,`number_card_id` 有值,`iot_card_id` 和 `device_id` 为 NULL
|
||||
**支付规则**:
|
||||
- `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: 创建单卡套餐购买订单
|
||||
#### Scenario: 全额钱包支付
|
||||
|
||||
- **WHEN** 用户(ID 为 2001)为 IoT 卡(ID 为 1001)购买套餐(ID 为 3001),金额为 30.00 元
|
||||
- **THEN** 系统创建订单记录,`order_type` 为 1,`iot_card_id` 为 1001,`device_id` 为 NULL,`package_id` 为 3001,`user_id` 为 2001,`amount` 为 30.00,状态为 1(待支付)
|
||||
- **WHEN** 用户购买套餐,订单金额为 30 00 分(30 元),选择钱包支付,钱包余额为 10000 分
|
||||
- **THEN** 系统创建订单,`amount` 为 3000,`payment_method` 为 "wallet",`wallet_payment_amount` 为 3000,`online_payment_amount` 为 0
|
||||
|
||||
#### Scenario: 创建设备级套餐购买订单
|
||||
#### Scenario: 全额在线支付
|
||||
|
||||
- **WHEN** 用户(ID 为 2001)为设备(ID 为 5001,绑定 3 张 IoT 卡)购买套餐(ID 为 3002),金额为 399.00 元
|
||||
- **THEN** 系统创建订单记录,`order_type` 为 1,`device_id` 为 5001,`iot_card_id` 为 NULL,`package_id` 为 3002,`user_id` 为 2001,`amount` 为 399.00,状态为 1(待支付)
|
||||
- **WHEN** 用户购买套餐,订单金额为 3000 分(30 元),选择在线支付
|
||||
- **THEN** 系统创建订单,`amount` 为 3000,`payment_method` 为 "online",`wallet_payment_amount` 为 0,`online_payment_amount` 为 3000
|
||||
|
||||
#### Scenario: 创建号卡订单(运营商回传)
|
||||
#### Scenario: 混合支付
|
||||
|
||||
- **WHEN** Gateway 回传运营商订单,虚拟商品编码对应号卡 ID 为 6001,代理 ID 为 123,订单金额为 30.00 元
|
||||
- **THEN** 系统创建订单记录,`order_type` 为 2,`number_card_id` 为 6001,`iot_card_id` 为 NULL,`device_id` 为 NULL,`agent_id` 为 123,`amount` 为 30.00,`payment_method` 为 "carrier",状态为 2(已支付)
|
||||
- **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** 系统拒绝创建订单,返回错误信息"钱包余额不足",建议用户选择混合支付或在线支付
|
||||
|
||||
---
|
||||
|
||||
@@ -207,41 +201,75 @@ This capability supports:
|
||||
|
||||
### Requirement: 订单数据校验
|
||||
|
||||
系统 SHALL 对订单数据进行校验,确保数据完整性和一致性。
|
||||
系统 SHALL 对订单数据进行校验,确保数据完整性和一致性,特别是支付金额的一致性。
|
||||
|
||||
**校验规则**:
|
||||
- 订单编号(order_no):必填,长度 1-50 字符,唯一
|
||||
- 订单类型(order_type):必填,枚举值 1(套餐订单) | 2(号卡订单)
|
||||
- IoT 卡 ID(iot_card_id):套餐订单时 iot_card_id 和 device_id 二选一
|
||||
- 设备 ID(device_id):套餐订单时 iot_card_id 和 device_id 二选一
|
||||
- 号卡 ID(number_card_id):号卡订单时必填
|
||||
- 套餐 ID(package_id):套餐订单时必填
|
||||
- 用户 ID(user_id):必填,≥ 1
|
||||
- 订单金额(amount):必填,≥ 0,最多 2 位小数
|
||||
- 支付方式(payment_method):必填,枚举值 "wallet" | "online" | "carrier"
|
||||
- 状态(status):必填,枚举值 1-5
|
||||
**新增校验规则**:
|
||||
- `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: 创建订单时金额为负数
|
||||
#### Scenario: 支付金额不一致
|
||||
|
||||
- **WHEN** 创建订单,金额为 -10.00
|
||||
- **THEN** 系统拒绝创建,返回错误信息"订单金额必须 ≥ 0"
|
||||
- **WHEN** 创建订单,`amount` 为 5000,`wallet_payment_amount` 为 2000,`online_payment_amount` 为 2000
|
||||
- **THEN** 系统拒绝创建,返回错误信息"支付金额总和与订单金额不一致"
|
||||
|
||||
#### Scenario: 创建订单时订单编号重复
|
||||
#### Scenario: 钱包支付时在线支付金额不为 0
|
||||
|
||||
- **WHEN** 创建订单,订单编号为已存在的 "ORD-2025-001"
|
||||
- **THEN** 系统拒绝创建,返回错误信息"订单编号已存在"
|
||||
- **WHEN** 创建订单,`payment_method` 为 "wallet",`wallet_payment_amount` 为 3000,`online_payment_amount` 为 0(正确),但用户错误地设置 `online_payment_amount` 为 100
|
||||
- **THEN** 系统拒绝创建,返回错误信息"钱包支付时在线支付金额必须为 0"
|
||||
|
||||
#### Scenario: 创建套餐订单时未关联 IoT 卡或设备
|
||||
#### Scenario: 混合支付时钱包支付金额为 0
|
||||
|
||||
- **WHEN** 创建套餐订单,`iot_card_id` 和 `device_id` 都为 NULL
|
||||
- **THEN** 系统拒绝创建,返回错误信息"套餐订单必须关联 IoT 卡或设备"
|
||||
- **WHEN** 创建订单,`payment_method` 为 "mixed",`wallet_payment_amount` 为 0,`online_payment_amount` 为 5000
|
||||
- **THEN** 系统拒绝创建,返回错误信息"混合支付时钱包支付金额和在线支付金额都必须大于 0"
|
||||
|
||||
#### Scenario: 创建套餐订单时同时关联 IoT 卡和设备
|
||||
### Requirement: 订单支付处理
|
||||
|
||||
- **WHEN** 创建套餐订单,`iot_card_id` 为 1001,`device_id` 为 5001
|
||||
- **THEN** 系统拒绝创建,返回错误信息"套餐订单不能同时关联 IoT 卡和设备"
|
||||
系统 SHALL 根据支付方式正确处理订单支付,包括钱包扣款、在线支付、混合支付等。
|
||||
|
||||
#### Scenario: 创建号卡订单时未关联号卡
|
||||
**钱包支付流程**:
|
||||
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),订单状态变更为"已取消"
|
||||
|
||||
---
|
||||
|
||||
- **WHEN** 创建号卡订单,`number_card_id` 为 NULL
|
||||
- **THEN** 系统拒绝创建,返回错误信息"号卡订单必须关联号卡"
|
||||
|
||||
Reference in New Issue
Block a user