|
|
# 卡派查价 — 第三方系统对接指南
|
|
|
|
|
|
本文档面向**宿主业务系统**(如美美与共)开发方,说明如何通过 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 <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 |
|