重构用的提案,现在不敢跑
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 49s

This commit is contained in:
2026-04-14 17:38:36 +08:00
parent 8706247436
commit d26010b29d
11 changed files with 492 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-04-14

View File

@@ -0,0 +1,143 @@
## Context
当前 `tb_asset_recharge_record` 混合了两个职责:业务订单(用户关心:状态、金额、套餐)和支付凭证(系统关心:交易号、支付渠道、回调)。强充流程创建的是充值记录而非订单,导致用户在 `/api/c/v1/orders` 看不到强充产生的记录。幂等键命中时直接报错而非引导用户恢复支付,造成"列表为空 + 无法重新下单"的死锁体验。
**现有数据流**
```
强充发起 → 写 tb_asset_recharge_record (单号前缀 CRCH)
微信回调 → 更新 tb_asset_recharge_record + 增加钱包余额
auto_purchase → 读 tb_asset_recharge_record.linked_package_ids → 创建 tb_order (套餐) + 扣款
```
**目标数据流**
```
强充发起 → 写 tb_recharge_order + tb_payment (CRCH 前缀保持兼容)
微信回调 → 更新 tb_payment + 更新 tb_recharge_order + 增加钱包余额
auto_purchase → 读 tb_recharge_order.linked_package_ids → 创建 tb_order (套餐) + 创建 tb_payment (钱包) + 扣款
```
## Goals / Non-Goals
**Goals:**
- 彻底替换 `tb_asset_recharge_record`,用 `tb_recharge_order` + `tb_payment` 完整承接其所有功能
- 充值订单通过独立接口 `/api/c/v1/recharge-orders` 展示;套餐订单接口 `/api/c/v1/orders` 保持不变
- 幂等键命中时返回已有待支付充值订单,引导前端拉起支付而非报错
- 普通钱包充值(非强充)同样写 `tb_recharge_order` + `tb_payment`
- 全部功能验证通过后,作为最后一步 DROP `tb_asset_recharge_record`
**Non-Goals:**
- 不合并 `tb_order``tb_recharge_order`(各自独立,不引入多态 order_type
- 不做 UNION 查询(充值订单和套餐订单各有独立入口,不混合展示)
- 不引入父子订单关联关系
- 不考虑回退(一次性完整替换,无过渡期)
- 不迁移历史数据历史充值记录业务已完成DROP 旧表即可)
## Decisions
### 决策 1新建 `tb_recharge_order` 而非扩展 `tb_order`
**选择**:独立的 `tb_recharge_order` 表,不将 `wallet_recharge` 作为 `order_type` 注入 `tb_order`
**原因**:充值订单与套餐订单领域边界清晰——充值订单无佣金、无卖家、无套餐明细、有自动购包状态;合并进 `tb_order` 会使该表充满特定类型才有意义的 NULL 列,且所有查询都需要加 `order_type` 判断。独立表让每张表的字段 100% 有效,业务逻辑无需条件分支。
**放弃的替代方案**:多态 `order_type` 方案——混合 NULL 列问题和业务逻辑污染问题无法解决。
### 决策 2充值订单和套餐订单各自独立接口
**选择**`/api/c/v1/orders` 保持不变,仅返回套餐购买订单(`tb_order`)。新增 `/api/c/v1/recharge-orders` 独立返回充值订单(`tb_recharge_order`)。
**原因**:两类订单业务语义不同——充值订单有 auto_purchase_status套餐订单有佣金状态强行混合展示需要 UNION 和统一 DTO引入不必要的复杂度和长期维护负担。前端各看各的接口职责清晰DTO 字段 100% 有意义。
### 决策 3`tb_payment` 统一承接所有支付凭证
**选择**:新建 `tb_payment` 表,`order_id` + `order_type``package_order` / `recharge_order`)多态关联到对应业务表。
**原因**:支付是基础设施,与业务订单解耦后,未来新增支付方式(富途、积分等)只需修改支付层,不触碰业务订单表。支付回调路由从"按单号前缀分发到不同 Service"简化为"查 `tb_payment` → 关联业务订单"。
**字段设计**
```sql
CREATE TABLE tb_payment (
id BIGSERIAL PRIMARY KEY,
payment_no VARCHAR(40) NOT NULL UNIQUE, -- 保持 CRCH/PAY 前缀兼容
order_id BIGINT NOT NULL,
order_type VARCHAR(30) NOT NULL, -- 'package_order' | 'recharge_order'
payment_method VARCHAR(20) NOT NULL, -- wallet | wechat | alipay | offline
amount BIGINT NOT NULL, -- 分
status SMALLINT NOT NULL DEFAULT 1, -- 1待支付 2已支付 3已失败 4已退款
third_party_trade_no VARCHAR(100),
payment_config_id BIGINT,
payment_voucher_key VARCHAR(500),
paid_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_payment_order ON tb_payment(order_id, order_type);
CREATE INDEX idx_payment_no ON tb_payment(payment_no) WHERE deleted_at IS NULL;
CREATE INDEX idx_payment_status ON tb_payment(status) WHERE deleted_at IS NULL;
```
### 决策 4`tb_recharge_order` 字段设计
承接 `tb_asset_recharge_record` 的业务字段 + 增加与资产的直接关联:
```sql
CREATE TABLE tb_recharge_order (
id BIGSERIAL PRIMARY KEY,
recharge_order_no VARCHAR(40) NOT NULL UNIQUE, -- 沿用 CRCH 前缀
user_id BIGINT NOT NULL, -- 发起用户(个人客户 ID
asset_wallet_id BIGINT NOT NULL,
resource_type VARCHAR(20) NOT NULL, -- iot_card | device
resource_id BIGINT NOT NULL,
iot_card_id BIGINT, -- 单卡充值时有值(用于 ListOrders 查询)
device_id BIGINT, -- 设备充值时有值
amount BIGINT NOT NULL, -- 充值金额(分)
status SMALLINT NOT NULL DEFAULT 1, -- 1待支付 2已支付 3已关闭 4已退款
paid_at TIMESTAMPTZ,
shop_id_tag BIGINT NOT NULL DEFAULT 0,
enterprise_id_tag BIGINT,
operator_type VARCHAR(30) NOT NULL, -- personal_customer | admin_user
generation INT NOT NULL DEFAULT 1,
-- 强充专有字段(普通充值为 NULL
linked_package_ids JSONB,
linked_order_type VARCHAR(20),
linked_carrier_type VARCHAR(20),
linked_carrier_id BIGINT,
auto_purchase_status VARCHAR(20), -- pending | success | failed
-- 累计充值(用于强充阈值判断)
accumulated_recharge_series BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_recharge_order_card ON tb_recharge_order(iot_card_id, generation) WHERE deleted_at IS NULL;
CREATE INDEX idx_recharge_order_device ON tb_recharge_order(device_id, generation) WHERE deleted_at IS NULL;
CREATE INDEX idx_recharge_order_user ON tb_recharge_order(user_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_recharge_order_no ON tb_recharge_order(recharge_order_no) WHERE deleted_at IS NULL;
CREATE INDEX idx_recharge_order_status ON tb_recharge_order(status) WHERE deleted_at IS NULL;
```
### 决策 5旧表在所有验证通过后作为最后一步 DROP
**选择**:迁移过程中保留 `tb_asset_recharge_record`(只读,不再写入);全部功能验证通过后,最后一个任务执行 `DROP TABLE tb_asset_recharge_record`
**原因**:保留旧表到最后是唯一的安全网——若验证发现遗漏的调用路径,还能对照旧表数据排查。一旦验证完整通过,立即 DROP不拖延不留历史包袱。不做重命名归档不设 3-6 个月观察期。
### 决策 6幂等键修改
**现有问题**`SetNX(redisKey, "processing")` 命中时直接返回 "订单正在创建中",不区分"真正在处理"和"已创建待支付"。
**修改方案**`!claimed``GET` 当前值:
- 值为 `"processing"` → 并发请求,返回原有报错
- 值为 `recharge_order_no` → 查 `tb_recharge_order`,若 `status=1`(待支付)则返回已有充值订单,前端拉起支付
## Risks / Trade-offs
- **[历史充值记录不可见]** → 接受,`/api/c/v1/recharge-orders` 仅展示新表数据,用户在新表建立前的历史充值不展示;历史充值已到账,业务无影响
- **[`/api/c/v1/wallet/recharges` 数据断层]** → 接受,旧接口后端改为查 `tb_recharge_order`,历史数据不补填
## Open Questions
- `auto_purchase_status` 触发失败时的重试策略是否调整(当前 Asynq 最多重试 3 次)?(不在本次范围,单独讨论)

View File

@@ -0,0 +1,41 @@
## Why
当前 `tb_asset_recharge_record` 表同时承担"充值业务订单"和"第三方支付凭证"两个职责,导致强充订单对用户不可见、幂等键锁死后无法引导用户恢复支付、支付与业务逻辑强耦合难以扩展。需要彻底替换为职责单一的新结构:独立的充值订单表 `tb_recharge_order` + 独立的支付记录表 `tb_payment`,一次性完整删除 `tb_asset_recharge_record` 及其所有相关代码。
## What Changes
- **新增** `tb_recharge_order` 表:承接充值业务订单(用户可见、有状态流转、挂 `iot_card_id`/`device_id`
- **新增** `tb_payment` 表:统一记录所有支付凭证(微信/支付宝/钱包),与业务订单解耦
- **新增** `/api/c/v1/recharge-orders` 充值订单列表接口:独立展示充值订单,不与套餐订单混合
- **修改** `/api/c/v1/orders` 列表接口:保持不变,仅展示套餐购买订单(`tb_order`
- **修改** 支付回调链路:完全重写为查 `tb_payment` 路由,彻底移除旧链路
- **修改** `auto_purchase` 异步任务:从 `tb_recharge_order` 读取关联套餐 IDs
- **修改** 幂等检查逻辑:幂等键命中时返回已有充值订单,引导前端拉起支付
- **BREAKING** 直接删除 `tb_asset_recharge_record` 表及全部相关代码(`AssetRechargeStore``RechargeService` 写路径等),无过渡期,无 fallback
## Capabilities
### New Capabilities
- `recharge-order`:充值订单管理——创建、列表查询、详情、状态流转(待支付→已支付→自动购包完成),独立于套餐订单展示
- `payment-record`:支付记录管理——统一记录微信/支付宝/钱包支付凭证,关联业务订单(`tb_order``tb_recharge_order`),支持幂等回调处理
### Modified Capabilities
- `client-order-purchase`:强充下单改写新表;幂等检查命中时返回已有充值订单而非报错;`/api/c/v1/orders` 接口本身保持不变(仍只返回套餐订单)
- `force-recharge-check`:强充判断逻辑不变,创建数据改写 `tb_recharge_order` + `tb_payment`
- `order-payment``PayOrder` 接口支付成功时同步创建 `tb_payment` 记录
- `wallet-recharge`:普通充值全部改写 `tb_recharge_order` + `tb_payment``/api/c/v1/wallet/recharges` 接口后端数据源改为 `tb_recharge_order`
- `asset-recharge-adaptation`:该 spec 描述的能力全部迁移至新 spec原 spec 废弃
## Impact
**数据库**:新增 2 张表(`tb_recharge_order``tb_payment`),直接 DROP `tb_asset_recharge_record`
**代码删除**`AssetRechargeRecord` 模型、`asset_recharge_store.go``recharge/service.go` 中所有写方法及回调处理、bootstrap 中相关注入
**代码新增**`internal/model/recharge_order.go``internal/model/payment.go``recharge_order_store.go``payment_store.go``recharge_order/service.go`
**代码修改**`client_order/service.go`(强充创建 + 幂等恢复)、`auto_purchase.go`(改读新表)、`callback/payment.go`(完全重写路由)、`client_wallet.go`GetRechargeRecords 改查新表)、`wechat_config/service.go`resolvePaymentConfigID 改查新表)、`wechat_config_store.go`CountPendingRechargesByConfigID 改查新表、bootstrap 注入
**API 变化**:新增 `/api/c/v1/recharge-orders` 端点;`/api/c/v1/orders` 不变;`/api/c/v1/wallet/recharges` 后端换表但接口不变

View File

@@ -0,0 +1,30 @@
## REMOVED Requirements
### Requirement: 资产充值关联支付配置
**Reason**: `tb_asset_recharge_record` 表删除,支付配置关联迁移至 `tb_payment.payment_config_id`
**Migration**: 查询支付配置时从 `tb_payment` 读取,`PaymentStore.GetByPaymentNo()` 返回含 `payment_config_id` 的完整记录。
### Requirement: 资产充值表结构变更
**Reason**: `tb_asset_recharge_record` 表重命名为 `tb_asset_recharge_record_archive` 并停止写入,相关表结构变更不再需要维护。
**Migration**: 新的充值数据结构参见 `recharge-order` spec`tb_recharge_order`)和 `payment-record` spec`tb_payment`)。
### Requirement: 资产充值回调按配置验签
**Reason**: 充值回调路由改为通过 `tb_payment` 查找配置,验签逻辑迁移至 `payment-record` spec 的支付路由统一需求。
**Migration**: 回调处理查 `tb_payment.payment_config_id` 加载对应配置验签,与原逻辑等价。
### Requirement: 资产充值记录扩展字段(操作人与代际)
**Reason**: 这些字段operator_type、generation迁移至 `tb_recharge_order` 表,继续有效。
**Migration**: `tb_recharge_order.operator_type``tb_recharge_order.generation` 字段语义不变,字段值枚举不变。
## ADDED Requirements
### Requirement: 旧表在验证完成后删除
系统 SHALL 在所有新功能验证通过后,作为最后一步执行 `DROP TABLE tb_asset_recharge_record`。迁移期间旧表保留但不写入,仅用于人工对照核查。
#### Scenario: 旧表无新写入
- **WHEN** 新代码部署完成后
- **THEN** `tb_asset_recharge_record` 的 updated_at 最新记录时间早于部署时间,确认无新写入
#### Scenario: DROP 旧表
- **WHEN** 所有验证任务通过tasks.md 第 11 组全部完成)
- **THEN** 执行迁移文件 DROP TABLE旧表彻底删除

View File

@@ -0,0 +1,33 @@
## MODIFIED Requirements
### Requirement: D2 套餐订单列表接口
`/api/c/v1/orders` 接口 SHALL 保持不变,仅查询 `tb_order` 返回套餐购买订单。充值订单通过独立接口 `/api/c/v1/recharge-orders` 展示,两者不混合。
#### Scenario: 订单列表只返回套餐订单
- **WHEN** 用户查询某张卡的订单列表
- **THEN** 仅返回该卡下的套餐订单(来自 `tb_order`),不包含充值订单
- **THEN** 响应结构与原有接口完全兼容,无新增字段
### Requirement: AutoPurchaseAfterRecharge 异步任务
auto_purchase 任务 SHALL 从 `tb_recharge_order` 读取 linked_package_ids执行完成后更新 `tb_recharge_order.auto_purchase_status`,同时创建套餐订单(`tb_order`)和对应的 `tb_payment`payment_method=wallet。不再读取 `tb_asset_recharge_record`
#### Scenario: 自动购包成功写入新表
- **WHEN** auto_purchase 任务从 `tb_recharge_order` 获取 linked_package_ids 并完成购包
- **THEN** 创建 `tb_order`type=single_card/devicepayment_status=2
- **THEN** 创建 `tb_payment`payment_method=walletstatus=2关联新订单
- **THEN** `tb_recharge_order.auto_purchase_status` 更新为 success
## ADDED Requirements
### Requirement: D1 下单幂等恢复
系统 SHALL 在幂等键命中且对应充值订单为待支付状态时,返回已有充值订单信息(含 payment_config 引导),而非返回错误 "订单正在创建中"。
#### Scenario: 幂等键命中且订单待支付
- **WHEN** 用户重复发起下单请求Redis 幂等键已存在且值为 recharge_order_no
- **THEN** 查询 `tb_recharge_order`status=1待支付
- **THEN** 接口返回已有充值订单信息HTTP 200包含 `idempotent: true` 标志
- **THEN** 前端可凭此引导用户拉起已有支付而非展示报错
#### Scenario: 幂等键命中但订单已支付或关闭
- **WHEN** 幂等键存在,但对应充值订单 status=2/3已支付或已关闭
- **THEN** 删除 Redis 幂等键,允许重新发起下单

View File

@@ -0,0 +1,14 @@
## MODIFIED Requirements
### Requirement: 强充检查结果对客户端透出
强充判断逻辑 SHALL 不变(检查余额是否低于阈值),但触发强充后的创建流程 SHALL 写入 `tb_recharge_order` + `tb_payment`,不再写入 `tb_asset_recharge_record`。强充订单的 linked_package_ids、linked_carrier_type、linked_carrier_id 存储在 `tb_recharge_order` 中。
#### Scenario: 余额不足触发强充,写入新表
- **WHEN** 用户购买套餐时余额低于强充阈值,触发强充流程
- **THEN** 创建 `tb_recharge_order` 记录(含 linked_package_ids、linked_carrier_type、linked_carrier_id、auto_purchase_status=pending
- **THEN** 创建 `tb_payment` 记录payment_method=wechatstatus=1
- **THEN** 不再写入 `tb_asset_recharge_record`
#### Scenario: 强充发起后订单在列表可见
- **WHEN** 强充发起成功(微信支付参数返回前端)
- **THEN** 用户立刻可在 `/api/c/v1/orders` 列表中看到该充值订单status=1待支付

View File

@@ -0,0 +1,29 @@
## MODIFIED Requirements
### Requirement: 钱包支付
钱包支付 SHALL 在扣款成功的同一事务中创建 `tb_payment` 记录payment_method=walletstatus=2paid_at=当前时间),关联到 `tb_order`order_type=package_order。不再仅通过 `tb_order.payment_method` 隐式记录支付信息。
#### Scenario: 钱包支付同步创建支付记录
- **WHEN** 用户通过钱包支付套餐订单,扣款成功
- **THEN** 在同一事务中:创建 `tb_payment`method=walletstatus=2、更新 `tb_order.payment_status=2`、扣减钱包余额、创建钱包流水
#### Scenario: 钱包余额不足时不创建支付记录
- **WHEN** 用户发起钱包支付但余额不足
- **THEN** 事务回滚,不创建 `tb_payment` 记录,返回余额不足错误
### Requirement: 第三方支付回调
第三方支付回调处理 SHALL 从 `tb_payment` 查找 payment_no找到后更新 `tb_payment.status=2` 并更新关联的 `tb_order``tb_recharge_order`
#### Scenario: 新单号回调走新路由
- **WHEN** 第三方支付回调payment_no 在 `tb_payment` 中存在
- **THEN** 更新 `tb_payment`status=2third_party_trade_nopaid_at
- **THEN** 根据 `tb_payment.order_type` 更新关联业务订单(`tb_order``tb_recharge_order`
## ADDED Requirements
### Requirement: 支付记录幂等保障
系统 SHALL 通过 `tb_payment``payment_no` 唯一约束 + 状态检查确保 PayOrder 接口的幂等性:同一 order_id 在 `tb_payment` 中已有 status=2 的记录时,直接返回成功,不重复扣款。
#### Scenario: 重复调用 PayOrder 幂等返回
- **WHEN** 相同 order_id 的 PayOrder 请求重复到达,`tb_payment` 中已存在 status=2 记录
- **THEN** 直接返回支付成功,不重复扣减钱包余额

View File

@@ -0,0 +1,40 @@
## ADDED Requirements
### Requirement: 支付记录创建
系统 SHALL 在每次发起支付时创建 `tb_payment` 记录,关联到业务订单(`tb_order``tb_recharge_order`),记录 order_type 用于多态关联。钱包直接支付时 status 直接设为 2已支付第三方支付时 status 为 1待支付
#### Scenario: 微信支付创建支付记录
- **WHEN** 用户发起微信支付(强充或套餐直接微信支付)
- **THEN** `tb_payment` 创建 status=1 的记录payment_method=wechat关联 order_id 和 order_type
#### Scenario: 钱包支付创建支付记录
- **WHEN** 用户通过钱包支付套餐订单
- **THEN** `tb_payment` 创建 status=2 的记录payment_method=walletpaid_at=当前时间
### Requirement: 支付回调幂等处理
系统 SHALL 通过 `tb_payment.payment_no` 唯一约束和状态检查确保支付回调幂等,重复回调不产生重复业务效果。
#### Scenario: 首次支付成功回调
- **WHEN** 第三方支付成功回调payment_no 在 `tb_payment` 中存在且 status=1
- **THEN** 更新 `tb_payment.status`=2记录 third_party_trade_no 和 paid_at
- **THEN** 触发关联业务订单的后续处理
#### Scenario: 重复支付成功回调
- **WHEN** 相同 payment_no 的支付成功回调再次到达,`tb_payment.status` 已为 2
- **THEN** 直接返回成功,不执行任何业务更新
### Requirement: 支付路由统一
系统 SHALL 在支付回调处理时,优先从 `tb_payment` 查找 payment_no若存在则走新路由更新 `tb_payment` + 关联业务订单);若不存在则 fallback 到旧 `tb_asset_recharge_record` 查询(兼容迁移过渡期)。
#### Scenario: 新单号走新路由
- **WHEN** 支付回调的 payment_no 在 `tb_payment` 中存在
- **THEN** 走新支付路由:更新 `tb_payment` → 更新关联 `tb_recharge_order``tb_order` → 触发后续业务
### Requirement: 支付配置关联
系统 SHALL 在 `tb_payment` 中存储 payment_config_id关联发起支付时使用的微信/支付宝配置,支持多配置场景下的对账。
#### Scenario: 记录支付配置快照
- **WHEN** 创建支付记录时
- **THEN** 将当前使用的 payment_config_id 写入 `tb_payment`

View File

@@ -0,0 +1,50 @@
## ADDED Requirements
### Requirement: 充值订单创建
系统 SHALL 在强充发起时创建 `tb_recharge_order` 记录,记录充值金额、关联资产、关联套餐 IDs初始状态为待支付status=1。同时创建对应的 `tb_payment` 记录payment_method=wechatstatus=1
#### Scenario: 强充发起成功
- **WHEN** 用户发起强充(余额不足场景),系统成功调用微信支付 API
- **THEN** `tb_recharge_order` 创建一条记录status=1linked_package_ids 已设置)
- **THEN** `tb_payment` 创建一条关联记录order_type=recharge_orderpayment_method=wechatstatus=1
- **THEN** 接口返回充值订单信息 + 微信支付参数
#### Scenario: 微信支付 API 调用失败
- **WHEN** 系统在调用微信支付 API 时发生错误
- **THEN** `tb_recharge_order` 记录被回滚(或标记为已关闭 status=3
- **THEN** `tb_payment` 记录被回滚
- **THEN** Redis 幂等键被清除,允许用户重新发起
### Requirement: 充值订单状态流转
系统 SHALL 通过支付回调更新充值订单状态待支付1→ 已支付2关闭3退款4。状态更新使用乐观锁确保幂等性。
#### Scenario: 微信支付回调成功
- **WHEN** 微信支付成功回调到达payment_no 存在于 `tb_payment`
- **THEN** `tb_payment.status` 更新为 2记录 third_party_trade_no 和 paid_at
- **THEN** 关联的 `tb_recharge_order.status` 更新为 2
- **THEN** 钱包余额增加,创建钱包流水记录
- **THEN** 若 linked_package_ids 非空,触发 auto_purchase 异步任务
#### Scenario: 重复回调幂等处理
- **WHEN** 同一 payment_no 的支付成功回调重复到达
- **THEN** 系统检测到 `tb_payment.status` 已为 2直接返回成功
- **THEN** 不重复更新钱包余额,不重复触发 auto_purchase
### Requirement: 自动购包状态追踪
系统 SHALL 在 `tb_recharge_order` 上记录 auto_purchase_status由 Asynq Worker 更新,支持 pending / success / failed 三种状态。
#### Scenario: 自动购包成功
- **WHEN** auto_purchase 任务成功创建套餐订单并激活套餐
- **THEN** `tb_recharge_order.auto_purchase_status` 更新为 success
#### Scenario: 自动购包失败
- **WHEN** auto_purchase 任务在最大重试次数后仍失败
- **THEN** `tb_recharge_order.auto_purchase_status` 更新为 failed
- **THEN** 记录错误日志,告警通知运营
### Requirement: 充值订单列表查询
系统 SHALL 支持按 iot_card_id 或 device_id + generation 查询充值订单列表,支持按 status 过滤。
#### Scenario: 按资产查询充值订单
- **WHEN** 系统查询某张卡的充值订单列表
- **THEN** 返回该卡iot_card_id 匹配)且 generation 匹配的所有充值订单,按 created_at 降序

View File

@@ -0,0 +1,31 @@
## MODIFIED Requirements
### Requirement: 创建钱包充值订单
创建钱包充值订单 SHALL 写入 `tb_recharge_order`(非强充时 linked_package_ids 为 NULL并同时创建 `tb_payment` 记录。不再写入 `tb_asset_recharge_record`。充值订单号格式保持 CRCH 前缀兼容。
#### Scenario: 用户主动充值写新表
- **WHEN** 用户发起钱包充值(非强充场景)
- **THEN** 创建 `tb_recharge_order`linked_package_ids=NULLauto_purchase_status=NULL
- **THEN** 创建 `tb_payment`payment_method=wechat/alipaystatus=1
- **THEN** 不写 `tb_asset_recharge_record`
### Requirement: 充值支付回调处理
充值支付回调 SHALL 查 `tb_payment` 获取充值订单,更新 `tb_payment.status=2`,更新 `tb_recharge_order.status=2`,增加钱包余额,创建钱包流水。原有 `RechargeService.HandlePaymentCallback` 中对 `tb_asset_recharge_record` 的操作全部迁移到新表。
#### Scenario: 充值回调更新新表
- **WHEN** 充值支付回调到达
- **THEN** 查 `tb_payment` by payment_no
- **THEN** 事务:更新 `tb_payment.status=2` → 更新 `tb_recharge_order.status=2` → 增加 `tb_asset_wallet.balance` → 创建 `tb_asset_wallet_transaction`reference_type=recharge
### Requirement: 充值记录新增 auto_purchase_status 状态追踪
此需求 SHALL 迁移至 `tb_recharge_order.auto_purchase_status` 字段语义和状态枚举不变pending / success / failed`tb_asset_recharge_record` 上的该字段随表一起归档停用。
#### Scenario: 强充后 auto_purchase 完成,更新新表字段
- **WHEN** auto_purchase 任务完成套餐激活
- **THEN** `tb_recharge_order.auto_purchase_status` 更新为 success不再更新 `tb_asset_recharge_record`
## REMOVED Requirements
### Requirement: Store 方法签名支持事务参数
**Reason**: `AssetRechargeStore` 连同 `tb_asset_recharge_record` 一起删除,其事务方法签名不再需要维护。
**Migration**: 事务支持迁移至 `RechargeOrderStore``PaymentStore`,方法签名设计参考 `recharge-order` spec。

View File

@@ -0,0 +1,79 @@
## 1. 数据库迁移(建新表)
- [ ] 1.1 创建迁移文件:新建 `tb_recharge_order` 表(含所有字段和索引,参考 design.md 决策 4 的字段定义)
- [ ] 1.2 创建迁移文件:新建 `tb_payment` 表(含所有字段和索引,参考 design.md 决策 3 的字段定义)
## 2. Model 层
- [ ] 2.1 新建 `internal/model/recharge_order.go`:定义 `RechargeOrder` 模型、状态常量1待支付 2已支付 3已关闭 4已退款`AutoPurchaseStatus` 常量pending/success/failed
- [ ] 2.2 新建 `internal/model/payment.go`:定义 `Payment` 模型、状态常量1待支付 2已支付 3已失败 4已退款`OrderType` 常量package_order / recharge_order
## 3. Store 层
- [ ] 3.1 新建 `internal/store/postgres/recharge_order_store.go`:实现 Create、GetByRechargeOrderNo、GetByID、UpdateStatus乐观锁、ListByCard、ListByDevice
- [ ] 3.2 新建 `internal/store/postgres/payment_store.go`:实现 Create、GetByPaymentNo、UpdateStatus、UpdatePaymentInfo、ListByOrderID
- [ ] 3.3 在 `internal/bootstrap/stores.go` 中注册 `RechargeOrderStore``PaymentStore`
## 4. 强充创建流程重写
- [ ] 4.1 修改 `internal/service/client_order/service.go``createForceRechargeOrder`:改为写 `tb_recharge_order` + `tb_payment`,移除对 `rechargeRecordStore` 的调用
- [ ] 4.2 修改幂等键逻辑:`!claimed` 时 GET Redis 值,若为 recharge_order_no 则查 `tb_recharge_order`status=1 时返回已有充值订单(含 `idempotent: true` 标志status≠1 时删 key 允许重建
- [ ] 4.3 调整 `markClientPurchaseCreated`:存入 `recharge_order_no`(来自 `tb_recharge_order`,不再存充值记录号)
- [ ] 4.4 修改 `ClientOrderService` 结构体:注入 `rechargeOrderStore``paymentStore`,移除 `rechargeRecordStore` 字段
- [ ] 4.5 修改 `internal/bootstrap/handlers.go`:更新 `ClientOrderService` 的依赖注入
## 5. 支付回调链路完全重写
- [ ] 5.1 新建 `internal/service/recharge_order/service.go`:实现 `HandlePaymentCallback(ctx, paymentNo, paymentMethod, transactionID)`——查 `tb_payment` → 乐观锁更新 `tb_payment.status=2` → 更新 `tb_recharge_order.status=2` → 事务内增加钱包余额 + 创建钱包流水 + 更新累计充值 → 触发 auto_purchase 任务(若 linked_package_ids 非空)
- [ ] 5.2 修改 `internal/handler/callback/payment.go``dispatchWechatCallback` 改为查 `tb_payment` by payment_no找到则调 `rechargeOrderService.HandlePaymentCallback`CRCH 前缀单号不再路由到旧 `RechargeService`
- [ ] 5.3 修改 `internal/bootstrap/handlers.go`:注入 `rechargeOrderService` 到支付回调 Handler
- [ ] 5.4 修改 `internal/service/wechat_config/service.go``resolvePaymentConfigID`CRCH 前缀路由从 `AssetRechargeStore.GetByRechargeNo` 改为 `RechargeOrderStore.GetByRechargeOrderNo`,获取 `payment_config_id` 用于微信支付回调验签
- [ ] 5.5 修改 `internal/store/postgres/wechat_config_store.go``CountPendingRechargesByConfigID`SQL 从 `tb_asset_recharge_record` 改为查询 `tb_recharge_order`(按 `payment_config_id` 统计待支付充值数,用于删除配置前检查)
## 6. 普通充值流程迁移
- [ ] 6.1 修改 `internal/handler/app/client_wallet.go``CreateRecharge`:改为调用新的充值订单创建逻辑,写 `tb_recharge_order` + `tb_payment`,移除对旧 `RechargeService.Create` 的调用
- [ ] 6.2 修改 `internal/handler/app/client_wallet.go``GetRechargeRecords`:改为从 `tb_recharge_order` 查询
## 7. PayOrder 增加支付记录
- [ ] 7.1 修改 `internal/service/order/service.go``WalletPay`:在扣款事务中增加 `INSERT tb_payment`method=walletstatus=2order_type=package_order
- [ ] 7.2 在 `WalletPay` 中增加幂等检查:若 `tb_payment` 中已有该 order_id 且 status=2 的记录,直接返回成功,不重复扣款
## 8. auto_purchase 任务迁移
- [ ] 8.1 修改 `internal/task/auto_purchase.go`:任务 payload 改为传 `recharge_order_id`,从 `tb_recharge_order` 读取 linked_package_ids移除对 `tb_asset_recharge_record` 的读取
- [ ] 8.2 修改 `auto_purchase.go`:创建套餐订单(`tb_order`)的同时在同一事务内创建 `tb_payment`method=walletstatus=2order_type=package_order
- [ ] 8.3 修改 `auto_purchase.go`:任务完成/失败后更新 `tb_recharge_order.auto_purchase_status`,移除对 `tb_asset_recharge_record` 的写入
## 9. 新增充值订单独立接口
- [ ] 9.1 新增 `internal/handler/app/client_recharge_order.go`:实现 `ListRechargeOrders`GET `/api/c/v1/recharge-orders`,按 identifier + generation 查 `tb_recharge_order`,支持 status 过滤)和 `GetRechargeOrderDetail`GET `/api/c/v1/recharge-orders/:id`
- [ ] 9.2 新增对应 DTO`ClientRechargeOrderListRequest``ClientRechargeOrderListItem`(含 auto_purchase_status、recharge_order_no、amount、status、status_name、created_at
- [ ] 9.3 注册路由:在 C 端路由组中注册 `/api/c/v1/recharge-orders` 相关路由
- [ ] 9.4 修改 `ClientCreateOrderResponse``recharge` 分支返回 `recharge_order_no`(来自新表),增加 `idempotent` bool 字段
## 10. 删除旧代码
- [ ] 10.1 删除 `internal/service/recharge/service.go` 中全部写方法和回调处理HandlePaymentCallback、Create 等),若无其他引用则删除整个文件
- [ ] 10.2 删除 `internal/store/postgres/asset_recharge_store.go` 全部内容(无需保留任何方法)
- [ ] 10.3 删除 `internal/model/asset_wallet.go` 中的 `AssetRechargeRecord` 模型定义
- [ ] 10.4 从 `internal/bootstrap/stores.go``handlers.go` 中移除 `AssetRechargeStore` 的所有注册和注入
- [ ] 10.5 从 `internal/service/client_order/service.go` 中移除 `rechargeRecordStore` 字段和全部引用
- [ ] 10.6 运行 `go build ./...`,修复所有因删除旧代码引起的编译错误
## 11. 验证
- [ ] 11.1 用 PostgreSQL MCP 验证:发起强充后 `tb_recharge_order``tb_payment` 均有新记录,`tb_asset_recharge_record` 无新记录
- [ ] 11.2 用 PostgreSQL MCP 验证:微信支付回调后 `tb_payment.status=2``tb_recharge_order.status=2`,钱包余额正确增加
- [ ] 11.3 用 PostgreSQL MCP 验证auto_purchase 完成后 `tb_order` 新增套餐订单,`tb_recharge_order.auto_purchase_status=success`
- [ ] 11.4 手动验证GET `/api/c/v1/recharge-orders` 正确返回充值订单列表GET `/api/c/v1/orders` 仍只返回套餐订单
- [ ] 11.5 手动验证:重复发起相同参数的下单请求,返回 HTTP 200 且 `idempotent=true`,不报"订单正在创建中"
- [ ] 11.6 手动验证:普通充值(非强充)写 `tb_recharge_order`,回调后钱包余额正确增加
- [ ] 11.7 手动验证PayOrder 支付套餐后 `tb_payment` 有对应 wallet 支付记录,重复调用幂等返回成功
## 12. 删除旧表(最后执行)
- [ ] 12.1 确认 `tb_asset_recharge_record` 无任何新写入(检查该表 updated_at 最新时间是否在部署前)
- [ ] 12.2 创建迁移文件:`DROP TABLE tb_asset_recharge_record`
- [ ] 12.3 执行迁移,确认表已删除