# 线下收款登记（司机现场代收人 / 对公转账）
- **变更日期**: 2026-07-07
- **端类型**: 管理后台
- **变更类型**: 新增接口
- **Issue**: https://git.1814.love:8443/wx/HL/issues/4763
- **PR**: https://git.1814.love:8443/wx/HL/pulls/4766

---
## 1. 接口背景

核单 epic PR1。核单流程中，定制师需要记录非微信支付渠道的线下收款（司机现场代收人现金、对公转账），并确保订单 `paid_amount` 实时更新。
登记成功后事务层推进 `paid_amount` 累加并重算 `pay_status`(PARTIAL_PAID/FULLY_PAID)。
已撤销的凭据行保留在列表供审计溯源。

---
## 2. 变更清单

| 序号 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 1 | POST | `/v3/admin/order/{orderId}/payment/manual-receipt` | 登记线下收款 |
| 2 | DELETE | `/v3/admin/order/{orderId}/payment/manual-receipt/{receiptId}` | 撤销线下收款（转删，行保留） |
| 3 | GET | `/v3/admin/order/{orderId}/payment/manual-receipt` | 查询订单线下收款列表 |

---
## 3. 接口详情

| 项 | 说明 |
|-----|------|
| 认证 | JWT Bearer(管理端), Gateway 注入 X-Admin-Id/X-Admin-RealName |
| 幂等性 | POST 非幂等; DELETE 幂等守卫（已撤销返 520406） |
| 限流 | 网关全局限流 |
| 订单状态白名单 | 仅 CANCELLED 拒绝，其余状态（含已结算）均可登记 |

---
## 4. 接口入参

### 4.1 路径参数（三个接口通用）
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| orderId | Long | 是 | 订单 ID |

DELETE 额外路径参数: receiptId (Long, 必, 凭据 ID 由 POST 响应获取)

### 4.2 请求体(POST)
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| channel | String | 是 | 收款渠道: DRIVER_CASH/BANK_TRANSFER |
| payType | String | 是 | 款项类型: DEPOSIT/BALANCE/FULL |
| amount | BigDecimal | 是 | >0 |
| receivedAt | LocalDateTime | 否 | 可补录历史，不传默认当前 |
| transferRef | String | 条件必填 | `BANK_TRANSFER`时必填 |
| collectorStaffId | Long | 条件必填 | `DRIVER_CASH`时必填且属本订单人员 |
| voucherUrls | List<String> | 否 | 凭证图片 URL |
| remark | String | 否 | 备注, max 500 |

**DELETE 请求体**: voidReason (String, 否, 撤销原因)
---
## 5. 出参字段

POST/DELETE 返回单条 `ManualReceiptVO`，GET 返回 `List<ManualReceiptVO>`。
> `paidAmountAfter`/`payStatusAfter` 仅在 POST/DELETE 响应有値，GET 列表为 null。

| 字段 | 类型 | 说明 |
|------|------|------|
| id | String(Long) | 凭据 ID(雪花序列化） |
| orderId | String(Long) | 订单 ID |
| channel | String | DRIVER_CASH/BANK_TRANSFER |
| channelLabel | String | 渠道显示标签 |
| payType | String | DEPOSIT/BALANCE/FULL |
| payTypeLabel | String | 款项显示标签 |
| amount | BigDecimal | 收款金额 |
| receivedAt | LocalDateTime | 收款时间 |
| collectorStaffId | String(Long) | 代收人 assignmentId, 仅 DRIVER_CASH 有値 |
| collectorStaffName | String | 代收人姓名, 仅 DRIVER_CASH 有値 |
| transferRef | String | 流水号, 仅 BANK_TRANSFER 有値 |
| voucherUrls | List<String> | 凭证图片 URL |
| remark | String | 备注 |
| operatorName | String | 登记人姓名 |
| createTime | LocalDateTime | 登记时间 |
| voided | Boolean | 是否已撤销 |
| voidedByName/voidedAt/voidReason | - | 撤销时有値 |
| paidAmountAfter | BigDecimal | 操作后订单累计已付金额, GET 为 null |
| payStatusAfter | String | 操作后订单支付状态, GET 为 null |

---
## 6. 枚举/数据字典

**channel:** DRIVER_CASH(司机现场代收人) / BANK_TRANSFER(对公转账)

**payType:** DEPOSIT(订金) / BALANCE(尾款) / FULL(全款)

**payStatusAfter:** UNPAID(未付款) / PARTIAL_PAID(已付订金) / FULLY_PAID(已付全款)
---
## 7. 错误码

| 错误码 | 含义 | 触发场景 |
|--------|------|---------|
| 520401 | 收款渠道非法 | channel not DRIVER_CASH/BANK_TRANSFER |
| 520402 | 对公转账必填转账流水号 | channel=BANK_TRANSFER & transferRef 为空 |
| 520403 | 必须指定代收人 | channel=DRIVER_CASH & collectorStaffId 为空 |
| 520404 | 代收人不属本订单人员 | collectorStaffId 不在order_staff_assignment中 |
| 520405 | 凭据不存在 | receiptId 非法 |
| 520406 | 已撤销，无法重复 | 对已撤销行再 DELETE |
| 520407 | 订单已取消，不允许登记 | 订单状态为 CANCELLED |
| 520408 | 收款金额必须>0 | amount<=0 |

---
## 8. 示例

### 8.1 典型成功 -- 登记司机现场代收人尾款

请求:
```http
POST /v3/admin/order/1234567890123456/payment/manual-receipt
Content-Type: application/json
X-Admin-RealName: 扎西师傅

{
  "channel": "DRIVER_CASH",
  "payType": "BALANCE",
  "amount": 8800.00,
  "collectorStaffId": "9800001001",
  "remark": "客户现场支付尾款"
}
```

响应:
```json
{
  "code": 200,
  "data": {
    "id": "1900000001000001",
    "channelLabel": "司机现场代收人",
    "payTypeLabel": "尾款",
    "amount": 8800.00,
    "collectorStaffName": "扎西师傅",
    "voided": false,
    "paidAmountAfter": 24800.00,
    "payStatusAfter": "FULLY_PAID"
  }
}
```

### 8.2 边界 -- 对公转账，补录历史时间
```json
{
  "channel": "BANK_TRANSFER",
  "payType": "DEPOSIT",
  "amount": 5000.00,
  "receivedAt": "2026-06-20T10:00:00",
  "transferRef": "GZL20260620001"
}
```
返回: paidAmountAfter 为当前累计已付金额。

### 8.3 业务失败 -- DRIVER_CASH 未指定代收人
```json
{
  "channel": "DRIVER_CASH",
  "amount": 3000.00
}
```
```json
{
  "code": 520403,
  "msg": "司机现场必须指定代收人"
}
```

---
## 9. 业务边界

**适用:** 客户现金付款 / 对公转账补录
**不适用:** 微信支付/支付宝 / 订单已取消
**特殊边界:**
- 同一订单可登记多条，每条独立计入`paid_amount`
- 撤销后`paid_amount`同事务回退，行记录保留
- paidAmountAfter/payStatusAfter 仅在 POST/DELETE 响应, GET 列表为 null

---
## 12. 注意事项

- id/orderId 均为 Long 雪花，序列化为字符串，前端禁 Number
- collectorStaffId 属于本订单人员配置(order_staff_assignment)中选取
- 登记后在核单对账页查看`paid_amount`，需重请 GET /v3/admin/order/{orderId}/settlement/recon

---
## 13. 关联/联系人

- Issue: https://git.1814.love:8443/wx/HL/issues/4763
- PR: https://git.1814.love:8443/wx/HL/pulls/4766
- Commit: https://git.1814.love:8443/wx/HL/commit/076bd285a5cc87665fdf431f9fe0a704dcdd7a33
- 后端负责人: 腰苏图