diff --git a/changelogs-v2/2026-08/04_5460_终止行程幂等重放契约恢复-修改接口-管理后台.md b/changelogs-v2/2026-08/04_5460_终止行程幂等重放契约恢复-修改接口-管理后台.md new file mode 100644 index 0000000..d2b7c66 --- /dev/null +++ b/changelogs-v2/2026-08/04_5460_终止行程幂等重放契约恢复-修改接口-管理后台.md @@ -0,0 +1,147 @@ +--- +schema: "hl-changelog/v2" +ticket: "5460" +title: "终止行程接口幂等重放契约恢复:同正文重放返回既有结果,不同正文返回 581049" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "2026-08-04" +status_note: "行为修复:恢复 #5379 已承诺的终止行程幂等重放语义(#5379 最终合并版丢失了早期迭代的幂等实现,导致首次终止成功后同正文重放被 581018 提前拦截、无法取回既有结果)。PR #5468 已合并 dev-v3 并部署 TEST,网关已验证:新订单首次终止 200、同正文重放返回既有 terminateRefundId/finalRefund、不同边界/正文返回 581049(修复前同场景实测 581018)。" +updated_at: "2026-08-04" +base: "dev-v3" +--- + +# 终止行程接口幂等重放契约恢复:同正文重放返回既有结果,不同正文返回 581049 + +> **服务**: hl-order-service-v3 +> **PR**: #5468 +> **Issue**: #5460 +> **日期**: 2026-08-04 +> **影响范围**: 管理后台订单终止行程(出行中终止)提交接口的重试/重放行为 + +--- + +## ⚠️ 关键变化 + +**#5379 契约承诺的幂等重放语义此前在 TEST 实测失效**:首次终止提交成功但响应丢失后,对同一订单原样重放同一请求,接口返回 `581018「出行中取消仅适用于出行中订单」`,而不是返回既有终止结果;用不同正文重试也返回 581018 而不是 `581049`。**现已修复**,行为与 #5379 changelog 契约一致: + +| 场景 | 修复前实测 | 修复后 | +|------|-----------|--------| +| 相同正文重放(首次已成功) | 581018(错误) | `200`,返回既有终止结果(含 terminateRefundId / finalRefund) | +| 不同正文 / 不同终止边界重试 | 581018(错误) | `581049` 终止行程重试与首次终止边界或请求不一致 | + +前端无需改动,但**超时/网络重试逻辑现在可以依赖该接口的幂等语义**:重试时原样复用首次请求正文即可取回既有结果,不要修改正文内容(包括 `vehicles[].used` 与 `lineUsages[].used`)。 + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 终止行程 | POST | `/v3/admin/order/:id/terminate` | 行为修复 | 幂等重放语义恢复(契约不变,见 #5379) | + +## 三、接口详情 + +### 1. 终止行程 `POST /v3/admin/order/:id/terminate` + +**请求体**: `OrderTerminateTripReqVO`(字段、必填性、约束均不变,与 #5379 一致) + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `endDayNumber` | Integer | ✅ | 停在第几天(1-based);**重试必须与首次完全一致** | +| `cancelReason` | String | ✅ | 终止原因;重试必须与首次完全一致 | +| `adjustAmount` | BigDecimal | 否 | 人工调整额;重试必须与首次一致(0 与 null 视为等价) | +| `lineUsages[]` | Array | 否 | 统一资源行已用判定;重试必须与首次一致 | +| `rooms[]` / `tickets[]` / `services[]` / `supplies[]` | Array | 否 | 兼容分组入参;重试必须与首次一致 | +| `vehicles[]` | Array | 否 | 用车行(refId/dayNumber/used);used 仍参与重放一致性校验,金额以后端 Fleet 事实为准 | + +**业务边界(重放判定规则)**: + +1. 订单为 TRAVELLING 时:正常执行首次终止 → `200`,订单变 COMPLETED。 +2. 订单已 COMPLETED(已终止)时,**先判定重放身份**,不再直接返回 581018: + - 同边界 + 同正文(请求指纹一致)→ `200`,返回既有终止结果:`terminateRefundId`、`baselineRefund`、`adjustAmount`、`finalRefund`、`settlementRefundId`、`newStatus=COMPLETED`、`newFlowStatus=PENDING_REVIEW`;不产生任何副作用(不重复写退款、不重复发事件)。 + - 同边界但正文不一致,或边界不一致(`endDayNumber` 等不同)→ `581049 终止行程重试与首次终止边界或请求不一致`。 + - 升级前已终止的订单(无指纹快照):按主表与明细快照做语义等价校验,等价重放同样返回既有结果,不等价返回 581049。 +3. `endDayNumber` 超出订单行程 → `581047`(不变);车辆费用不可用 → `584100` fail closed(不变)。 + +**错误码**: + +| 错误码 | 含义 | 触发条件 | +|--------|------|----------| +| 581018 | 出行中取消仅适用于出行中订单 | 订单非 TRAVELLING 且**从未终止**(自然结束/已取消等)时提交终止 | +| 581049 | 终止行程重试与首次终止边界或请求不一致 | 已终止订单以不同边界/不同正文重试 | +| 581047 | 终止行程:结束日不在订单行程范围内 | endDayNumber 越界 | +| 581044 | 终止行程:结束日之前的资源必须标记为已使用 | 结束日前资源 used=false | + +**典型成功示例(同正文重放)**: + +请求: + +```json +POST /v3/admin/order/2084280231786430466/terminate +Authorization: Bearer +Content-Type: application/json + +{ + "cancelReason": "客户高反送医终止", + "endDayNumber": 2, + "adjustAmount": 0, + "lineUsages": [ + { "lineKey": "HOTEL_ASSIGNMENT:96011:1", "used": true }, + { "lineKey": "ITINERARY_NODE:97010:1", "used": true } + ], + "vehicles": [ + { "refId": 9000004500000001, "dayNumber": 1, "used": true } + ] +} +``` + +响应(首次终止与同正文重放均为此响应,重放时 terminateRefundId/finalRefund 与首次一致): + +```json +{ + "code": 200, + "data": { + "terminateRefundId": "2084497785524011009", + "baselineRefund": "6500.00", + "adjustAmount": "0.00", + "finalRefund": "6500.00", + "settlementRefundId": "2084497785561759746", + "newStatus": "COMPLETED", + "newFlowStatus": "PENDING_REVIEW" + }, + "success": true +} +``` + +**异常示例(不同正文重试)**: + +```json +{ + "code": 581049, + "message": "终止行程重试与首次终止边界或请求不一致", + "success": false +} +``` + +## 四、验证证据 + +- `mvn -pl hl-order-service-v3 -am verify` 全绿(含 spotless + 全部单元/集成测试;新增幂等重放回归:首次终止写指纹 → 同正文重放 → 不同正文冲突三态)。 +- TEST 网关验证(wx 定制师,2026-08-04):新订单首次终止 200 且写指纹;同正文重放返回同一 terminateRefundId/finalRefund;不同 endDayNumber 返回 581049;升级前已终止订单同语义重放返回既有 terminateRefundId/finalRefund、不同边界返回 581049(修复前同场景实测 581018)。 +- 已部署 TEST(dev-v3 滚动部署,双实例健康)。 + +## 五、相关文档 + +- 关联 Issue: [wx/HL#5460](https://git.1814.love:8443/wx/HL/issues/5460) +- 关联 PR: [wx/HL#5468](https://git.1814.love:8443/wx/HL/pulls/5468) +- 契约基线: [03_5379_终止行程车辆退款权威口径-修改接口-管理后台.md](./03_5379_终止行程车辆退款权威口径-修改接口-管理后台.md) + +## 关联 / 联系人 + +### 联系人 + +- **后端负责人**: @wx