# [新增接口·管理后台] admin H5 客服代用户发起退款 (#2930)

> **PR**: #2932 | **服务**: hl-order-service-v2 | **更新时间**: 2026-05-23 10:30

## 1. 接口背景

管理后台 H5 客服操作台需要支持定制师/客服代用户发起退款申请。现有小程序端退款接口由用户自助发起，不适用客服代操作场景。

本接口允许客服在 H5 操作台手动填写退款金额并代用户提交，后端校验金额不超过订单订金金额，申请人留痕为 ADMIN 类型，创建后仍走主管审批流程。

## 2. 变更清单

| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 代用户发起退款申请 | POST | /admin/order/refund/apply | 新增 | 客服 H5 代用户提交退款，金额客服手填，不超订金 |

## 3. 接口详情

### 3.1 代用户发起退款申请

- **使用场景**：客服在 H5 操作台查看订单后，代用户发起退款申请
- **认证**：需要管理后台 JWT（Bearer Token），adminId 由后端从 Token 中提取
- **幂等性**：是，Header 携带 `Idempotent-Key`（UUID），5 秒内重复提交视为同一请求
- **限流**：无

调用前置步骤：先调 `GET /admin/order/refund/reason/list` 获取退款原因字典，拿到 reasonValue（字典 key）和 reasonText（字典显示文本）后再调本接口。

## 4. 接口入参

### 4.1 路径参数 / Query 参数

无。

### 4.2 请求体字段

| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|------|------|------|------|----------|
| orderId | Long | 是 | 订单 ID | 不能为空 |
| amount | BigDecimal | 是 | 退款金额 | 最小 0.01，且不能超过订单订金金额 |
| reasonValue | String | 是 | 退款原因字典值 | 从 /admin/order/refund/reason/list 获取；最长 100 字符 |
| reasonText | String | 是 | 退款原因文本 | 字典对应的显示文本；最长 500 字符 |
| reasonDetail | String | 否 | 补充说明 | 最长 2000 字符 |

注意：refundType 无需前端传入，后端固定设置为 DEPOSIT（订金退款）。

## 5. 出参（响应）

### 5.1 响应结构

`Result<RefundApplicationVO>`

### 5.2 响应字段

| 字段 | 类型 | 说明 |
|------|------|------|
| applicationId | Long | 退款申请 ID |
| orderId | Long | 订单 ID |
| orderNo | String | 订单号 |
| productName | String | 产品名称 |
| refundType | String | 退款类型，本接口固定返回 DEPOSIT |
| refundTypeLabel | String | 退款类型标签，固定返回 订金退款 |
| reasonText | String | 退款原因文本 |
| reasonDetail | String | 补充说明（可为 null） |
| paidAmount | BigDecimal | 订单已付金额 |
| calculatedAmount | BigDecimal | 本次退款金额（即客服填写的 amount） |
| actualAmount | BigDecimal | 实际退款金额（审批后确认，初始为 null） |
| status | String | 退款申请状态，创建后固定返回 PENDING |
| statusLabel | String | 退款状态标签，创建后固定返回 待审核 |
| applicantType | String | 申请人类型，固定返回 ADMIN |
| applicantId | Long | 申请人管理员 ID |
| applicantName | String | 申请人姓名 |
| createTime | String (ISO 8601) | 申请创建时间 |
| updateTime | String (ISO 8601) | 最后更新时间 |
| reviewAdminId | Long | 审批人 ID（创建时为 null，审批后填充） |
| reviewAdminName | String | 审批人姓名（创建时为 null） |
| reviewRemark | String | 审批备注（创建时为 null） |
| reviewedAt | String (ISO 8601) | 审批时间（创建时为 null） |
| approvalNo | String | 审批单号（创建时为 null） |
| departureDate | String (yyyy-MM-dd) | 出发日期 |
| daysBeforeDept | Integer | 距出发天数 |
| refundRatio | Integer | 退款比例（百分比，本接口为 null，不调退款政策） |
| policyId | Long | 退款政策 ID（本接口为 null） |
| policyName | String | 退款政策名称（本接口为 null） |
| appealStatus | Integer | 申诉状态（创建时为 null） |
| appealStatusLabel | String | 申诉状态标签（创建时为 null） |
| appealReason | String | 申诉原因（创建时为 null） |
| appealAmount | BigDecimal | 申诉退款金额（创建时为 null） |
| appealedAt | String (ISO 8601) | 申诉时间（创建时为 null） |
| refundedAt | String (ISO 8601) | 退款完成时间（创建时为 null） |

本接口创建的申请：policyId / policyName / refundRatio 均为 null（客服手填金额，不走退款政策计算）。

## 6. 枚举 / 数据字典

### 6.1 refundType（退款类型，出参字段）

**所属字段**：refundType | **类型**：String | 本接口固定为 DEPOSIT

| 值 | 中文 | 说明 |
|----|------|------|
| FULL | 全额退款 | 全额退还已付金额 |
| PARTIAL | 部分退款 | 部分退款 |
| DEPOSIT | 订金退款 | 仅退还订金部分，本接口固定此类型 |
| BALANCE | 尾款退款 | 仅退还尾款部分 |

### 6.2 status（退款申请状态，出参字段）

**所属字段**：status | **类型**：String | 创建后固定为 PENDING

| 值 | 中文 | 说明 |
|----|------|------|
| PENDING | 待审核 | 申请已提交，等待主管审批 |
| APPROVED | 已通过 | 主管审批通过，进入退款流程 |
| REJECTED | 已拒绝 | 主管审批拒绝 |
| APPEALING | 申诉中 | 用户对拒绝结果申诉 |
| APPEAL_APPROVED | 申诉通过 | 申诉被通过 |
| APPEAL_REJECTED | 申诉拒绝 | 申诉被拒绝 |
| REFUNDING | 退款中 | 正在处理退款 |
| REFUNDED | 已退款 | 退款已完成 |
| CANCELLED | 已取消 | 申请已撤回取消 |

活跃状态（同订单已有此类状态时拒绝重复提交）：PENDING / APPROVED / REFUNDING / APPEALING / APPEAL_APPROVED

### 6.3 applicantType（申请人类型，出参字段）

**所属字段**：applicantType | **类型**：String | 本接口固定为 ADMIN

| 值 | 中文 | 说明 |
|----|------|------|
| USER | 用户 | 小程序用户自助发起（mp 接口） |
| ADMIN | 管理员 | 客服/定制师代发起（本接口） |

### 6.4 reasonValue（退款原因字典值，入参字段）

**所属字段**：reasonValue | **类型**：String | **必填**：是

字典值通过 `GET /admin/order/refund/reason/list` 接口获取，下面为示例（以实际字典接口返回为准）：

| 值（示例） | 中文 |
|-----------|------|
| schedule_conflict | 行程冲突/时间变动 |
| personal_reason | 个人原因 |
| product_issue | 产品问题 |
| service_complaint | 服务投诉 |

实际可用值以 `GET /admin/order/refund/reason/list` 返回为准。

## 7. 错误码

| code | 含义 | 触发场景 |
|------|------|----------|
| 200 | 成功 | 退款申请创建成功 |
| 401 | 未授权 | 未携带或 Token 无效/过期 |
| 404 | 订单不存在 | orderId 对应的订单不存在 |
| 530007 | 退款金额超过订金金额 | amount 大于订单 depositAmount，message 含具体金额：退款金额不能超过订金金额: amount={x}, deposit={y} |
| 530004 | 重复退款申请 | 同一订单已存在 ACTIVE 状态的退款申请 |

## 8. 示例

### 8.1 典型成功

场景说明：客服代用户发起订金退款，退 500 元，选择行程冲突原因。

请求：

```
POST /admin/order/refund/apply
Authorization: Bearer <admin-jwt>
Idempotent-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Content-Type: application/json
```

```json
{
  "orderId": 1234567890123,
  "amount": 500.00,
  "reasonValue": "schedule_conflict",
  "reasonText": "行程冲突/时间变动",
  "reasonDetail": "客户因工作变动无法出行，需要退款"
}
```

响应：

```json
{
  "code": 200,
  "data": {
    "applicationId": 9876543210001,
    "orderId": 1234567890123,
    "orderNo": "HL2026052300001",
    "productName": "云南大理双廊古镇 5 日深度游",
    "refundType": "DEPOSIT",
    "refundTypeLabel": "订金退款",
    "reasonText": "行程冲突/时间变动",
    "reasonDetail": "客户因工作变动无法出行，需要退款",
    "paidAmount": 2000.00,
    "calculatedAmount": 500.00,
    "actualAmount": null,
    "policyId": null,
    "policyName": null,
    "refundRatio": null,
    "status": "PENDING",
    "statusLabel": "待审核",
    "applicantType": "ADMIN",
    "applicantId": 10086,
    "applicantName": "李小明",
    "departureDate": "2026-06-15",
    "daysBeforeDept": 23,
    "reviewAdminId": null,
    "reviewAdminName": null,
    "reviewRemark": null,
    "reviewedAt": null,
    "approvalNo": null,
    "appealStatus": null,
    "appealStatusLabel": null,
    "appealReason": null,
    "appealAmount": null,
    "appealedAt": null,
    "refundedAt": null,
    "createTime": "2026-05-23T10:30:00",
    "updateTime": "2026-05-23T10:30:00"
  },
  "message": "ok",
  "success": true
}
```

### 8.2 边界情况

场景说明：退款金额等于订单订金金额上限（全额退订金），不填补充说明（reasonDetail 为 null）。

请求：

```
POST /admin/order/refund/apply
Authorization: Bearer <admin-jwt>
Idempotent-Key: b2c3d4e5-f6a7-8901-bcde-f12345678901
Content-Type: application/json
```

```json
{
  "orderId": 1234567890124,
  "amount": 2000.00,
  "reasonValue": "personal_reason",
  "reasonText": "个人原因",
  "reasonDetail": null
}
```

响应：

```json
{
  "code": 200,
  "data": {
    "applicationId": 9876543210002,
    "orderId": 1234567890124,
    "orderNo": "HL2026052300002",
    "productName": "西藏拉萨布达拉宫 7 日游",
    "refundType": "DEPOSIT",
    "refundTypeLabel": "订金退款",
    "reasonText": "个人原因",
    "reasonDetail": null,
    "paidAmount": 5000.00,
    "calculatedAmount": 2000.00,
    "actualAmount": null,
    "policyId": null,
    "policyName": null,
    "refundRatio": null,
    "status": "PENDING",
    "statusLabel": "待审核",
    "applicantType": "ADMIN",
    "applicantId": 10086,
    "applicantName": "李小明",
    "departureDate": "2026-06-10",
    "daysBeforeDept": 18,
    "reviewAdminId": null,
    "reviewAdminName": null,
    "reviewRemark": null,
    "reviewedAt": null,
    "approvalNo": null,
    "createTime": "2026-05-23T10:35:00",
    "updateTime": "2026-05-23T10:35:00"
  },
  "message": "ok",
  "success": true
}
```

### 8.3 业务失败（异常）

场景说明：退款金额超过订金金额，触发 530007 错误。

请求：

```
POST /admin/order/refund/apply
Authorization: Bearer <admin-jwt>
Idempotent-Key: c3d4e5f6-a7b8-9012-cdef-123456789012
Content-Type: application/json
```

```json
{
  "orderId": 1234567890123,
  "amount": 9999.00,
  "reasonValue": "personal_reason",
  "reasonText": "个人原因"
}
```

响应：

```json
{
  "code": 530007,
  "message": "退款金额不能超过订金金额: amount=9999.00, deposit=2000.00",
  "success": false
}
```

## 9. 业务边界

- 适用场景：订单已支付（存在成功支付记录）时可发起退款申请
- 不适用场景：
  - 订单不存在 -> 返回 404
  - 订单未支付 -> 后端校验失败，返回业务错误
  - 同订单已有 ACTIVE 状态退款申请（PENDING/APPROVED/REFUNDING/APPEALING/APPEAL_APPROVED）-> 返回 530004 拒绝重复提交
  - amount 超过订单订金金额 -> 返回 530007
  - amount <= 0 -> 返回 400 参数校验失败
- 特殊边界：
  - 本接口不调退款政策计算，policyId/policyName/refundRatio 均为 null
  - 退款类型固定为 DEPOSIT，不允许客服指定其他类型
  - 申请创建后状态固定为 PENDING，需走 `PUT /admin/order/refund/{applicationId}/review` 主管审批流程
  - 5 秒内携带相同 Idempotent-Key 重复提交视为同一请求，返回第一次的结果（不重复创建）

## 10. 修改前后对比

全新接口，无修改前后对比。管理后台之前没有代用户发起退款的入口，本次为纯新增。

## 11. 影响评估 / 回滚

- 是否破坏向后兼容：否（全新接口，旧代码无感知）
- 前端是否必须同步上线：否（后端先上，前端可独立部署）

## 12. 注意事项

- Header 必须携带 `Idempotent-Key`（UUID 格式），防止客服误操作重复提交。前端在进入发起退款页时生成一个 UUID，整个提交流程复用同一个 key
- 退款原因字典值需提前获取：调本接口前先调 `GET /admin/order/refund/reason/list` 拿到可用的 reasonValue 列表，不可自行填写字典 key
- 金额校验上限为订单订金金额，超过返回 530007，message 含具体金额数值
- 提交成功后申请状态为 PENDING，可通过 applicationId 跳转至退款进度页，或按订单维度查询退款列表

## 13. 关联 / 联系人

### 13.1 链接

- **Issue**: [#2930](https://git.1814.love:8443/wx/HL/issues/2930)
- **PR**: [#2932](https://git.1814.love:8443/wx/HL/pulls/2932)
- **Merge commit**: [17e7fce90](https://git.1814.love:8443/wx/HL/commit/17e7fce90e5e4eb21e4e9c6bb4fb1d86e9c1fa21)
- **实现 commit**: [ca5e1a366](https://git.1814.love:8443/wx/HL/commit/ca5e1a36647edb01ba6c7b1337c07149c41665cb)

### 13.2 联系人

- **后端负责人**: @yaosutu
