From e3ca30311a9146bc14ac1db466be60af7dba664b Mon Sep 17 00:00:00 2001 From: yaosutu <770858045@qq.com> Date: Sat, 23 May 2026 10:51:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8E=A5=E5=8F=A3=EF=BC=9A?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=90=8E=E5=8F=B0=20admin=20H5=20=E5=AE=A2?= =?UTF-8?q?=E6=9C=8D=E4=BB=A3=E7=94=A8=E6=88=B7=E5=8F=91=E8=B5=B7=E9=80=80?= =?UTF-8?q?=E6=AC=BE=20POST=20/admin/order/refund/apply=EF=BC=88PR=20#2932?= =?UTF-8?q?=EF=BC=8CIssue=20#2930=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...min代用户发起退款接口-新增接口-管理后台.md | 366 ++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 changelogs-v2/2026-05/23_2930_admin代用户发起退款接口-新增接口-管理后台.md diff --git a/changelogs-v2/2026-05/23_2930_admin代用户发起退款接口-新增接口-管理后台.md b/changelogs-v2/2026-05/23_2930_admin代用户发起退款接口-新增接口-管理后台.md new file mode 100644 index 0000000..17447c5 --- /dev/null +++ b/changelogs-v2/2026-05/23_2930_admin代用户发起退款接口-新增接口-管理后台.md @@ -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 操作台查看订单后,代用户发起退款申请 +- **认证**:需要管理后台 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` + +### 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 +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 +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 +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