diff --git a/docs/admin-openapi.yaml b/docs/admin-openapi.yaml index e304af5..2f880c6 100644 --- a/docs/admin-openapi.yaml +++ b/docs/admin-openapi.yaml @@ -191,6 +191,9 @@ components: payment_transaction_id: description: 第三方支付流水号 type: string + payment_voucher_key: + description: 支付凭证对象存储Key(线下支付时存在) + type: string recharge_no: description: 充值单号(ARCH前缀) type: string @@ -2320,6 +2323,9 @@ components: payment_method: description: 支付方式 (wechat:微信在线支付, offline:线下转账仅平台可用) type: string + payment_voucher_key: + description: 支付凭证对象存储Key(payment_method=offline 时必填,微信支付时忽略) + type: string shop_id: description: 目标店铺ID,代理只能填自己店铺 minimum: 0 diff --git a/openspec/changes/agent-recharges-payment-voucher/.openspec.yaml b/openspec/changes/agent-recharges-payment-voucher/.openspec.yaml new file mode 100644 index 0000000..863bff1 --- /dev/null +++ b/openspec/changes/agent-recharges-payment-voucher/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-04-17 diff --git a/openspec/changes/agent-recharges-payment-voucher/design.md b/openspec/changes/agent-recharges-payment-voucher/design.md new file mode 100644 index 0000000..e772494 --- /dev/null +++ b/openspec/changes/agent-recharges-payment-voucher/design.md @@ -0,0 +1,57 @@ +## Context + +`POST /api/admin/agent-recharges` 是平台管理员为代理商创建线下充值订单的接口。当 `payment_method=offline` 时,代表平台已收到代理商的线下转账,此时应同步录入支付凭证(如银行转账截图的对象存储 key),以便后续对账审计。 + +充值流程分两个阶段: +1. **创建阶段**(`POST /api/admin/agent-recharges`):平台录入充值单 + 上传凭证 +2. **确认阶段**(`POST /api/admin/agent-recharges/:id/offline-pay`):财务二次确认,凭证已在创建时录入,无需重复上传 + +凭证文件本身通过已有的 `/storage/upload-url` 预签名 URL 上传至对象存储(OSS),后端仅存储 `file_key`。 + +## Goals / Non-Goals + +**Goals:** +- `Create` 接口在 `payment_method=offline` 时强制要求传入 `payment_voucher_key` +- 凭证 key 随充值记录一起写入数据库 +- 响应 DTO 返回凭证 key 供前端展示 +- `OfflinePay` 接口不做任何凭证相关改动 + +**Non-Goals:** +- 不涉及文件上传本身(OSS 预签名链路已有) +- 不修改 `OfflinePay` 接口(财务确认流程独立于凭证管理) +- 不做凭证有效性验证(文件是否真实存在于 OSS) +- `payment_method=wechat` 时 `payment_voucher_key` 无需传,应忽略 + +## Decisions + +### 决策1:凭证在创建时收集,而非确认时 + +**选择**:在 `Create` 时收集凭证,因为平台管理员在录入线下充值单时就已持有转账凭证。 + +**理由**:线下充值的业务含义是"平台已收到转账,现在录入系统"。凭证是这次录入的必要信息。`OfflinePay` 是财务的二次确认操作,凭证应已在录入时保存,财务只负责审核确认。 + +### 决策2:字段命名与 orders 保持一致 + +**选择**:使用 `payment_voucher_key`,与 `order.payment_voucher_key` 完全一致。 + +**理由**:统一命名降低认知负担,前端可复用同一组件。 + +### 决策3:`wechat` 支付方式时字段可选 + +**选择**:`payment_voucher_key` 使用 `validate:"omitempty,max=500"`,仅在 Service 层对 `offline` 方式做非空校验。 + +**理由**:微信在线支付无需凭证,不应因为加了字段就破坏微信支付的调用契约。 + +### 决策4:凭证随 Create 直接写入 + +**选择**:在 `Create` 方法构建 `AgentRechargeRecord` 时直接赋值 `PaymentVoucherKey`,与记录创建同一个 `agentRechargeStore.Create()` 调用写入。 + +**理由**:凭证是充值记录的组成部分,不需要单独事务,最简路径。 + +## Risks / Trade-offs + +**[风险] 历史记录无凭证** +→ 历史已创建的线下充值记录 `payment_voucher_key` 为空,属正常情况,不补录。 + +**[风险] 数据库迁移** +→ 新列 `VARCHAR(500)` 允许 NULL(兼容历史数据),新接口 Service 层对 `offline` 方式强制非空。迁移简单,无需锁表。 diff --git a/openspec/changes/agent-recharges-payment-voucher/proposal.md b/openspec/changes/agent-recharges-payment-voucher/proposal.md new file mode 100644 index 0000000..4ad5b7e --- /dev/null +++ b/openspec/changes/agent-recharges-payment-voucher/proposal.md @@ -0,0 +1,32 @@ +## Why + +`/api/admin/agent-recharges` 线下充值确认接口(`OfflinePay`)未要求上传支付凭证,而同类的 `/api/admin/orders` 线下支付已强制上传凭证。两个业务场景均涉及线下资金流转,审计和对账需要同等的凭证留存能力。 + +## What Changes + +- `CreateAgentRechargeRequest` DTO 新增 `payment_voucher_key` 字段(`payment_method=offline` 时必填) +- `AgentRechargeResponse` DTO 新增 `payment_voucher_key` 响应字段 +- `AgentRechargeRecord` Model 新增 `payment_voucher_key` 数据库列 +- `Create` Service 方法新增:当 `payment_method=offline` 时校验凭证非空,并将凭证写入充值记录 +- 数据库迁移:`tb_agent_recharge_record` 表新增 `payment_voucher_key` 列 +- `OfflinePay` 接口不涉及凭证(财务确认流程,凭证已在创建时录入) + +## Capabilities + +### New Capabilities + +- `agent-recharge-payment-voucher`:代理充值线下支付凭证上传与存储能力,对齐 orders 接口的凭证管理标准 + +### Modified Capabilities + +(无已有 spec 变更) + +## Impact + +| 层级 | 文件 | 变更类型 | +|------|------|---------| +| DTO | `internal/model/dto/agent_recharge_dto.go` | `CreateAgentRechargeRequest` + `AgentRechargeResponse` 新增字段 | +| Model | `internal/model/agent_wallet.go` | `AgentRechargeRecord` 新增字段 | +| Service | `internal/service/agent_recharge/service.go` | `Create` 方法新增校验与保存逻辑 | +| DB | `migrations/` | 新增迁移文件 | +| Handler | `internal/handler/admin/agent_recharge.go` | 无需改动(透传) | diff --git a/openspec/changes/agent-recharges-payment-voucher/specs/agent-recharge-payment-voucher/spec.md b/openspec/changes/agent-recharges-payment-voucher/specs/agent-recharge-payment-voucher/spec.md new file mode 100644 index 0000000..c335665 --- /dev/null +++ b/openspec/changes/agent-recharges-payment-voucher/specs/agent-recharge-payment-voucher/spec.md @@ -0,0 +1,43 @@ +## ADDED Requirements + +### Requirement: 创建线下充值订单时必须上传支付凭证 + +系统 SHALL 在 `POST /api/admin/agent-recharges` 接口中,当 `payment_method=offline` 时强制要求 `payment_voucher_key` 字段非空。凭证 key 为通过 `/storage/upload-url` 上传至对象存储后获得的文件标识符。当 `payment_method=wechat` 时,`payment_voucher_key` 字段忽略。 + +#### Scenario: 线下支付缺少凭证时拒绝创建 + +- **WHEN** 调用 `POST /api/admin/agent-recharges`,`payment_method=offline` 且 `payment_voucher_key` 为空或未传 +- **THEN** 系统返回 400,错误码 `CodeInvalidParam`,消息"线下充值必须上传支付凭证" + +#### Scenario: 线下支付有凭证时创建成功 + +- **WHEN** 调用 `POST /api/admin/agent-recharges`,`payment_method=offline` 且 `payment_voucher_key` 非空 +- **THEN** 系统创建充值记录,凭证 key 写入 `tb_agent_recharge_record.payment_voucher_key`,返回 200 + +#### Scenario: 微信支付不需要凭证 + +- **WHEN** 调用 `POST /api/admin/agent-recharges`,`payment_method=wechat`,不传 `payment_voucher_key` +- **THEN** 系统正常创建充值记录,不做凭证校验,返回 200 + +### Requirement: 充值记录存储支付凭证 + +系统 SHALL 将 `payment_voucher_key` 持久化到 `tb_agent_recharge_record` 表的 `payment_voucher_key` 列,与充值记录创建在同一个数据库操作内完成。 + +#### Scenario: 凭证随记录创建写入 + +- **WHEN** `Create` 方法执行创建充值记录 +- **THEN** `payment_voucher_key` 与其他字段一同写入数据库,不可分割 + +### Requirement: 充值记录响应包含凭证信息 + +系统 SHALL 在 `AgentRechargeResponse` 中返回 `payment_voucher_key` 字段,允许为空(历史记录及微信支付无凭证)。 + +#### Scenario: 已上传凭证的线下充值记录查询 + +- **WHEN** 查询已创建的线下充值记录 +- **THEN** 响应 `payment_voucher_key` 字段返回非空的对象存储 key + +#### Scenario: 微信支付记录无凭证字段 + +- **WHEN** 查询微信支付的充值记录 +- **THEN** 响应 `payment_voucher_key` 字段为空字符串或 omitempty(前端不展示) diff --git a/openspec/changes/agent-recharges-payment-voucher/tasks.md b/openspec/changes/agent-recharges-payment-voucher/tasks.md new file mode 100644 index 0000000..c583c47 --- /dev/null +++ b/openspec/changes/agent-recharges-payment-voucher/tasks.md @@ -0,0 +1,32 @@ +## 1. 数据库迁移 + +- [x] 1.1 在 `migrations/` 目录创建迁移文件,为 `tb_agent_recharge_record` 表添加 `payment_voucher_key VARCHAR(500)` 列(允许 NULL,兼容历史数据) +- [x] 1.2 执行迁移,验证 `tb_agent_recharge_record` 表已包含 `payment_voucher_key` 列(使用 PostgreSQL MCP 查询 `information_schema.columns` 确认) + +## 2. Model 层 + +- [x] 2.1 在 `internal/model/agent_wallet.go` 的 `AgentRechargeRecord` 结构体中添加 `PaymentVoucherKey string` 字段,包含 gorm tag 和中文注释 +- [x] 2.2 运行 `lsp_diagnostics` 确认 `agent_wallet.go` 无错误 + +## 3. DTO 层 + +- [x] 3.1 在 `internal/model/dto/agent_recharge_dto.go` 的 `CreateAgentRechargeRequest` 中添加 `PaymentVoucherKey` 字段(`validate:"omitempty,max=500"`,含 description 标签,注明 `payment_method=offline` 时必填) +- [x] 3.2 在 `AgentRechargeResponse` 中添加 `PaymentVoucherKey` 响应字段(`omitempty`) +- [x] 3.3 运行 `lsp_diagnostics` 确认 `agent_recharge_dto.go` 无错误 + +## 4. Service 层 + +- [x] 4.1 在 `internal/service/agent_recharge/service.go` 的 `Create` 方法中,在支付方式判断逻辑后添加凭证非空检查:当 `req.PaymentMethod == "offline"` 且 `strings.TrimSpace(req.PaymentVoucherKey) == ""` 时返回 `errors.New(errors.CodeInvalidParam, "线下充值必须上传支付凭证")` +- [x] 4.2 在 `Create` 方法构建 `AgentRechargeRecord` 的赋值块中添加 `PaymentVoucherKey: strings.TrimSpace(req.PaymentVoucherKey)` 字段 +- [x] 4.3 在 `toResponse`(或等价的响应构建函数)中将 `record.PaymentVoucherKey` 映射到 `AgentRechargeResponse.PaymentVoucherKey` +- [x] 4.4 运行 `lsp_diagnostics` 确认 `agent_recharge/service.go` 无错误 + +## 5. 文档生成器更新 + +- [x] 5.1 检查 `cmd/api/docs.go` 和 `cmd/gendocs/main.go` 中 `AgentRechargeHandler` 的注册方式(本次未新增 Handler,仅修改已有请求体,确认无需额外改动) + +## 6. 接口验证 + +- [x] 6.1 调用 `POST /api/admin/agent-recharges`(`payment_method=offline`,不传凭证),验证返回 400"线下充值必须上传支付凭证" +- [x] 6.2 调用 `POST /api/admin/agent-recharges`(`payment_method=offline`,传入合法凭证 key),验证返回 200,使用 PostgreSQL MCP 查询 `tb_agent_recharge_record` 确认 `payment_voucher_key` 字段已写入正确值 +- [x] 6.3 调用 `POST /api/admin/agent-recharges`(`payment_method=wechat`,不传凭证),验证正常创建成功,无凭证校验报错