# 小程序订单 - 保险弹窗接口 + hotels/serviceItems 字段修复 - **日期**: 2026-04-19 - **PR**: - [#885](https://git.1814.love:8443/wx/HL/pulls/885) hotels coverUrl 回填 - [#936](https://git.1814.love:8443/wx/HL/pulls/936) 保险弹窗 + clickable=true + 过滤 CANCELLED + 被保人 - **类型**: FEATURE + BUGFIX - **状态**: 已合并到 dev + 测试环境部署中 - **服务**: hl-mp-service + hl-order-service-v2 --- ## 一、接口变更清单 | # | 方法 | 路径 | 变更类型 | 影响位置 | 变更内容 | |---|------|------|---------|---------|---------| | 1 | GET | `/mp/order/{orderId}/insurance` | 🟢 **新增接口** | — | 保险详情弹窗 | | 2 | GET | `/mp/order/{orderId}/hotels` | 🔴 **出参修复** | `data.items[].coverUrl` | 已分配路径此前硬编码 null, 现从产品快照反查返回图片 URL | | 3 | POST `/mp/order/create`
GET `/mp/order/{id}`
PUT `/mp/order/{id}/edit` | 🔴 **出参字段值变化** | `data.serviceItems[type=INSURANCE].clickable` | 此前 INCLUDED 时返回 false, 现统一返回 true | 所有接口**入参无变化**。 --- ## 二、接口详情 ### 1. 🟢 新增: `GET /mp/order/{orderId}/insurance` 保险详情弹窗数据。 #### 鉴权 - Header: `Authorization: Bearer {mp-token}` - 订单必须属于当前用户, 否则返回 `code=403` #### 路径参数 | 参数 | 位置 | 类型 | 必填 | 说明 | |------|------|------|------|------| | `orderId` | path | Long | 是 | 订单 ID | #### Query 参数 无。 #### Request Body 无。 #### 响应 Schema ```typescript { code: number; // 200 成功 / 403 无权 / 404 订单不存在 message: string; data: MpInsuranceDetailVO | null; // 订单无保险时为 null success: boolean; } interface MpInsuranceDetailVO { // ===== 方案基础信息 ===== schemeId: string; // 方案 ID (String 防 JS 大数丢精度) schemeName: string; // 方案名 description: string | null; // 方案描述 isOverseas: boolean; // 境内/境外 totalDays: number; // 适用行程天数 notice: 'INCLUDED' | 'OPTIONAL'; // INCLUDED=必含, OPTIONAL=可选 // ===== 保障分段(方案配置, 与订单无关) ===== segments: CoverageSegment[]; // ===== 投保记录(订单付款后才有) ===== policies: PolicyItem[]; } interface CoverageSegment { segmentName: string; // 分段名 "低风险"/"高风险" dayOffsetStart: number; // 起始天(1 起) dayOffsetEnd: number; // 结束天(-1 表示最后一天) productName: string; // 保险产品名 planName: string; // 保障计划名 } interface PolicyItem { insuranceOrderId: string; // 保险订单 ID (String, 用于下载单张保单) policyNo: string; // 保单号 productName: string; planName: string; premium: number; // 保费(元) insuredCount: number; // 被保人数 coverageStartDate: string; // 起保日 yyyy-MM-dd coverageEndDate: string; // 截止日 status: 'PENDING' | 'INSURING' | 'INSURED' | 'FAILED'; // CANCELLED 不返回 statusLabel: string; // 状态中文 insuredPersons: InsuredPerson[]; } interface InsuredPerson { name: string; // 姓名 idCardType: 'ID_CARD' | 'PASSPORT'; idCardNo: string; // 证件号(后端已脱敏: 110***********1234) birthday: string; // 生日 yyyy-MM-dd gender: 'MALE' | 'FEMALE'; phone: string; // 手机号(后端已脱敏: 138****5678) } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": { "schemeId": "2028762847782060034", "schemeName": "测试3日方案", "description": null, "isOverseas": false, "totalDays": 3, "notice": "INCLUDED", "segments": [ { "segmentName": "低风险", "dayOffsetStart": 1, "dayOffsetEnd": 2, "productName": "山河令(太保山东新)", "planName": "10万计划" }, { "segmentName": "高风险", "dayOffsetStart": 3, "dayOffsetEnd": 3, "productName": "平安保游万里行马术专属保险(互联网)", "planName": "计划一" } ], "policies": [ { "insuranceOrderId": "9100000000001001", "policyNo": "BY202604190001", "productName": "山河令(太保山东新)", "planName": "10万计划", "premium": 50.00, "insuredCount": 2, "coverageStartDate": "2026-05-28", "coverageEndDate": "2026-05-29", "status": "INSURED", "statusLabel": "已承保", "insuredPersons": [ { "name": "张三", "idCardType": "ID_CARD", "idCardNo": "110***********2222", "birthday": "1990-01-01", "gender": "MALE", "phone": "138****8001" }, { "name": "李四", "idCardType": "ID_CARD", "idCardNo": "110***********3333", "birthday": "1995-02-02", "gender": "FEMALE", "phone": "138****8002" } ] } ] }, "success": true } ``` #### 几种特殊返回 | 场景 | 返回 | |------|------| | 订单不存在 | `code=404, data=null, message="订单不存在"` | | 订单不属于当前用户 | `code=403, data=null, message="无权查看此订单"` | | 订单未配保险 (`insuranceNotice=EXCLUDED`) 或无 INSURANCE 服务项 | `code=200, data=null` | | 订单配了保险但**未付款** | `code=200, data.policies=[]` (只有方案+分段, 无保单) | | 订单已付款, 正在投保中 | `code=200, data.policies` 含 `status=PENDING` 或 `INSURING` 的条目 | | 一订单多保单 (分段投保 / 退保重保) | `code=200, data.policies` 返回多条, **CANCELLED 已过滤不返回** | | 方案在数据库被删 | `code=200, data=null` (兜底) | --- ### 2. 🔴 修复: `GET /mp/order/{orderId}/hotels` #### 变更位置 出参 `data.items[].coverUrl` #### 变更前 已分配路径 (订单已确认实际入住酒店时) 返回: ```json { "items": [ { "hotelId": "xxx", "hotelName": "某酒店", "coverUrl": null, "assignmentDate": "2026-05-28", "roomType": "大床房", "hotelType": "HOTEL" } ] } ``` #### 变更后 ```json { "items": [ { "hotelId": "xxx", "hotelName": "某酒店", "coverUrl": "https://cdn.../hotel-cover.jpg", "assignmentDate": "2026-05-28", "roomType": "大床房", "hotelType": "HOTEL" } ] } ``` #### 变更原因 订单分配表 `order_hotel_assignment` 不存 coverUrl, 此前硬编码 null. 现按 hotelId 从产品快照 `hotels[]` 里反查图片 URL. 未分配路径行为不变. #### 其他字段 无变化 (`hotelId`/`hotelName`/`assignmentDate`/`roomType`/`hotelType` 及同级 `nightCount`/`roomTypeStats` 均无改动)。 --- ### 3. 🔴 字段值变化: `serviceItems[type=INSURANCE].clickable` #### 影响接口 - `POST /mp/order/create` 返回 `data.serviceItems` - `GET /mp/order/{orderId}` 返回 `data.serviceItems` - `PUT /mp/order/{orderId}/edit` 返回 `data.serviceItems` #### 变更位置 `data.serviceItems` 数组中 `type === 'INSURANCE'` 的元素的 `clickable` 字段。 #### 变更前 | `insuranceNotice` | `clickable` | |---|---| | `EXCLUDED` | 条目不返回 | | `INCLUDED` (必含保险) | **false** | | `OPTIONAL` (可选加保) | true | #### 变更后 | `insuranceNotice` | `clickable` | |---|---| | `EXCLUDED` | 条目不返回 (不变) | | `INCLUDED` | **true** (与 OPTIONAL 统一) | | `OPTIONAL` | true (不变) | #### 变更原因 新增了保险弹窗接口 `/mp/order/{id}/insurance` 后, INCLUDED 也有点击交互需求 (查看方案 + 保单列表), 不再需要前端禁用这个条目。 #### 历史订单 在本次改动之前已下单的订单, `order_service_item` 表里固化的 `clickable` 仍是原值 (可能 false), 新下单的订单起是 true。如果需要批量修正历史数据, 可提 Issue 加补偿脚本。 --- ## 三、不兼容变更 **无**。 - 新增接口: 老前端不调用不影响 - hotels `coverUrl`: 从 null 变为有值, 前端原本 null 态处理代码仍然正确 - `clickable`: 从 false 变为 true 只是让条目变可点, 旧代码不点它也不会出错