You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
chajia/deploy/第三方对接指南.md

407 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 卡派查价 — 第三方系统对接指南
本文档面向**宿主业务系统**(如美美与共)开发方,说明如何通过 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 + JSONUTF-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 查价: 内部提交询价 + 轮询10420 秒
查价-->>宿主前端: 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约 1030 秒,最长约 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 步显示「正在查询报价,约需 1030 秒」,禁止重复提交 |
| 不需要 | 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. 方式 BToken 三步接口
后期启用鉴权时,将 `HOST_PUBLIC_API_ENABLED=false`,改用以下流程。**宿主须自行轮询。**
### 6.1 鉴权
```http
Authorization: Bearer <Service Token>
X-Customer-Id: <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 |