- 终止退款预览:新增 POST /v3/admin/order/{id}/terminate/refund-preview,返回资源清单+行程信息
- 终止行程接口改造:入参改资源已用判定+调整额,出参补退款明细字段(破坏性,前端必改)
160 行
7.1 KiB
Markdown
160 行
7.1 KiB
Markdown
# 【✨新增接口·管理后台】终止行程·退款预览 (#3556)
|
||
|
||
> **PR**: #3557 | **服务**: hl-order-service-v3 | **更新时间**: 2026-06-06
|
||
|
||
## 1. 接口背景
|
||
|
||
出行中订单因特殊原因需要提前终止行程时,需要给定制师一个「按已用资源算应退多少」的界面。本接口提供终止退款弹框所需的**全部资源清单 + 行程时间轴信息**,定制师选择「停在第几天」后,由前端本地标记每项资源已用/未用并实时算出退款额,最终在终止提交接口落账。
|
||
|
||
## 2. 变更清单
|
||
|
||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||
|---|------|------|------|----------|------|
|
||
| 1 | 终止退款预览 | POST | `/v3/admin/order/{id}/terminate/refund-preview` | 新增接口 | 返回终止退款弹框所需资源清单 + 行程信息 |
|
||
|
||
## 3. 接口详情
|
||
|
||
### 3.1 终止退款预览
|
||
|
||
- **使用场景**:出行中订单点击「终止行程」打开退款弹框时调用一次,拿到整趟全部资源清单
|
||
- **认证**:需要管理端 JWT(`Authorization` 头)
|
||
- **幂等性**:是(只读查询,不落库)
|
||
- **限流**:无
|
||
|
||
## 4. 接口入参
|
||
|
||
### 4.1 路径参数
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| id | String | ✅ | 订单 ID |
|
||
|
||
### 4.2 请求体字段
|
||
|
||
无请求体。前端**一次性**拿到整趟全部资源,「停在第几天」的选择与已用/未用标记、退款金额计算全部在前端本地完成,**不需要每选一天再调本接口**。
|
||
|
||
## 5. 出参(响应)
|
||
|
||
### 5.1 响应字段(`data`)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| paidAmount | BigDecimal | 已付金额(公司账上已收,前端算退款基线用) |
|
||
| tripDays | Integer | 行程总天数(画时间轴用,如 7 天 6 晚 = 7) |
|
||
| departDate | String(date) | 出发日期(`yyyy-MM-dd`,第 N 天日期 = 出发日 + N−1) |
|
||
| tripCurrentDay | Integer | 当前已出行到第几天(时间轴默认选中点) |
|
||
| rooms | Array<ResourceItem> | 住宿资源清单(每晚每组房一行) |
|
||
| tickets | Array<ResourceItem> | 门票资源清单(每景点每天一行) |
|
||
| vehicles | Array<ResourceItem> | 用车资源清单(**整车已按天展开,每天一行**) |
|
||
| insurance | Array<ResourceItem> | 保险清单(整单一行,锁定不退) |
|
||
|
||
### 5.2 ResourceItem 字段(rooms / tickets / vehicles / insurance 各行通用)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| refId | String | 资源记录 ID(提交终止时按此回传已用判定;用车多天共用同一 refId,靠 dayNumber 区分;保险可能为 null) |
|
||
| name | String | 资源名称(酒店名 / 景点名 / 车型 / 保险产品名) |
|
||
| dayNumber | Integer | 对应行程第几天(保险为 null) |
|
||
| dealPrice | BigDecimal | 成交单价(住宿=每间每晚价 / 门票=每张价 / 用车=每天每辆日租 / 保险=保费) |
|
||
| quantity | Integer | 数量(住宿=房间数 / 门票=张数 / 用车=车辆数 / 保险=1) |
|
||
| totalAmount | BigDecimal | 本行小计(= dealPrice × quantity) |
|
||
| locked | Boolean | 是否锁定不退(保险恒 true,其余 false) |
|
||
| lockedReason | String | 锁定原因(如「保险已生效不退」;未锁定为 null) |
|
||
|
||
> 响应**不含**「是否已用」标记与「退款合计」——这些由前端按所选结束天(`dayNumber ≤ 结束天` 默认已用,之后默认可退)实时计算。
|
||
|
||
## 6. 枚举 / 数据字典
|
||
|
||
本接口无枚举字段。资源分类通过 `rooms` / `tickets` / `vehicles` / `insurance` 四个数组区分,无独立 `resourceType` 字段。
|
||
|
||
## 7. 错误码
|
||
|
||
| code | 含义 | 触发场景 |
|
||
|------|------|----------|
|
||
| 581018 | 出行中取消仅适用于出行中订单 | 订单当前状态非「出行中」时调用本接口 |
|
||
|
||
## 8. 示例
|
||
|
||
### 8.1 典型成功
|
||
|
||
**请求**:
|
||
```
|
||
POST /v3/admin/order/60001234567890/terminate/refund-preview
|
||
Authorization: Bearer {token}
|
||
(无请求体)
|
||
```
|
||
|
||
**响应**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "ok",
|
||
"success": true,
|
||
"data": {
|
||
"paidAmount": 6000.00,
|
||
"tripDays": 7,
|
||
"departDate": "2026-05-12",
|
||
"tripCurrentDay": 3,
|
||
"rooms": [
|
||
{"refId": "96011", "name": "拉萨瑞吉度假酒店·大床房", "dayNumber": 1, "dealPrice": 1280.00, "quantity": 1, "totalAmount": 1280.00, "locked": false, "lockedReason": null},
|
||
{"refId": "96014", "name": "拉萨瑞吉度假酒店·大床房", "dayNumber": 4, "dealPrice": 1280.00, "quantity": 1, "totalAmount": 1280.00, "locked": false, "lockedReason": null}
|
||
],
|
||
"tickets": [
|
||
{"refId": "97001", "name": "布达拉宫门票", "dayNumber": 2, "dealPrice": 200.00, "quantity": 2, "totalAmount": 400.00, "locked": false, "lockedReason": null}
|
||
],
|
||
"vehicles": [
|
||
{"refId": "98001", "name": "丰田赛那 商务车", "dayNumber": 1, "dealPrice": 600.00, "quantity": 1, "totalAmount": 600.00, "locked": false, "lockedReason": null},
|
||
{"refId": "98001", "name": "丰田赛那 商务车", "dayNumber": 2, "dealPrice": 600.00, "quantity": 1, "totalAmount": 600.00, "locked": false, "lockedReason": null},
|
||
{"refId": "98001", "name": "丰田赛那 商务车", "dayNumber": 3, "dealPrice": 600.00, "quantity": 1, "totalAmount": 600.00, "locked": false, "lockedReason": null}
|
||
],
|
||
"insurance": [
|
||
{"refId": null, "name": "安联境内旅行险·尊享版", "dayNumber": null, "dealPrice": 256.00, "quantity": 1, "totalAmount": 256.00, "locked": true, "lockedReason": "保险已生效不退"}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 8.2 边界情况
|
||
|
||
**场景说明**:某天住两个酒店(同一 dayNumber 出现多行)。
|
||
|
||
**响应(rooms 片段)**:
|
||
```json
|
||
"rooms": [
|
||
{"refId": "96021", "name": "林芝希尔顿·双床房", "dayNumber": 2, "dealPrice": 980.00, "quantity": 1, "totalAmount": 980.00, "locked": false, "lockedReason": null},
|
||
{"refId": "96022", "name": "林芝工布庄园·大床房", "dayNumber": 2, "dealPrice": 880.00, "quantity": 1, "totalAmount": 880.00, "locked": false, "lockedReason": null}
|
||
]
|
||
```
|
||
|
||
### 8.3 业务失败(异常)
|
||
|
||
**场景说明**:订单非出行中(如已确认待出发)时调用。
|
||
|
||
**响应**:
|
||
```json
|
||
{ "code": 581018, "message": "出行中取消仅适用于出行中订单", "success": false }
|
||
```
|
||
|
||
## 9. 业务边界
|
||
|
||
- ✅ **适用场景**:订单当前状态 = 出行中(TRAVELLING)
|
||
- ❌ **不适用场景**:未出发(待支付 / 待完善 / 已确认待出发)→ 返回 581018;这些状态请走「取消订单」流程,不走终止退款
|
||
- ⚠️ **特殊边界**:
|
||
- 保险行恒 `locked=true`,不参与退款
|
||
- 同一天可有多行(多酒店 / 多门票 / 多车),按 refId 区分
|
||
- 用车一段车按行程天数展开为多行,多行共用同一 refId
|
||
|
||
## 13. 关联 / 联系人
|
||
|
||
### 13.1 链接
|
||
|
||
- **Issue**: [#3556](https://git.1814.love:8443/wx/HL/issues/3556)
|
||
- **PR**: [#3557](https://git.1814.love:8443/wx/HL/pulls/3557)
|
||
- **Merge commit**: [773dcdacf](https://git.1814.love:8443/wx/HL/commit/773dcdacf)
|
||
- **关联 PR(预览状态校验加固)**: [#3559](https://git.1814.love:8443/wx/HL/pulls/3559)
|
||
|
||
### 13.2 联系人
|
||
|
||
- **后端负责人**: @yst
|
||
- **前端对接(管理后台)**: 终止行程弹框页面
|