docs: 归档 asset-wallet-interface OpenSpec 提案,更新卡钱包 spec
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Admin 端查询资产钱包概况
|
||||
|
||||
系统 SHALL 提供 `GET /api/admin/assets/:asset_type/:id/wallet` 接口,允许平台用户和代理账号查询指定卡或设备的钱包余额概况。
|
||||
|
||||
**接口规格**:
|
||||
- 路径参数 `asset_type`:`card` 或 `device`
|
||||
- 路径参数 `id`:资产数据库 ID(uint)
|
||||
- 无请求体
|
||||
- 返回字段:`wallet_id`、`resource_type`、`resource_id`、`balance`、`frozen_balance`、`available_balance`、`currency`、`status`、`status_text`、`created_at`、`updated_at`
|
||||
|
||||
**权限规则**:
|
||||
- 平台用户/超级管理员:可查询所有资产钱包
|
||||
- 代理账号:只能查询 `shop_id_tag IN (当前店铺及下级店铺)` 的资产钱包(由 `ApplyShopTagFilter` 自动过滤)
|
||||
- 企业账号:Handler 层直接返回 403,禁止访问
|
||||
|
||||
#### Scenario: 平台用户查询卡钱包概况
|
||||
|
||||
- **WHEN** 平台用户请求 `GET /api/admin/assets/card/456/wallet`,该卡存在钱包记录,余额 100 元,冻结 0 元
|
||||
- **THEN** 系统返回 200,`balance=10000`,`frozen_balance=0`,`available_balance=10000`,`status=1`,`status_text="正常"`
|
||||
|
||||
#### Scenario: 代理账号查询下级资产钱包
|
||||
|
||||
- **WHEN** 代理账号(shop_id=10)请求 `GET /api/admin/assets/device/789/wallet`,该设备的 `shop_id_tag` 在该代理的下级店铺范围内
|
||||
- **THEN** 系统返回 200,返回该设备的钱包详情
|
||||
|
||||
#### Scenario: 代理账号查询越权资产钱包
|
||||
|
||||
- **WHEN** 代理账号(shop_id=10)请求 `GET /api/admin/assets/card/999/wallet`,该卡的 `shop_id_tag` 不在该代理的下级店铺范围内
|
||||
- **THEN** 系统返回 404,错误消息为"该资产暂无钱包记录"(不区分"无权"与"不存在")
|
||||
|
||||
#### Scenario: 企业账号请求被拒绝
|
||||
|
||||
- **WHEN** 企业账号请求 `GET /api/admin/assets/card/456/wallet`
|
||||
- **THEN** 系统返回 403,错误消息为"企业账号无权查看钱包信息"
|
||||
|
||||
#### Scenario: 资产无钱包记录
|
||||
|
||||
- **WHEN** 平台用户请求 `GET /api/admin/assets/card/456/wallet`,该卡尚未创建钱包(未充值过)
|
||||
- **THEN** 系统返回 404,错误消息为"该资产暂无钱包记录"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Admin 端查询资产钱包流水列表
|
||||
|
||||
系统 SHALL 提供 `GET /api/admin/assets/:asset_type/:id/wallet/transactions` 接口,允许平台用户和代理账号分页查询指定资产的钱包收支流水,每条流水包含可跳转的来源编号。
|
||||
|
||||
**接口规格**:
|
||||
- 路径参数:同上
|
||||
- 查询参数:`page`(默认 1)、`page_size`(默认 20,最大 100)、`transaction_type`(可选过滤)、`start_time`(可选)、`end_time`(可选)
|
||||
- 流水按 `created_at` 倒序排列
|
||||
- 每条流水返回:`id`、`transaction_type`、`transaction_type_text`、`amount`、`balance_before`、`balance_after`、`reference_type`、`reference_no`、`remark`、`created_at`
|
||||
|
||||
**来源编号跳转规则**:
|
||||
- `reference_type = "recharge"` → `reference_no` 为充值单号(`CRCH…`),前端可跳转至充值单详情
|
||||
- `reference_type = "order"` → `reference_no` 为订单号(`ORD…`),前端可跳转至订单详情
|
||||
|
||||
**权限规则**:与钱包概况接口相同
|
||||
|
||||
#### Scenario: 查询充值和扣款流水
|
||||
|
||||
- **WHEN** 平台用户请求 `GET /api/admin/assets/card/456/wallet/transactions?page=1&page_size=20`,该卡有 1 条充值流水(100 元)和 1 条扣款流水(-30 元)
|
||||
- **THEN** 系统返回 200,`total=2`,按时间倒序返回两条记录,充值流水 `amount=10000`、`reference_type="recharge"`、`reference_no="CRCH20260309001"`;扣款流水 `amount=-3000`、`reference_type="order"`、`reference_no="ORD20260310001"`
|
||||
|
||||
#### Scenario: 按交易类型过滤
|
||||
|
||||
- **WHEN** 平台用户请求 `GET /api/admin/assets/card/456/wallet/transactions?transaction_type=recharge`
|
||||
- **THEN** 系统只返回 `transaction_type="recharge"` 的流水记录
|
||||
|
||||
#### Scenario: 分页超出范围
|
||||
|
||||
- **WHEN** 请求 `page_size=200`(超过最大值 100)
|
||||
- **THEN** 系统返回 400,错误消息为参数验证失败
|
||||
|
||||
#### Scenario: 资产无流水记录
|
||||
|
||||
- **WHEN** 平台用户请求某资产的流水列表,该资产钱包存在但尚无任何流水
|
||||
- **THEN** 系统返回 200,`list=[]`,`total=0`
|
||||
@@ -0,0 +1,103 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 卡钱包实体定义
|
||||
|
||||
系统 SHALL 定义资产钱包(AssetWallet)实体,管理物联网卡和设备级别的钱包,支持资源转手场景。原 `CardWallet` / `tb_card_wallet` 全量改名为 `AssetWallet` / `tb_asset_wallet`。
|
||||
|
||||
**核心概念**:
|
||||
- **物联网卡钱包**:归属单张物联网卡,卡转手时钱包跟着卡走
|
||||
- **设备钱包**:归属设备(含1-4张卡),设备的多张卡共享钱包,设备转手时钱包跟着设备走
|
||||
|
||||
**实体字段(与原 CardWallet 完全一致,仅表名改变)**:
|
||||
- `id`:钱包 ID(主键,BIGINT,自增)
|
||||
- `resource_type`:资源类型(VARCHAR(20),枚举值:"iot_card" | "device")
|
||||
- `resource_id`:资源 ID(BIGINT)
|
||||
- `balance`:余额(BIGINT,单位:分,默认 0)
|
||||
- `frozen_balance`:冻结余额(BIGINT,单位:分,默认 0)
|
||||
- `currency`:币种(VARCHAR(10),默认 "CNY")
|
||||
- `status`:钱包状态(INT,1-正常 2-冻结 3-关闭,默认 1)
|
||||
- `version`:版本号(INT,乐观锁)
|
||||
- `shop_id_tag`:店铺 ID 标签(多租户过滤)
|
||||
- `enterprise_id_tag`:企业 ID 标签(可空)
|
||||
- `created_at` / `updated_at` / `deleted_at`
|
||||
|
||||
**表名变更**:`tb_card_wallet` → `tb_asset_wallet`
|
||||
|
||||
#### Scenario: 创建物联网卡钱包
|
||||
|
||||
- **WHEN** 个人客户通过 ICCID "8986001234567890" 登录,为该卡充值
|
||||
- **THEN** 系统创建钱包记录写入 `tb_asset_wallet`,`resource_type` 为 "iot_card",`resource_id` 为卡 ID
|
||||
|
||||
#### Scenario: 创建设备钱包
|
||||
|
||||
- **WHEN** 个人客户通过设备号登录,为设备充值
|
||||
- **THEN** 系统创建钱包记录写入 `tb_asset_wallet`,`resource_type` 为 "device",设备的所有卡共享该钱包
|
||||
|
||||
#### Scenario: 计算可用余额
|
||||
|
||||
- **WHEN** 钱包余额 10000 分,冻结余额 3000 分
|
||||
- **THEN** 可用余额 = 7000 分
|
||||
|
||||
#### Scenario: 防止同一资源重复创建钱包
|
||||
|
||||
- **WHEN** 物联网卡(ID=100)已有钱包,尝试再次创建
|
||||
- **THEN** 系统拒绝,返回错误"该资源已存在钱包"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 资产钱包交易记录
|
||||
|
||||
系统 SHALL 记录所有资产钱包余额变动,包括充值、套餐扣费、退款,确保完整收支审计追踪。原 `CardWalletTransaction` / `tb_card_wallet_transaction` 全量改名为 `AssetWalletTransaction` / `tb_asset_wallet_transaction`,同时 `reference_id (bigint)` 字段改为 `reference_no (varchar 50)`。
|
||||
|
||||
**实体字段**:
|
||||
- `id`:交易记录 ID(主键)
|
||||
- `asset_wallet_id`:资产钱包 ID(关联 `tb_asset_wallet.id`,原 `card_wallet_id`)
|
||||
- `resource_type`:资源类型(冗余字段)
|
||||
- `resource_id`:资源 ID(冗余字段)
|
||||
- `user_id`:操作人用户 ID
|
||||
- `transaction_type`:交易类型(`recharge` / `deduct` / `refund`)
|
||||
- `amount`:变动金额(分,充值为正,扣款/退款为负)
|
||||
- `balance_before`:变动前余额(分)
|
||||
- `balance_after`:变动后余额(分)
|
||||
- `status`:交易状态(1-成功 2-失败 3-处理中)
|
||||
- `reference_type`:关联业务类型(`recharge` 或 `order`,可空)
|
||||
- `reference_no`:关联业务编号,存储充值单号(`CRCH…`)或订单号(`ORD…`)(VARCHAR(50),可空)— **原字段 `reference_id (bigint)` 改名并变更类型**
|
||||
- `remark`:备注(TEXT,可空)
|
||||
- `metadata`:扩展信息(JSONB,可空)
|
||||
- `creator`:创建人 ID
|
||||
- `shop_id_tag` / `enterprise_id_tag`:多租户标签
|
||||
|
||||
**表名变更**:`tb_card_wallet_transaction` → `tb_asset_wallet_transaction`
|
||||
|
||||
**字段变更**:`reference_id bigint` → `reference_no varchar(50)`
|
||||
|
||||
#### Scenario: 充值写入流水记录
|
||||
|
||||
- **WHEN** 个人客户完成充值(充值单号 CRCH20260309001,金额 100 元),充值回调成功
|
||||
- **THEN** 系统在 `tb_asset_wallet_transaction` 写入一条记录:`transaction_type="recharge"`,`amount=10000`,`reference_type="recharge"`,`reference_no="CRCH20260309001"`
|
||||
|
||||
#### Scenario: 钱包支付套餐写入扣款流水
|
||||
|
||||
- **WHEN** 个人客户使用钱包支付套餐订单(订单号 ORD20260310001,金额 30 元),`WalletPay` 执行成功
|
||||
- **THEN** 系统在同一事务内向 `tb_asset_wallet_transaction` 写入一条记录:`transaction_type="deduct"`,`amount=-3000`,`reference_type="order"`,`reference_no="ORD20260310001"`,`balance_before` 为扣款前余额,`balance_after` = `balance_before - 3000`
|
||||
|
||||
#### Scenario: 充值流水 reference_no 格式
|
||||
|
||||
- **WHEN** 系统写入充值流水
|
||||
- **THEN** `reference_no` 存储充值单号(格式:`CRCH` + 时间戳 + 随机数),而非数据库主键 ID
|
||||
|
||||
#### Scenario: 扣款流水 reference_no 格式
|
||||
|
||||
- **WHEN** 系统写入扣款流水
|
||||
- **THEN** `reference_no` 存储订单号(格式:`ORD` + 时间戳 + 6位随机数),而非数据库主键 ID
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 充值记录表改名
|
||||
|
||||
系统 SHALL 将原 `tb_card_recharge_record` 表重命名为 `tb_asset_recharge_record`,对应 Go 类型由 `CardRechargeRecord` 改名为 `AssetRechargeRecord`。H5 充值接口 JSON 响应字段 `wallet_id` 不变(保持向后兼容)。
|
||||
|
||||
#### Scenario: H5 充值接口字段不变
|
||||
|
||||
- **WHEN** 前端调用 `GET /api/h5/wallets/recharges/:id`,充值记录关联的钱包 ID 为 123
|
||||
- **THEN** 响应 JSON 中 `wallet_id` 仍为 `123`,JSON 字段名不变(仅 Go 内部字段名从 `CardWalletID` 改为 `AssetWalletID`)
|
||||
Reference in New Issue
Block a user