Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2.8 KiB
Context
POST /api/admin/agent-recharges 是平台管理员为代理商创建线下充值订单的接口。当 payment_method=offline 时,代表平台已收到代理商的线下转账,此时应同步录入支付凭证(如银行转账截图的对象存储 key),以便后续对账审计。
充值流程分两个阶段:
- 创建阶段(
POST /api/admin/agent-recharges):平台录入充值单 + 上传凭证 - 确认阶段(
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 方式强制非空。迁移简单,无需锁表。