## 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` 方式强制非空。迁移简单，无需锁表。
