# mp-service: 订单保险详情独立接口契约梳理

> **服务**: hl-mp-service（端口 8085）→ 透传 hl-order-service-v2（端口 8094）
> **PR**: -（纯契约梳理，无代码变更）
> **Issue**: -
> **日期**: 2026-04-23
> **影响范围**: C 端「订单详情 - 服务包含 - 保险」子详情入口

---

## ⚠️ 关键变化

**无接口变更**。本 changelog 只梳理 `GET /mp/order/{orderId}/insurance` 的入参/出参契约，方便前端对照接入。

⚠️ **路径提醒**：Controller 类名为 `MpOrderServiceDetailController`，但 `@RequestMapping` 是 `/mp/order`（不是 `/mp/order-service`）。完整路径以本文档为准。

---

## 一、接口基本信息

| 项 | 值 |
|----|----|
| 路径 | `/mp/order/{orderId}/insurance` |
| 方法 | GET |
| Controller | `MpOrderServiceDetailController#getInsurance` |
| 返回类型 | `Result<MpInsuranceDetailVO>` |
| 登录 | 需要（网关 token 解析 userId 透传）|
| 缓存 | 无 `@MpCache` 注解 |

---

## 二、调用时机与上下游关系

| 场景 | 推荐接口 |
|------|---------|
| 订单详情页首屏（聚合） | `GET /mp/order/{orderId}/dashboard` → 取 `.insurance` 字段 |
| 从「服务包含」列表点「保险」进子详情 | **`GET /mp/order/{orderId}/insurance`**（本接口）|
| 下载保单 PDF（整单合并） | `GET /mp/insurance/policy-pdf/{orderId}` |
| 下载单张保单 PDF | `GET /mp/insurance/policy/{insuranceOrderId}/download` |

两条路径返回同一个 VO（`MpInsuranceDetailVO`），字段结构一致。**不要在订单详情页同时调 dashboard 和本接口**。

`insuranceOrderId`（下载单张保单用）从本接口返回的 `policies[].insuranceOrderId` 取。

---

## 三、入参

### 路径参数

| 字段 | 位置 | 类型 | 必填 | 说明 |
|------|------|------|------|------|
| `orderId` | Path | Long | ✅ | 订单ID |

### Header

| 字段 | 必填 | 说明 |
|------|------|------|
| 登录 token | ✅ | 由网关校验并解析出 `userId`，前端无需手动传 `userId` |

---

## 四、出参 `Result<MpInsuranceDetailVO>`

### 顶层 `MpInsuranceDetailVO`

| 字段 | 类型 | 说明 |
|------|------|------|
| `schemeId` | Long | 方案ID |
| `schemeName` | String | 方案名称 |
| `description` | String | 方案描述 |
| `isOverseas` | Boolean | 是否境外 |
| `totalDays` | Integer | 适用行程天数 |
| `notice` | String | 保险告知状态：`INCLUDED` / `OPTIONAL` / `EXCLUDED` |
| `segments` | `List<CoverageSegment>` | 保障分段列表 |
| `policies` | `List<PolicyItem>` | 投保记录列表（未付款为空数组）|

### `CoverageSegment` — 保障分段（方案层）

| 字段 | 类型 | 说明 |
|------|------|------|
| `segmentName` | String | 分段名称 |
| `dayOffsetStart` | Integer | 起始天 |
| `dayOffsetEnd` | Integer | 结束天（`-1` = 最后一天）|
| `productName` | String | 保险产品名称 |
| `planName` | String | 保险计划名称 |

### `PolicyItem` — 投保记录（订单层，已付款才有）

| 字段 | 类型 | 说明 |
|------|------|------|
| `insuranceOrderId` | Long | 保险订单ID（下载单张保单用）|
| `policyNo` | String | 保单号 |
| `productName` | String | 保险产品名称 |
| `planName` | String | 保险计划名称 |
| `premium` | BigDecimal | 保费（元）|
| `insuredCount` | Integer | 被保人数 |
| `coverageStartDate` | LocalDate | 保障开始日期 |
| `coverageEndDate` | LocalDate | 保障结束日期 |
| `status` | String | `PENDING` / `INSURING` / `INSURED` / `FAILED`（`CANCELLED` 已由后端过滤不返回）|
| `statusLabel` | String | 状态中文标签 |
| `insuredPersons` | `List<InsuredPerson>` | 被保人列表 |

### `InsuredPerson` — 被保人（证件/电话已脱敏）

| 字段 | 类型 | 说明 |
|------|------|------|
| `name` | String | 姓名 |
| `idCardType` | String | `ID_CARD` / `PASSPORT` |
| `idCardNo` | String | 证件号码（脱敏）|
| `birthday` | LocalDate | 出生日期 |
| `gender` | String | `MALE` / `FEMALE` |
| `phone` | String | 手机号（脱敏）|

---

## 五、响应示例

### 已投保订单

```json
{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "schemeId": 101,
    "schemeName": "境内 7 日综合保障",
    "description": "含意外/医疗/行李/延误",
    "isOverseas": false,
    "totalDays": 7,
    "notice": "INCLUDED",
    "segments": [
      {
        "segmentName": "全程",
        "dayOffsetStart": 1,
        "dayOffsetEnd": -1,
        "productName": "平安境内旅行险",
        "planName": "豪华计划"
      }
    ],
    "policies": [
      {
        "insuranceOrderId": 20439,
        "policyNo": "PICC2026042300001",
        "productName": "平安境内旅行险",
        "planName": "豪华计划",
        "premium": 38.00,
        "insuredCount": 2,
        "coverageStartDate": "2026-05-01",
        "coverageEndDate": "2026-05-07",
        "status": "INSURED",
        "statusLabel": "已生效",
        "insuredPersons": [
          {
            "name": "张三",
            "idCardType": "ID_CARD",
            "idCardNo": "1101**********1234",
            "birthday": "1990-01-01",
            "gender": "MALE",
            "phone": "138****5678"
          }
        ]
      }
    ]
  }
}
```

### 未付款订单（policies 为空数组）

```json
{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "schemeId": 101,
    "schemeName": "境内 7 日综合保障",
    "isOverseas": false,
    "totalDays": 7,
    "notice": "INCLUDED",
    "segments": [ /* 方案分段仍有值 */ ],
    "policies": []
  }
}
```

### 订单不含保险（notice=EXCLUDED）

```json
{
  "code": 200,
  "data": {
    "schemeId": null,
    "schemeName": null,
    "notice": "EXCLUDED",
    "segments": [],
    "policies": []
  }
}
```

---

## 六、边界行为

- 未登录 → 401（网关拦截）
- `orderId` 不属于当前 userId → 业务异常，`Result.code != 200`
- 订单存在但未付款 → `policies=[]`，但 `segments` / `schemeName` 仍返回（方案信息属于产品快照）
- 订单不含保险（`notice=EXCLUDED`）→ `policies=[]` 且 `segments=[]`
- `status=CANCELLED` 的保单 → **后端已过滤**，不会出现在 `policies` 中

---

## 七、契约约束

- 下载单张保单时的 `insuranceOrderId` **必须**取自本接口返回的 `policies[].insuranceOrderId`，不要用 `orderId` 替代
- `dayOffsetEnd=-1` 是约定值，表示"保障到行程最后一天"，非真实天数
- `premium` 是 `BigDecimal`，JSON 序列化为数字（非字符串），精度 2 位小数

---

## 八、不影响范围

- **仅新增文档梳理**，代码零改动
- `/mp/order/{orderId}/dashboard` 聚合接口中的 `.insurance` 字段与本接口返回 VO 完全一致，两者**可互换取用**
- 保单 PDF 下载两个接口（`/mp/insurance/policy-pdf/{orderId}` 和 `/mp/insurance/policy/{insuranceOrderId}/download`）与本接口是上下游关系，未做变更

---

## 九、相关文档

- `MpOrderServiceDetailController.java`（hl-mp-service）
- VO：`MpInsuranceDetailVO`（hl-mp-service + hl-order-service-v2 镜像一致）
- 关联 changelog：
  - `2026-04-23_mp-dashboard-endpoints-timing-guide.md`（订单/行程 5 个聚合接口调用时机梳理）
  - `2026-04-23_mp-service_contract-insurance-api-inventory.md`（保险/合同详情 5 接口梳理）
