# mp-service: 保险/合同详情 5 个接口梳理 + 合同接口返回值强类型化 > **服务** hl-mp-service(端口 8085)→ 透传 hl-order-service-v2(端口 8094) > **PR**: wx/HL#1314 · **Issue**: wx/HL#1312 > **日期**: 2026-04-23 > **影响范围**: C 端「订单详情页」保险详情弹窗、合同详情、保单 PDF 下载 --- ## ⚠️ 关键变化 合同相关 3 个接口(`GET /mp/contract/list` / `GET /mp/contract/{id}` / `GET /mp/contract/by-order/{orderId}`)的返回值类型从后端弱类型 `Map` 改为具名 VO(`MpContractVO` / `MpContractDetailVO`)。**JSON 结构与字段名均未改变**,Swagger 上能看到具名 schema 了,前端无需改动;本 changelog 重点是一次性把这 5 个接口契约梳理清楚。 --- ## 一、接口清单 | # | 接口 | 方法 | 路径 | 返回类型 | |---|------|------|------|----------| | 1 | 订单详情聚合(含保险详情) | GET | `/mp/order/{orderId}/dashboard` | `MpOrderDashboardVO`(取 `.insurance` 字段) | | 2 | 合同详情(按订单) | GET | `/mp/contract/by-order/{orderId}` | `MpContractVO` | | 3 | 合同详情(按合同 ID) | GET | `/mp/contract/{id}` | `MpContractDetailVO` | | 4 | 订单保单合并 PDF | GET | `/mp/insurance/policy-pdf/{orderId}` | `Map`(透传 `PolicyShareVO`) | | 5 | 单张保单下载 | GET | `/mp/insurance/policy/{insuranceOrderId}/download` | `String`(Base64) | - 全部需要登录,`userId` 由 mp-service 从 token 中取出后透传给 order-v2 - 仅 5 号需要 `insuranceOrderId`,该 ID 来自订单详情出行人的 `insurancePolicies[].insuranceOrderId` --- ## 二、接口详情 ### 1. 保险详情 `GET /mp/order/{orderId}/dashboard` 保险详情没有独立接口,走订单详情聚合 `MpOrderDashboardVO`,取 `.insurance` 字段(`MpInsuranceDetailVO`)。未投保或查询失败时该字段为 `null`(降级,不阻断页面)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 说明 | |------|------|------|------|------| | `orderId` | Path | Long | ✅ | 订单 ID | #### 出参 `Result` — 聚合 VO 全字段 | 字段 | 类型 | 说明 | |------|------|------| | `order` | `MpOrderDetailVO` | 订单主体(详见 2026-04-17 订单详情 changelog) | | `preTripChecklist` | `MpPreTripChecklistVO` | 出发准备清单(降级时 null) | | `files.contract` | `MpContractSummaryVO` | 合同摘要(无合同 null) | | `files.invoice` | `MpInvoiceSummaryVO` | 发票摘要(未开票 null) | | `files.insurancePolicyPdfUrl` | `String` | 保单合并 PDF 下载路径,固定为 `/mp/insurance/policy-pdf/{orderId}` | | `insurance` | `MpInsuranceDetailVO` | **保险详情(本 changelog 重点字段)** | | `refund` | `RefundVO` | 退款模块(仅取消/退款中状态有值) | #### `insurance`(`MpInsuranceDetailVO`)字段 | 字段 | 类型 | 说明 | |------|------|------| | `schemeId` | Long | 方案 ID | | `schemeName` | String | 方案名称 | | `description` | String | 方案描述 | | `isOverseas` | Boolean | 是否境外 | | `totalDays` | Integer | 适用行程天数 | | `notice` | String | 保险告知状态:`INCLUDED` / `OPTIONAL` / `EXCLUDED` | | `segments` | `List` | 保障分段列表 | | `segments[].segmentName` | String | 分段名称 | | `segments[].dayOffsetStart` | Integer | 起始天 | | `segments[].dayOffsetEnd` | Integer | 结束天(`-1` = 最后一天) | | `segments[].productName` | String | 保险产品名称 | | `segments[].planName` | String | 保险计划名称 | | `policies` | `List` | 投保记录列表(未投保为空数组) | | `policies[].insuranceOrderId` | Long | 保险订单 ID(用于接口 5 下载) | | `policies[].policyNo` | String | 保单号 | | `policies[].productName` | String | 保险产品名称 | | `policies[].planName` | String | 计划名称 | | `policies[].premium` | BigDecimal | 保费(元) | | `policies[].insuredCount` | Integer | 被保人数 | | `policies[].coverageStartDate` | LocalDate | 保障开始日期 | | `policies[].coverageEndDate` | LocalDate | 保障结束日期 | | `policies[].status` | String | 保险状态:`PENDING` / `INSURING` / `INSURED` / `FAILED`(`CANCELLED` 已过滤不返回) | | `policies[].statusLabel` | String | 状态中文标签 | | `policies[].insuredPersons` | `List` | 被保人列表 | | `insuredPersons[].name` | String | 姓名(明文,脱敏由前端处理) | | `insuredPersons[].idCardType` | String | 证件类型:`ID_CARD` / `PASSPORT` | | `insuredPersons[].idCardNo` | String | 证件号码(明文) | | `insuredPersons[].birthday` | LocalDate | 出生日期 | | `insuredPersons[].gender` | String | `MALE` / `FEMALE` | | `insuredPersons[].phone` | String | 手机号(明文) | --- ### 2. 合同详情(按订单) `GET /mp/contract/by-order/{orderId}` 返回订单关联的**最新**有效合同(非作废),一般用于订单详情页的"合同"入口。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 说明 | |------|------|------|------|------| | `orderId` | Path | Long | ✅ | 订单 ID | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | `contractId` | Long | 合同 ID | | `orderId` | Long | 订单 ID | | `templateCode` | String | 模板编码 | | `templateName` | String | 模板名称 | | `contractNumber` | String | 合同编号 | | `platform` | String | 签约平台 | | `contractType` | String | 字典 `contract_type`:`TOUR` / `INSURANCE` | | `contractTypeLabel` | String | 合同类型标签 | | `mode` | String | 字典 `contract_mode`:`ONLINE` / `OFFLINE` | | `modeLabel` | String | 签约模式标签 | | `status` | String | 字典 `contract_status` | | `statusLabel` | String | 合同状态标签 | | `signUrl` | String | 签署 URL | | `qrCodeUrl` | String | 签署二维码 URL | | `fileUrl` | String | 合同文件 URL | | `agencyCode` | String | 旅行社编号 | | `travelAgencyName` | String | 旅行社名称 | | `destination` | String | 目的地 | | `departureDate` | LocalDate | 出发日期 | | `returnDate` | LocalDate | 返回日期 | | `totalAmount` | BigDecimal | 合同总金额 | | `touristCount` | Integer | 出行人数 | | `contactName` | String | 联系人姓名(明文,脱敏由前端处理) | | `contactPhone` | String | 联系人电话(明文,脱敏由前端处理) | | `createTime` | LocalDateTime | 创建时间 | 无合同时 `data` 为 `null`。 --- ### 3. 合同详情(按合同 ID) `GET /mp/contract/{id}` 按合同 ID 查完整详情,返回合同基本信息 + 出行人签署详情 + 状态变更日志。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 说明 | |------|------|------|------|------| | `id` | Path | Long | ✅ | 合同 ID | #### 出参 `Result` `MpContractDetailVO` 继承 `MpContractVO`(字段见接口 2),额外字段: | 字段 | 类型 | 说明 | |------|------|------| | `supplementaryClause` | String | 补充约定内容 | | `travelers` | `List` | 出行人列表 | | `travelers[].travelerId` | Long | 出行人 ID | | `travelers[].name` | String | 姓名(明文) | | `travelers[].idCardType` | String | `ID_CARD` / `PASSPORT` | | `travelers[].idCardNo` | String | 证件号码(明文) | | `travelers[].phone` | String | 手机号(明文) | | `travelers[].isSigner` | Boolean | 是否签署人 | | `statusLogs` | `List` | 状态变更日志 | | `statusLogs[].logId` | Long | 日志 ID | | `statusLogs[].contractId` | Long | 合同 ID | | `statusLogs[].oldStatus` | String | 旧状态 | | `statusLogs[].newStatus` | String | 新状态 | | `statusLogs[].source` | String | 变更来源:`CALLBACK` / `POLLING` / `MANUAL` | | `statusLogs[].rawPayload` | String | 原始载荷(仅调试用) | | `statusLogs[].createTime` | LocalDateTime | 创建时间 | 权限:合同 `userId` 必须与登录用户一致,否则 403。 --- ### 4. 订单保单合并 PDF `GET /mp/insurance/policy-pdf/{orderId}` 合并订单下所有出行人的有效保单为一个 PDF,返回 Base64。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 说明 | |------|------|------|------|------| | `orderId` | Path | Long | ✅ | 订单 ID | #### 出参 `Result>`(BFF 当前透传 order-v2 `PolicyShareVO`) | 字段 | 类型 | 说明 | |------|------|------| | `pdfBase64` | String | 合并后的保单 PDF(Base64 编码) | | `personName` | String | 出行人姓名(订单范围分享时一般为空/null) | | `policyCount` | Integer | 成功合并的保单数量 | | `failedCount` | Integer | 下载失败的保单数量 | --- ### 5. 单张保单下载 `GET /mp/insurance/policy/{insuranceOrderId}/download` 下载指定保险订单的单张保单 PDF,返回 Base64 字符串。`insuranceOrderId` 从接口 1 的 `insurance.policies[].insuranceOrderId` 取。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 说明 | |------|------|------|------|------| | `insuranceOrderId` | Path | Long | ✅ | 保险订单 ID(非订单 ID) | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | `data` | String | 保单 PDF(Base64 编码) | --- ## 三、响应示例 ### 接口 2 `GET /mp/contract/by-order/{orderId}` ```json { "code": 200, "message": "成功", "data": { "contractId": 9001, "orderId": 20439, "templateCode": "TOUR_STANDARD", "templateName": "旅游服务合同(标准版)", "contractNumber": "HL-2026-0423-0001", "platform": "fadada", "contractType": "TOUR", "contractTypeLabel": "旅游合同", "mode": "ONLINE", "modeLabel": "线上签署", "status": "SIGNED", "statusLabel": "已签署", "signUrl": "https://sign.fadada.com/xxx", "qrCodeUrl": "https://cdn.1814.love/qr-9001.png", "fileUrl": "https://cdn.1814.love/contract-9001.pdf", "agencyCode": "HULAL001", "travelAgencyName": "呼籁旅行", "destination": "呼伦贝尔", "departureDate": "2026-07-01", "returnDate": "2026-07-07", "totalAmount": "12600.00", "touristCount": 4, "contactName": "张三", "contactPhone": "13800138000", "createTime": "2026-04-20 10:30:00" }, "success": true } ``` ### 接口 4 `GET /mp/insurance/policy-pdf/{orderId}` ```json { "code": 200, "message": "成功", "data": { "pdfBase64": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PC9UeX...(截断)", "personName": null, "policyCount": 4, "failedCount": 0 }, "success": true } ``` --- ## 四、边界行为 - 未登录 → 401(网关拦截) - 订单/合同不属于当前 `userId` → 403 - 订单无合同(接口 2)→ `data: null` - 订单未投保(接口 1 的 `insurance` 字段)→ `insurance: null`,不阻断页面 - 保单 PDF 下载失败(接口 4)→ `failedCount > 0`,`pdfBase64` 仍返回已合并部分;下游整体失败时 BFF 返 `500` - 合同服务整体不可用(接口 2/3)→ BFF 返 `500 合同服务不可用,请稍后重试` --- ## 五、不影响范围 - **仅影响**:C 端订单详情页的保险详情弹窗 / 合同详情 / 保单 PDF 下载入口 - **零影响**: - 下单、支付、退款、发票所有接口 - 合同列表 `GET /mp/contract/list`(未列出,字段同接口 2 `MpContractVO`) - 管理后台合同/保险所有接口(不走 `/mp/` 前缀) - JSON 字段名与取值逻辑(本次仅后端 VO 强类型化) --- ## 六、相关历史 PR | PR | Issue | 说明 | 是否仍有效 | |----|-------|------|------------| | wx/HL#1314 | wx/HL#1312 | 合同接口返回值从 Map 改为强类型 VO(本 PR) | ✅ 最新 | | 2026-04-17 `mp-order-detail-refactor` | - | 订单详情聚合 VO `MpOrderDashboardVO` | ✅ 有效 | --- ## 七、代码位置 - Controller - `hl-mp-service` · `com.hulalv.mp.controller.MpOrderController#getOrderDashboard` - `hl-mp-service` · `com.hulalv.mp.controller.MpContractController` - `hl-mp-service` · `com.hulalv.mp.controller.MpInsuranceController` - VO - `hl-mp-service` · `com.hulalv.mp.vo.MpOrderDashboardVO` - `hl-mp-service` · `com.hulalv.mp.vo.MpInsuranceDetailVO` - `hl-mp-service` · `com.hulalv.mp.vo.MpContractVO` / `MpContractDetailVO` / `MpContractStatusLogVO` - 下游 - `hl-order-service-v2` · `com.hulalv.contract.controller.internal.InternalMpContractController` - `hl-order-service-v2` · `com.hulalv.insurance.controller.internal.InternalInsuranceController` - `hl-order-service-v2` · `com.hulalv.insurance.vo.PolicyShareVO` --- ## 八、相关文档 - 关联 Issue: [wx/HL#1312](https://git.1814.love:8443/wx/HL/issues/1312) - 关联 PR: [wx/HL#1314](https://git.1814.love:8443/wx/HL/pulls/1314) - 订单详情聚合 VO 参考: `changelogs/2026-04/2026-04-17_order-v2_mp-order-detail-refactor.md`