新增接口:管理后台 admin H5 客服代用户发起退款 POST /admin/order/refund/apply(PR #2932,Issue #2930)

这个提交包含在:
yaosutu 2026-05-23 10:51:56 +08:00
父节点 f026b7fa04
当前提交 e3ca30311a

查看文件

@ -0,0 +1,366 @@
# [新增接口·管理后台] 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 操作台查看订单后,代用户发起退款申请
- **认证**:需要管理后台 JWTBearer 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