# 卡派查价 — 第三方系统对接指南 本文档面向**宿主业务系统**(如美美与共)开发方,说明如何通过 HTTP API 接入实时卡派(LTL)询价。 > 接口速查见 [`API.md`](./API.md)。示例 JSON 见 [`host-candidates-req.example.json`](./host-candidates-req.example.json)。 --- ## 1. 对接前准备 | 项 | 说明 | |----|------| | **Base URL** | 由我方提供,例如 `https://if.dev.51track.vip` | | **推荐方式** | **方式 A**:两步宿主接口,免 Token,服务端同步返回报价 | | **备用方式** | **方式 B**:Token + 三步接口 + 宿主自行轮询(后期启用鉴权时使用) | | **协议** | HTTPS + JSON,UTF-8 | **服务器开关(我方运维配置):** ```env HOST_PUBLIC_API_ENABLED=true HOST_PUBLIC_DEFAULT_CUSTOMER_ID=CUST_001 ``` --- ## 2. 方式 A:两步接口(推荐,美美与共当前使用) ### 2.1 流程概览 ```mermaid sequenceDiagram participant 用户 participant 宿主前端 as 宿主前端 participant 查价 as 查价 API 用户->>宿主前端: 填写地址 + 货物,点「获取报价」 宿主前端->>查价: ① POST /api/host/quote/candidates 查价-->>宿主前端: 候选列表 + host_session_id 宿主前端->>用户: 展示候选,选择提货/派送地址 用户->>宿主前端: 确认候选 宿主前端->>查价: ② POST /api/host/quote/submit Note over 查价: 内部提交询价 + 轮询,10–420 秒 查价-->>宿主前端: status=done + quotes 宿主前端->>用户: 展示 final_total 等 ``` | 调用次序 | 接口 | 宿主做什么 | 查价做什么 | |----------|------|------------|------------| | 第 1 次 | `POST /api/host/quote/candidates` | 传地址 + 货物 | 返回联想候选 + `host_session_id` | | 第 2 次 | `POST /api/host/quote/submit` | 传会话 ID + 用户选的候选 | **内部轮询**,直接返回最终报价 | **宿主无需:** Token、GET 轮询、`quote_id` 管理。 **宿主需要:** 地址候选选择 UI、报价结果 UI、第 2 步 Loading(约 10–30 秒,最长约 7 分钟)。 ### 2.2 鉴权 方式 A **不需要**任何 Authorization 头,只需: ```http Content-Type: application/json ``` ### 2.3 第一步:联想地址 + 暂存货物 ```http POST /api/host/quote/candidates Content-Type: application/json ``` **请求体:** ```json { "pickup_address": { "street": "1234 Warehouse Blvd", "city": "Los Angeles", "state": "CA", "zip": "90001" }, "delivery_address": { "street": "5678 Distribution Dr", "city": "Dallas", "state": "TX", "zip": "75201" }, "cargo": { "weight": { "value": 500, "unit": "kg" }, "dimensions": { "length": 120, "width": 100, "height": 150, "unit": "cm" }, "pallet_count": 2, "cargo_type": "general_freight" } } ``` | 字段 | 必填 | 说明 | |------|------|------| | `pickup_address` / `delivery_address` | 是 | 草稿地址 | | `*.street` / `*.city` / `*.state` | 是 | 美国地址;`state` 为二字码 | | `*.zip` | 否 | 美国邮编,可省略或空字符串 | | `cargo` | 是 | 货物信息(第一步传入后由服务端暂存,第二步无需重复传) | **成功响应示例:** ```json { "code": 0, "message": "ok", "data": { "host_session_id": "eb75548a-2c0b-4282-becd-ca3a0f5a3c15", "pickup_candidates": [ { "option_id": "Eisx...", "display_label": "1234 Warehouse Street, Los Angeles, CA, USA", "formatted_address": "1234 Warehouse Street, Los Angeles, CA, USA", "street": "1234 Warehouse Street", "city": "Los Angeles", "state": "CA", "zip": "90001", "selectable": true } ], "delivery_candidates": [ { "option_id": "EihE...", "display_label": "Distribution Dr, Wilmer, Dallas, TX, USA", "formatted_address": "Distribution Dr, Wilmer, Dallas, TX, USA", "street": "5678 Distribution Dr, Wilmer", "city": "Dallas", "state": "TX", "zip": "75201", "selectable": true } ], "requires_selection": true } } ``` **前端处理:** - 用 `display_label` 展示给用户。 - 仅允许选择 `selectable: true` 的项;不可选项展示 `unavailable_reason`。 - 保存 `host_session_id`(30 分钟有效)和用户选中的**完整候选对象**。 ### 2.4 第二步:确认候选并取报价(同步) ```http POST /api/host/quote/submit Content-Type: application/json ``` **请求体:** ```json { "host_session_id": "eb75548a-2c0b-4282-becd-ca3a0f5a3c15", "pickup_candidate": { "option_id": "Eisx...", "display_label": "1234 Warehouse Street, Los Angeles, CA, USA", "formatted_address": "1234 Warehouse Street, Los Angeles, CA, USA", "street": "1234 Warehouse Street", "city": "Los Angeles", "state": "CA", "zip": "90001", "selectable": true }, "delivery_candidate": { "option_id": "EihE...", "display_label": "Distribution Dr, Wilmer, Dallas, TX, USA", "formatted_address": "Distribution Dr, Wilmer, Dallas, TX, USA", "street": "5678 Distribution Dr, Wilmer", "city": "Dallas", "state": "TX", "zip": "75201", "selectable": true } } ``` | 字段 | 必填 | 说明 | |------|------|------| | `host_session_id` | 是 | 第一步返回的 UUID | | `pickup_candidate` | 是 | 第一步 `pickup_candidates` 中用户选中项(完整对象) | | `delivery_candidate` | 是 | 第一步 `delivery_candidates` 中用户选中项(完整对象) | **成功响应(节选):** ```json { "code": 0, "message": "ok", "data": { "quote_id": "QTE_20260630_0003", "status": "done", "currency": "USD", "valid_until": "2026-06-30T10:30:00.000Z", "quotes": [ { "service_level": "standard", "rate_option": "lowest", "carrier": "Frontline Freight", "transit_days": "Est. 5 business days", "final_total": 337.06 } ] } } ``` **展示给用户:** 使用 `quotes[].final_total`(USD)、`carrier`、`transit_days`。 **失败:** HTTP 200 时 `data.status` 可能为 `failed`,读 `error_message`;HTTP 504 表示服务端轮询超时。 ### 2.5 美美与共前端职责 | 模块 | 说明 | |------|------| | 表单 | 收集地址(仓库代码需先转为 street/city/state/zip)和货物 | | 地址候选 UI | 第 1 步后展示 `pickup_candidates` / `delivery_candidates` 供用户选择 | | 报价结果 UI | 第 2 步后展示 `quotes` 列表 | | Loading | 第 2 步显示「正在查询报价,约需 10–30 秒」,禁止重复提交 | | 不需要 | Token、轮询、`quote_id` 管理 | ### 2.6 美美与共调用示例(JavaScript) ```javascript // 第 1 步:联想 async function fetchCandidates(form) { const res = await fetch("https://if.dev.51track.vip/api/host/quote/candidates", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ pickup_address: form.pickup, delivery_address: form.delivery, cargo: form.cargo, }), }); const json = await res.json(); if (json.code !== 0) throw new Error(json.message); return json.data; // { host_session_id, pickup_candidates, delivery_candidates } } // 第 2 步:确认并取报价(一次请求,无需轮询) async function submitQuote(hostSessionId, pickupCandidate, deliveryCandidate) { const res = await fetch("https://if.dev.51track.vip/api/host/quote/submit", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ host_session_id: hostSessionId, pickup_candidate: pickupCandidate, delivery_candidate: deliveryCandidate, }), }); const json = await res.json(); if (json.code !== 0) throw new Error(json.message); if (json.data.status === "failed") throw new Error(json.data.error_message ?? "询价失败"); return json.data.quotes; } ``` --- ## 3. 统一响应格式 ```json { "code": 0, "message": "ok", "data": { } } ``` | 字段 | 说明 | |------|------| | `code` | `0` 成功;非 0 为错误码 | | `message` | 人类可读说明 | | `data` | 业务数据 | --- ## 4. 货物类型与限制 **`cargo_type` 枚举:** `general_freight` · `machinery` · `furniture` · `electronics` · `building_materials` · `auto_parts` · `food_nonperishable` · `apparel` · `other` | 项 | 限制 | |----|------| | 托盘数 | 1 – 25 | | 单托重量 | 0.01 – 9,999 lb | | 单托尺寸(换算英寸后) | 长 ≤ 999,宽 ≤ 99,高 ≤ 99 | --- ## 5. 自测方法 ### 5.1 第一步:联想(PowerShell) > **注意:** Windows PowerShell 下 `curl.exe -d '{...}'` 容易因引号转义导致 JSON 无效,返回 `请求体格式无效`。**请用 JSON 文件或 Invoke-RestMethod。** **推荐:JSON 文件** ```powershell curl.exe -s -X POST "https://if.dev.51track.vip/api/host/quote/candidates" ` -H "Content-Type: application/json" ` --data-binary "@deploy/host-candidates-req.example.json" ``` **推荐:Invoke-RestMethod** ```powershell $body = Get-Content "deploy/host-candidates-req.example.json" -Raw Invoke-RestMethod -Uri "https://if.dev.51track.vip/api/host/quote/candidates" ` -Method POST -ContentType "application/json" -Body $body ``` 成功标志:`code: 0`,且 `data.host_session_id` 存在。 ### 5.2 第二步:取报价 从第一步响应中各取一条 `selectable: true` 的候选,构造 body 调用: ```powershell # 将 host_session_id、pickup_candidate、delivery_candidate 填入 body 后: Invoke-RestMethod -Uri "https://if.dev.51track.vip/api/host/quote/submit" ` -Method POST -ContentType "application/json" -Body $submitJson ``` 成功标志:`data.status === "done"`,`data.quotes` 含 `final_total`。 ### 5.3 Linux / macOS ```bash curl -s -X POST "https://if.dev.51track.vip/api/host/quote/candidates" \ -H "Content-Type: application/json" \ --data-binary "@deploy/host-candidates-req.example.json" ``` --- ## 6. 方式 B:Token 三步接口 后期启用鉴权时,将 `HOST_PUBLIC_API_ENABLED=false`,改用以下流程。**宿主须自行轮询。** ### 6.1 鉴权 ```http Authorization: Bearer X-Customer-Id: Content-Type: application/json ``` ### 6.2 流程 | 步骤 | 接口 | 说明 | |------|------|------| | 1 | `POST /api/addresses/mothership-candidates` | 仅地址,返回候选 | | 2 | `POST /api/quotes` | 确认地址 + 货物,返回 `quote_id` + `processing` | | 3 | `GET /api/quotes/{quote_id}` | 每 2 秒轮询,直到 `done` / `failed` | 第二步 body 须含 `customer_id`、`request_id`(UUID v4)、经 MotherShip 确认的地址字段(`mothership_option_id`、`selected_from_mothership: true` 等)及货物信息。 Token 由我方在 `HOST_SERVICE_TOKENS` 中配置后发放。 --- ## 7. 错误码 ### 方式 A 常见错误 | code | HTTP | 说明 | 处理 | |------|------|------|------| | `VALIDATION_FAILED` | 400 | 参数无效 | 检查字段;message 为「请求体格式无效」时检查 JSON 格式 | | `FORBIDDEN` | 403 | 公开接口未启用 | 确认 `HOST_PUBLIC_API_ENABLED=true` | | `SESSION_EXPIRED` | 404 | 会话过期 | 重新调用第一步 | | `ADDRESS_SESSION_MISMATCH` | 400 | 候选与第一步不匹配 | 使用第一步返回的候选对象 | | `ADDRESS_NOT_SELECTABLE` | 400 | 选了不可用地址 | 换 `selectable: true` 的候选 | | `QUOTE_TIMEOUT` | 504 | 询价超时 | 稍后重试 | | `RATE_LIMITED` | 429 | 限流 | 降低频率 | ### 通用错误 | code | HTTP | 说明 | |------|------|------| | `UNAUTHORIZED` | 401 | Token 无效(方式 B) | | `INTERNAL_ERROR` | 5xx | 服务异常 | 询价业务失败时 HTTP 可能仍为 200,`data.status === "failed"`,见 `error_message`。 --- ## 8. 安全与运维 1. 方式 A 当前免 Token,**仅建议在可信内网或已控域名下使用**;上线前改方式 B。 2. 方式 B 的 Token **仅放宿主服务端**,禁止写进浏览器或公开仓库。 3. 服务**未开放 CORS**;浏览器页请勿直接 `fetch` 查价域名,应通过宿主后端或同域代理。 4. 第二步网关/Nginx 须设 `proxy_read_timeout 420s;`。 5. 报价有效期见 `valid_until`,过期需重新询价。 --- ## 9. 联系与支持 | 事项 | 说明 | |------|------| | 开通 / 环境确认 | 联系查价系统管理员 | | 接口故障 | 提供 `host_session_id` 或 `quote_id`、发生时间 | | 文档版本 | 2026-06-30 |