refactor: 文档维护点收归mipu-api,新增SDK

- 移除 docs/ 和 Release CI,文档维护点收归 mipu-api 子模块
- 新增 sdk/mipu_requests Python SDK(构造参数传入凭据)
- 新增 CLAUDE.md 标注文档维护路径
- 精简 README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
杨柳杰
2026-05-04 19:12:16 +08:00
parent 58b84d6f94
commit 67db2ee5b2
48 changed files with 804 additions and 5528 deletions

View File

@@ -1,71 +0,0 @@
name: Release Docs
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
git clone -b ${GITHUB_REF#refs/tags/} \
"${{ github.server_url }}/${{ github.repository }}.git" .
- name: Package docs
run: |
if [ ! -d "docs" ]; then
echo "Error: docs directory not found"
exit 1
fi
TAG=${GITHUB_REF#refs/tags/}
echo "$TAG" > "docs/.version"
tar -czf /tmp/docs.tar.gz docs/
- name: Create Release and Upload
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
TAG=${GITHUB_REF#refs/tags/}
API="${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
AUTH="Authorization: token ${GITEA_TOKEN}"
# Delete existing release for this tag if any
EXISTING_ID=$(curl -sf "${API}/releases/tags/${TAG}" -H "$AUTH" 2>/dev/null \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$EXISTING_ID" ]; then
echo "Deleting existing release #${EXISTING_ID}"
curl -sf -X DELETE "${API}/releases/${EXISTING_ID}" -H "$AUTH" 2>/dev/null || true
fi
# Create release
curl -sf -X POST "${API}/releases" \
-H "$AUTH" \
-H "Content-Type: application/json" \
-d "{
\"tag_name\": \"${TAG}\",
\"name\": \"API文档 ${TAG}\",
\"body\": \"米普云航司API文档更新 ${TAG}\"
}"
# Get release ID
RELEASE_ID=$(curl -sf "${API}/releases/tags/${TAG}" -H "$AUTH" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "None" ]; then
echo "Error: failed to get release ID"
exit 1
fi
echo "Release #${RELEASE_ID} created"
# Upload docs.tar.gz
curl -sf -X POST "${API}/releases/${RELEASE_ID}/assets?name=docs.tar.gz" \
-H "$AUTH" \
-F "attachment=@/tmp/docs.tar.gz"
echo "Release ${TAG} created with docs.tar.gz"

26
CLAUDE.md Normal file
View File

@@ -0,0 +1,26 @@
# CLAUDE.md — 项目指引
## 项目结构
本项目是米普对外开放内容的统一管理仓库。
- `mipu-api/` — git submoduleClaude Code Skill**API 文档在此维护**
- `sdk/mipu_requests/` — Python SDKpip 包)
## API 文档
文档源文件位于 mipu-api 子模块内:
```
mipu-api/plugins/mipu-api/skills/mipu-api/docs/
```
修改文档时,进入 mipu-api 子模块操作:
```bash
cd mipu-api
vim plugins/mipu-api/skills/mipu-api/docs/03-01_shopping-search.md
git add -A && git commit -m "docs: 更新说明" && git push
cd ..
git add mipu-api && git commit -m "chore: 更新mipu-api" && git push
```

View File

@@ -6,53 +6,17 @@
| 子项目 | 说明 | | 子项目 | 说明 |
|--------|------| |--------|------|
| [mipu-api](./mipu-api/) | Claude Code 开发 Skill辅助外部开发者快速对接米普 API | | [mipu-api](./mipu-api/) | Claude Code 开发 SkillAPI 文档在其 `plugins/.../docs/` 内维护 |
| docs/ | API 文档源文件(本仓库维护,通过 Release 发布 tar 包) | | [sdk/mipu_requests](./sdk/mipu_requests/) | Python SDK封装米普 API 调用 |
| mipu-sdk | 开发 SDK待添加 |
## 文档发布流程
文档源文件在 `docs/` 目录维护。发布新版本:
```bash
# 1. 修改 docs/ 下的文档
# 2. 提交并打 tag
git add docs/
git commit -m "更新API文档"
git tag v1.1.0
git push && git push --tags
# Gitea Actions 自动:
# - 打包 docs/ 为 docs.tar.gz
# - 创建 Release 并附加 docs.tar.gz
```
Skill 端会在每次调用时自动检查 Release 版本并下载更新。
## 快速开始 ## 快速开始
克隆主仓库及所有子模块:
```bash ```bash
git clone --recurse-submodules <this-repo-url> git clone --recurse-submodules <this-repo-url>
``` ```
如果已经克隆了主仓库:
```bash
git submodule update --init --recursive
```
## 更新子模块 ## 更新子模块
拉取所有子模块的最新更新:
```bash ```bash
git submodule update --remote git submodule update --remote
``` ```
## 添加新子模块
```bash
git submodule add <repo-url> <directory-name>
```

View File

@@ -1,65 +0,0 @@
# 发布说明
## 前置配置(仅首次)
在 Gitea 仓库中配置 Actions Secret
1. 打开 `https://git.addhh.com/willow/mipu-open/-/settings/actions`
2. 点击 **Secrets****Add Secret**
3. Name: `GITEA_TOKEN`Value: (向管理员获取)
4. 保存
## 发布新版本
```bash
# 1. 修改 docs/ 下的文档内容
vim docs/03-01_shopping-search.md
# 2. 提交变更
git add docs/
git commit -m "docs: 更新shopping/search接口说明"
# 3. 打版本 tag
git tag v1.1.0
# 4. 推送代码和 tag
git push && git push --tags
```
推送 tag 后 Gitea Actions 自动执行:
1.`docs/` 目录打包为 `docs.tar.gz`
2.`willow/mipu-open` 仓库创建 Release附带 `docs.tar.gz`
## 版本号规则
使用语义化版本 `vMAJOR.MINOR.PATCH`
| 场景 | 示例 |
|------|------|
| 新增接口文档 | v1.1.0 |
| 修正文档错误 | v1.0.1 |
| 文档结构变更/破坏性调整 | v2.0.0 |
## 用户端更新
mipu-api Skill 每次被调用时自动检查:
1. 读取本地 `.version` 获取当前版本
2. 查询 `https://git.addhh.com/api/v1/repos/willow/mipu-open/releases/latest`
3. 版本不一致 → 下载 `docs.tar.gz` → 解压覆盖本地 `docs/`
4. 网络异常 → 静默跳过,使用本地缓存
## 目录结构
```
mipu-open/
├── docs/ ← 文档源文件(在此维护)
│ ├── 02-01_cache-search.md
│ ├── 03-01_shopping-search.md
│ └── ...
├── .gitea/workflows/
│ └── release-docs.yml ← CItag 触发自动打包发布
├── mipu-api/ ← git submodule (Claude Code Skill)
└── RELEASE.md ← 本文件
```

View File

@@ -1,144 +0,0 @@
<!-- mipuyun-api-doc: cache-search -->
# cache/search - 查询航司缓存数据
# 缓存票价搜索cache/searchAPI 说明文档
## 用户使用场景
> 用户希望以低成本,高性能的模式获取航司的价格数据,该数据源为我司缓存库内容,非实时提取航司数据。
## 错误场景(不应该使用本接口的场景)
> 本接口不会与航司产生任何交互,无法保障在响应时刻价格/库存与航司完全一致。
## 性能指标
> - 航司路线&旅行日期覆盖率按航司提供平均值为180天内>90%。
> - 价格准确率(验价变价率):<85%
## 请求说明
| **请求地址** | https://${endpoint}/cache/search |
| --- | --- |
| **请求方法** | POST |
| **注意事项** | Header必须带压缩请求。 |
## 请求参数
### Header
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝。 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境secret |
###
### 请求体
注灰色背景为高级特性,默认不不填写,根据需要进行开发。
```json
{
"journeyType": "OW",
"origin": "MIL",
"destination": "CAG",
"departureDate": "20251118",
"adults": 1,
"children": 0,
"infants": 0,
"agent": "u2web"
}
```
| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** |
| --- | --- | --- | --- | --- |
| journeyType | String | 是 | "OW" | 行程为单程还是往返程OW单程OneWayRT往返RoundTrip |
| origin | String | 是 | "CJJ" | 出发地为IATA 3字码兼容城市或者机场3字码 |
| originAirport | String | 否 | | |
| destination | String | 是 | "SHA" | 到达地为IATA3字码兼容城市或者机场3字码 |
| destinationAirport | String | 否 | | |
| departureDate | String | 是 | 20240326 | 出发日期,格式为 `YYYYMMDD`(如 2024 年 5 月 1 日为 20240501 |
| returnDate | String | 否 | 20240423仅 RT 必填) | 返程日期,格式同 departureDate仅当 journeyType=RT 时必传OW 时可传空 |
| adults | Integer | 是 | 2 | 成人,乘机人数量 |
| children | Integer | 是 | 1 | 儿童,乘机人数量 |
| infants | Integer | 是 | 0 | 婴儿,乘机人数量 |
| agent | String | 是 | "F9-web" | 你自行定义的F9执行器编码Agent Code |
| proxy | String | 是 | | 本次获取航司数据采用的代理方式。如果需要指定代理国家请在代理用户后面加上国家二字码。如myproxy-USUS代表美国 |
| acceptCacheMinutes | Integer | 否 | 5 | 默认值5**如无特殊情况,本参数不建议设置或调整。**为了避免频繁请求航司设置的缓存用户可以指定缓存时长不填写为5。 |
## 返回参数
探索一个返回参数jsonhttps://jsonhero.io/j/XmTLWK94Ae5X
| 参数名称 | 类型 | 示例值 | 说明 |
| --- | --- | --- | --- |
| itineraries | Array<Itinerary> | - | 航线组合列表,包含不同航班拼接的行程方案 |
| code | Integer | 0 | 系统状态码0 = 成功,非 0 为失败 |
| msg | String | null | 系统消息:成功时为 null失败时返回具体系统提示信息 |
| sessionId | String | 66d807b0-abbc-4b57-aeff-bbf4fc29fb75 | UUID: 与航司通信的session值可以用于加速后续动作如继续获取包裹选座下单。 |
# 常见问题
## origin/originAirport的区别是什么
origin出发地建议传入IATA规定的城市码必填
originAirport出发机场非必填
由于各个数据源对于城市码/机场码的需求场景不一致,服务端做了兼容处理。
一般情况只需要传入Origin(城市码)我们会自动将Origin翻译为机场码并对航司进行轮询获取所有数据。
部分特殊情况如只需要搜索单个机场码或者直接搜索城市码不希望服务端转换为机场码请将需要搜索的三字码直接放入OriginAirport。
**举例:**
入参origin: LON。
不传入originAirport 服务端发送给航司originAirport: LCY, LGW, LHR, LTN, SEN, STN
传入originAirport: LCY, 服务端实际发送给航司originAirport: LCY
传入originAirport: LON, 服务端实际发送给航司 originAirport: LON
**destination/destinationAirport逻辑同origin/originAirport**
##
# 业务案例
## 单人单程搜索
### 请求Request
```objective-c
```
### 请求Response
---
## 转机行程搜索
### 请求Request
```json
{
"journeyType": "OW",
"origin": "BOM",
"destination": "CNX",
"departureDate": "20251118",
"adults": 1,
"children": 0,
"infants": 0
}
```
### 响应Response
在线查看https://jsonhero.io/j/J0LMhczR0bJM/editor

View File

@@ -1,192 +0,0 @@
<!-- mipuyun-api-doc: shopping-search -->
# shopping/search - 运价报价
## 概述
航班搜索Flight SearchAPI 用于驱动 Agent 去数据源(航司)获取对应数据。
### 用户使用场景
本接口一般用于:
- 查询单家航司报价
- 建立航司数据本地缓存
### 错误场景(不建议使用)
本接口不适合以下场景:
- **秒级搜索响应**本接口为实时调用接口响应时间依赖数据源航司一般响应时间在10秒左右。如需秒级搜索响应请参考缓存搜索接口cache/search
- **验价**:验价请使用 shopping/select8秒内响应成功率98%)。
### 性能指标
| 指标 | 说明 |
|------|------|
| 响应速度 | 依赖数据源航司响应根据大数据统计90%的请求在10秒内 |
| 成功率 | >90% |
## 请求说明
| 项目 | 值 |
|------|-----|
| **请求地址** | `https://${endpoint}/shopping/search` |
| **请求方法** | POST |
| **注意事项** | Header 必须带压缩请求 |
### Header 参数
| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 |
|----------|------|----------|--------|------|
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret |
### 请求体参数
> 注:灰色背景为高级特性,默认不填写,根据需要进行开发。
```json
{
"journeyType": "OW",
"origin": "MIL",
"destination": "CAG",
"departureDate": "20251118",
"adults": 1,
"children": 0,
"infants": 0,
"agent": "u2web",
"proxy": "myproxy-us"
}
```
| 参数名称 | 类型 | 是否必选 | 示例值(默认值) | 说明 |
|----------|------|----------|-----------------|------|
| journeyType | String | 是 | "OW" | 行程类型OW=单程(OneWay)RT=往返(RoundTrip) |
| origin | String | 是 | "CJJ" | 出发地为IATA 3字码兼容城市或者机场3字码 |
| originAirport | String | 否 | - | 出发机场 |
| destination | String | 是 | "SHA" | 到达地为IATA 3字码兼容城市或者机场3字码 |
| destinationAirport | String | 否 | - | 到达机场 |
| departureDate | String | 是 | 20240326 | 出发日期,格式为 `YYYYMMDD`如2024年5月1日为20240501 |
| returnDate | String | 否 | 20240423 | 返程日期,格式同 departureDate仅当 journeyType=RT 时必传 |
| adults | Integer | 是 | 2 | 成人乘机人数量 |
| children | Integer | 是 | 1 | 儿童乘机人数量 |
| infants | Integer | 是 | 0 | 婴儿乘机人数量 |
| agent | String | 是 | "F9-web" | 你自行定义的执行器编码Agent Code |
| proxy | String | 是 | - | 本次获取航司数据采用的代理方式。如需指定代理国家请在代理用户后面加上国家二字码,如 myproxy-US 代表美国 |
| currency | String | 否 | - | 航司所支持的货币类型,如航司不支持对应货币,接口会进行报错 |
| acceptCacheMinutes | Integer | 否 | 5 | 默认值5。**如无特殊情况,本参数不建议设置或调整。**为了避免频繁请求航司设置的缓存用户可以指定缓存时长不填写为5分钟 |
| async | Boolean | 是 | false | 是否启用 Webhook 异步模式。<br>- **false**: 同步模式,等待完整结果返回<br>- **true**: 异步模式,立即返回 202结果通过 Webhook 回调<br><br>**异步模式说明**<br>1. 系统会同步检查是否有新鲜缓存acceptCacheMinutes如有则立即返回<br>2. 无缓存时返回 202 Accepted包含 requestId 用于追踪<br>3. 航司数据返回后,通过预先配置的 Webhook 回调通知<br><br>**注意**:使用异步模式需提前配置 `shopping_response` 类型的 Webhook |
| callbackId | String | 否 | - | 异步模式下的业务回调标识,用于客户自行匹配业务。建议格式:`search_{origin}_{destination}_{departureDate}_{adults}-{children}-{infants}`,最大 50 个字符 |
## 返回参数
在线查看返回参数示例https://jsonhero.io/j/XmTLWK94Ae5X
| 参数名称 | 类型 | 示例值 | 说明 |
|----------|------|--------|------|
| itineraries | Array\<Itinerary\> | - | 航线组合列表,包含不同航班拼接的行程方案。详见 [Itinerary 实体定义](08-01_itinerary.md) |
| code | Integer | 0 | 系统状态码0=成功非0为失败 |
| msg | String | null | 系统消息:成功时为 null失败时返回具体系统提示信息 |
| sessionId | String | 66d807b0-abbc-4b57-aeff-bbf4fc29fb75 | UUID与航司通信的 session 值,可以用于加速后续动作,如继续获取包裹、选座、下单 |
### 异步模式响应async=true
`async=true` 且无可用缓存时,系统立即返回 202 Accepted
| 参数名称 | 类型 | 说明 |
|----------|------|------|
| code | Integer | 202表示请求已接受正在处理中 |
| msg | String | "Accepted. Please wait for the webhook callback" |
| requestId | String | 请求IDUUID用于追踪和关联 Webhook 回调 |
| status | String | "PROCESSING"(处理中) |
完整结果将通过预先配置的 Webhook 回调返回。
## 常见问题
### origin/originAirport 的区别是什么?
**origin**:出发地,建议传入 IATA 规定的城市码,必填
**originAirport**:出发机场,非必填
由于各个数据源对于城市码/机场码的需求场景不一致,服务端做了兼容处理。
一般情况只需要传入 Origin城市码我们会自动将 Origin 翻译为机场码,并对航司进行轮询获取所有数据。
部分特殊情况,如只需要搜索单个机场码,或者直接搜索城市码不希望服务端转换为机场码,请将需要搜索的三字码直接放入 originAirport。
**举例:**
- 入参:`origin: LON`
- 不传入 originAirport服务端发送给航司`originAirport: LCY, LGW, LHR, LTN, SEN, STN`
- 传入 originAirport: LCY服务端实际发送给航司`originAirport: LCY`
- 传入 originAirport: LON服务端实际发送给航司`originAirport: LON`
**destination/destinationAirport 逻辑同 origin/originAirport**
### 这个接口会使用缓存吗?
默认情况本接口会读取一个 5 分钟短效缓存,如果你在 5 分钟重复请求同一个路线,那么第二次会直接返回缓存数据。
你也可以通过 `acceptCacheMinutes` 自己控制缓存时效。
## 响应实体介绍
### 响应示例
```json
{
"code": 0,
"msg": null,
"sessionId": "66d807b0-abbc-4b57-aeff-bbf4fc29fb75",
"itineraries": [
{
"outboundSegments": [
{
"index": 1,
"carrier": "VJ",
"flightNumber": "VJ884",
"operatingCarrier": "",
"operatingFlightnumber": "",
"originAirport": "BOM",
"destinationAirport": "SGN",
"departureTime": "202510270040",
"arrivalTime": "202510270705",
"departureTerminal": "",
"arrivalTerminal": "",
"stopCities": "",
"duration": 295,
"aircraftCode": "330",
"codeShare": false
}
],
"inboundSegments": [],
"fares": [
{
"fareBasis": "",
"rtnFareBasis": "",
"fareFamily": "Lion Economy",
"rtnFareFamily": "",
"currency": "USD",
"adultFare": 100.00,
"adultTax": 50.00,
"bookingCode": "Y",
"availableSeats": 7,
"flightPolicy": {
"airlineCode": "SL",
"fareFamilyType": "Lion Economy",
"freeAncillaryList": [
{ "categoryCode": "CabinBaggageOverheadLocker", "segmentIndex": 1, "paxType": "ADT", "piece": 1, "weight": 7, "size": "" },
{ "categoryCode": "StandardCheckedBaggage", "segmentIndex": 1, "paxType": "ADT", "piece": 1, "weight": 20 }
]
}
}
]
}
]
}
```
> **提示**`itineraries` 数组中的每个元素是一个完整的行程方案,详见 [Itinerary 实体定义](08-01_itinerary.md)

View File

@@ -1,206 +0,0 @@
<!-- mipuyun-api-doc: shopping-select -->
# shopping/select - 验价/确定航班
# 确定航班shopping/selectAPI 说明文档
## 用户使用场景
> 本接口目的是:
> 验价可满足8秒内响应>95%成功率。
> 进一步获取免费行李/退改规则信息对于无法再shopping/search获取的航司场景
## 错误场景(不应该使用本接口的场景)
> 如航司在点选行程后占位,本接口需谨慎使用,防止航司侧异常。
## 性能指标
> - 响应速度95%的请求<8秒。
> - 请求频率QPS根据你的商务合同确定
> - 接口成功率:>95%
## 请求说明
| **请求地址** | https://${endpoint}/shopping/select |
| --- | --- |
| **请求方法** | POST |
| **注意事项** | Header必须带压缩请求。 |
## 请求参数
### Header
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝。 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境secret |
###
### 请求体
```json
{
"sessionId": "66d807b0-abbc-4b57-aeff-bbf4fc29fb75",
"journeyType": "OW",
"origin": "MIL",
"destination": "CAG",
"departureDate": "20251118",
"adults": 1,
"children": 0,
"infants": 0,
"agent": "u2web",
"proxy": "myproxy-us",
"outboundFareFamily": "promo",
"inboundFareFamily": "",
"outboundSegments": [
{
"carrier": "SL",
"originAirport": "PHS",
"destinationAirport": "DMK",
"flightNumber": "SL557",
"departureDate": "20251119"
}
],
"inboundSegments": [
]
}
```
| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** |
| --- | --- | --- | --- | --- |
| sessionId | String | 否 | 66d807b0-abbc-4b57-aeff-bbf4fc29fb75 | 目的是为了复用前一步的shopping/search的session提升速度&成功率。如带入session请务必保障1. 出发到达/旅行日期/人数与前一步保持一致。 |
| journeyType | String | 是 | "OW" | 行程为单程还是往返程OW单程OneWayRT往返RoundTrip |
| origin | String | 是 | "CJJ" | 出发地为IATA 3字码兼容城市或者机场3字码 |
| destination | String | 是 | "SHA" | 到达地为IATA3字码兼容城市或者机场3字码 |
| departureDate | String | 是 | 20240326 | 出发日期,格式为 `YYYYMMDD`(如 2024 年 5 月 1 日为 20240501 |
| returnDate | String | 否 | 20240423仅 RT 必填) | 返程日期,格式同 departureDate仅当 journeyType=RT 时必传OW 时可传空 |
| adults | Integer | 是 | 2 | 成人,乘机人数量 |
| children | Integer | 是 | 1 | 儿童,乘机人数量 |
| infants | Integer | 是 | 0 | 婴儿,乘机人数量 |
| outboundSegments | Array<FlightSegmentRequest> | 是 | | 去程航段信息,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) |
| inboundSegments | Array<FlightSegmentRequest> | 否 | | 返程航段信息往返行程为必须,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) |
| outboundFareFamily | String | 是 | | 去程FareFamily的名字 |
| inboundFareFamily | String | 否 | | 回程FareFamily的名字仅往返程有值 |
| agent | String | 是 | "u2web" | 你自行定义的执行器编码Agent Code |
| proxy | String | 是 | | 本次获取航司数据采用的代理方式。如果需要指定代理国家请在代理用户后面加上国家二字码。如myproxy-USUS代表美国 |
| acceptCacheMinutes | Integer | 否 | 5 | 默认值5**如无特殊情况,本参数不建议设置或调整。**为了避免频繁请求航司设置的缓存用户可以指定缓存时长不填写为5。 |
| async | Boolean | 是 | false | 是否启用 Webhook 异步模式。<br>- **false**: 同步模式,等待完整结果返回<br>- **true**: 异步模式,立即返回 202结果通过 Webhook 回调<br><br>**异步模式说明**<br>1. 系统会同步检查是否有新鲜缓存acceptCacheMinutes如有则立即返回<br>2. 无缓存时返回 202 Accepted包含 requestId 用于追踪<br>3. 航司数据返回后,通过预先配置的 Webhook 回调通知<br><br>**注意**:使用异步模式需提前配置 `shopping_response` 类型的 Webhook |
| callbackId | String | 否 | - | 异步模式下的业务回调标识,用于客户自行匹配业务。建议格式:`select_{origin}_{destination}_{departureDate}_{adults}-{children}-{infants}_{flightNumber}`,最大 50 个字符 |
## 返回参数
探索一个返回参数json同shopping/searchhttps://jsonhero.io/j/XmTLWK94Ae5X
| 参数名称 | 类型 | 示例值 | 说明 |
| --- | --- | --- | --- |
| itineraries | Array<Itinerary> | - | 航线组合列表,包含不同航班拼接的行程方案 |
| baggageList | | | |
| | | | |
| code | Integer | 0 | 系统状态码0 = 成功,非 0 为失败 |
| msg | String | null | 系统消息:成功时为 null失败时返回具体系统提示信息 |
| sessionId | String | 66d807b0-abbc-4b57-aeff-bbf4fc29fb75 | UUID: 与航司通信的session值可以用于加速后续动作如继续获取包裹选座下单。 |
### 异步模式响应async=true
`async=true` 且无可用缓存时,系统立即返回 202 Accepted
| 参数名称 | 类型 | 说明 |
|----------|------|------|
| code | Integer | 202表示请求已接受正在处理中 |
| msg | String | "Accepted. Please wait for the webhook callback" |
| requestId | String | 请求IDUUID用于追踪和关联 Webhook 回调 |
| status | String | "PROCESSING"(处理中) |
完整结果将通过预先配置的 Webhook 回调返回。
## 响应实体介绍
### 响应示例
```json
{
"code": 0,
"msg": null,
"sessionId": "66d807b0-abbc-4b57-aeff-bbf4fc29fb75",
"itineraries": [
{
"outboundSegments": [
{
"index": 1,
"carrier": "SL",
"flightNumber": "SL557",
"operatingCarrier": "",
"operatingFlightnumber": "",
"originAirport": "PHS",
"destinationAirport": "DMK",
"departureTime": "202511190800",
"arrivalTime": "202511191130",
"departureTerminal": "",
"arrivalTerminal": "",
"stopCities": "",
"duration": 210,
"aircraftCode": "320",
"codeShare": false
}
],
"inboundSegments": [],
"fares": [
{
"fareBasis": "",
"rtnFareBasis": "",
"fareFamily": "promo",
"rtnFareFamily": "",
"currency": "USD",
"adultFare": 80.00,
"adultTax": 30.00,
"bookingCode": "Y",
"availableSeats": 9,
"flightPolicy": {
"airlineCode": "SL",
"fareFamilyType": "promo",
"freeAncillaryList": [
{ "categoryCode": "CabinBaggageOverheadLocker", "segmentIndex": 1, "paxType": "ADT", "piece": 1, "weight": 7, "size": "" },
{ "categoryCode": "StandardCheckedBaggage", "segmentIndex": 1, "paxType": "ADT", "piece": 0, "weight": 0 }
]
}
}
]
}
],
"baggageList": []
}
```
> **提示**`itineraries` 数组中的每个元素是一个完整的行程方案,详见 [Itinerary 实体定义](08-01_itinerary.md)
# 常见问题
## 航司不稳定的时候,如何保障成功率>95%
我们支持多个通过proxy入参实现并发请求。
**案例1竖线分隔随机挑选**
proxy= myproxy-us|myproxy-uk
行为随机从myproxy-us中挑选一个
**案例2加号分隔**
proxy= myproxy-us+myproxy-uk
行为:启动两个进程对数据源(航司)做并发访问,确保成功率。
**案例3组合**
proxy= myproxy1-us|myproxy2-au+myproxy1-uk|myproxy2-de
行为随机挑选myproxy1|myproxy2的代理并启动两个进程对数据源做并发访问。
## 如果航司侧响应大于8秒这个请求会被抛弃吗
不会为了兼容异常场景该接口我们最大等待航司响应的时长为15秒。

View File

@@ -1,264 +0,0 @@
<!-- mipuyun-api-doc: shopping-baggage -->
# shopping/baggage - 行李报价
## 用户使用场景
> 获取行程可支持的收费行李规格,以及各个规格的行李价格
## 性能指标
> - 通过sessionId访问或者sessionId有效响应时间<8秒。
## 请求说明
| **请求地址** | https://${endpoint}/shopping/baggage |
| --- | --- |
| **请求方法** | POST |
| **注意事项** | Header必须带压缩请求。 |
## 请求参数
### Header
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝。 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境secret |
###
### 请求体
```json
{
"sessionId": "66d807b0-abbc-4b57-aeff-bbf4fc29fb75",
"journeyType": "OW",
"origin": "MIL",
"destination": "CAG",
"departureDate": "20251118",
"adults": 1,
"children": 0,
"infants": 0,
"agent": "u2web",
"proxy": "myproxy-us",
"outboundFareFamily": "promo",
"inboundFareFamily": "",
"outboundSegments": [
{
"carrier": "SL",
"originAirport": "PHS",
"destinationAirport": "DMK",
"flightNumber": "SL557",
"departureDate": "20251119"
}
],
"inboundSegments": [
]
}
```
> **注意**:本接口的请求参数与 [shopping/select](03-02_shopping-select.md) 完全一致,详细的参数说明请参考 shopping/select 文档。
| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** |
| --- | --- | --- | --- | --- |
| sessionId | String | 否 | 66d807b0-abbc-4b57-aeff-bbf4fc29fb75 | 目的是为了复用前一步的shopping/search的session提升速度&成功率。如带入session请务必保障1. 出发到达/旅行日期/人数与前一步保持一致。 |
| journeyType | String | 是 | "OW" | 行程为单程还是往返程OW单程OneWayRT往返RoundTrip |
| origin | String | 是 | "CJJ" | 出发地为IATA 3字码兼容城市或者机场3字码 |
| destination | String | 是 | "SHA" | 到达地为IATA3字码兼容城市或者机场3字码 |
| departureDate | String | 是 | 20240326 | 出发日期,格式为 `YYYYMMDD`(如 2024 年 5 月 1 日为 20240501 |
| returnDate | String | 否 | 20240423仅 RT 必填) | 返程日期,格式同 departureDate仅当 journeyType=RT 时必传OW 时可传空 |
| adults | Integer | 是 | 2 | 成人,乘机人数量 |
| children | Integer | 是 | 1 | 儿童,乘机人数量 |
| infants | Integer | 是 | 0 | 婴儿,乘机人数量 |
| outboundSegments | Array<FlightSegmentRequest> | 是 | | 去程航段信息,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) |
| inboundSegments | Array<FlightSegmentRequest> | 否 | | 返程航段信息往返行程为必须,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) |
| outboundFareFamily | String | 是 | | 去程FareFamily的名字 |
| inboundFareFamily | String | 否 | | 回程FareFamily的名字仅往返程有值 |
| agent | String | 是 | "u2web" | 你自行定义的执行器编码Agent Code |
| proxy | String | 是 | | 本次获取航司数据采用的代理方式。如果需要指定代理国家请在代理用户后面加上国家二字码。如myproxy-USUS代表美国 |
| acceptCacheMinutes | Integer | 否 | 5 | 默认值5**如无特殊情况,本参数不建议设置或调整。**为了避免频繁请求航司设置的缓存用户可以指定缓存时长不填写为5。 |
| async | Boolean | 是 | false | 是否启用 Webhook 异步模式。<br>- **false**: 同步模式,等待完整结果返回<br>- **true**: 异步模式,立即返回 202结果通过 Webhook 回调<br><br>**异步模式说明**<br>1. 系统会同步检查是否有新鲜缓存acceptCacheMinutes如有则立即返回<br>2. 无缓存时返回 202 Accepted包含 requestId 用于追踪<br>3. 航司数据返回后,通过预先配置的 Webhook 回调通知<br><br>**注意**:使用异步模式需提前配置 `shopping_response` 类型的 Webhook |
| callbackId | String | 否 | - | 异步模式下的业务回调标识,用于客户自行匹配业务。建议格式:`baggage_{origin}_{destination}_{departureDate}_{adults}-{children}-{infants}_{flightNumber}`,最大 50 个字符 |
> **参数一致性说明**本接口的所有请求参数Header 和 请求体)与 [shopping/select](03-02_shopping-select.md) 完全相同。如果您已经熟悉 shopping/select 接口,可以直接使用相同的参数格式调用本接口。
## 返回参数
| 参数名称 | 类型 | 示例值 | 说明 |
| --- | --- | --- | --- |
| code | Integer | 0 | 系统状态码0 = 成功,非 0 为失败 |
| msg | String | null | 系统消息:成功时为 null失败时返回具体系统提示信息 |
| sessionId | String | 66d807b0-abbc-4b57-aeff-bbf4fc29fb75 | UUID: 与航司通信的session值可以用于加速后续动作如继续获取包裹选座下单。 |
| itineraries | Array\<Itinerary\> | - | 航线组合列表。由于在本接口已确定路线和FareFamily数组长度 ≤ 1 |
| ancillaries | Array\<[AncillaryGroup](#ancillarygroup)\> | {json实体} | 辅营产品分组列表(新结构,三级嵌套) |
| ancillaryList | Array | {json实体} | *(兼容旧结构)*`ancillaries` 为空时可能存在,建议优先使用 `ancillaries` |
### 异步模式响应async=true
`async=true` 且无可用缓存时,系统立即返回 202 Accepted
| 参数名称 | 类型 | 说明 |
|----------|------|------|
| code | Integer | 202表示请求已接受正在处理中 |
| msg | String | "Accepted. Please wait for the webhook callback" |
| requestId | String | 请求IDUUID用于追踪和关联 Webhook 回调 |
| status | String | "PROCESSING"(处理中) |
完整结果将通过预先配置的 Webhook 回调返回。
---
## 响应实体介绍
响应采用三级嵌套结构:**AncillaryGroup** → **AncillaryCategory****BaggageOption**
```
ancillaries (Array)
└─ AncillaryGroup ← 按航程方向 + 运价家族分组
└─ categoryList (Array)
└─ AncillaryCategory ← 按行李类别分组
└─ categoryDetail (Array)
└─ BaggageOption ← 具体行李选项及价格
```
### AncillaryGroup最外层
| 字段名 | 类型 | 说明 |
|--------|------|------|
| currency | String | 售价币种ISO 4217`CAD``EUR``USD` |
| fareFamilyType | String | 运价家族类型,如 `BASIC``SMART``PROMO` |
| journeyDirection | String | 航程方向:`outbound`(去程)/ `inbound`(回程) |
| categoryList | Array\<[AncillaryCategory](#ancillarycategory)\> | 辅营类别列表 |
### AncillaryCategory中间层
| 字段名 | 类型 | 说明 |
|--------|------|------|
| categoryCode | String | 类别代码,见 [categoryCode 说明](#categorycode-说明) |
| maxPieces | String | 该类别最多可购买件数 |
| pricingMode | String | 定价模式,见 [pricingMode 说明](#pricingmode-说明) |
| categoryDetail | Array\<[BaggageOption](#baggageoption)\> | 选项列表 |
### BaggageOption最内层
| 字段名 | 类型 | 说明 |
|--------|------|------|
| pieceNo | Integer | 第 N 件。STEP 模式下为序号1, 2, 3...FLAT/TIERED 模式下为 1 |
| weight | BigDecimal | 行李额重量KG |
| size | String | 尺寸限制,如 `"55x40x23cm"` |
| price | BigDecimal | 该选项/该件价格 |
| maxPieces | Integer | 该选项可购买数量 |
---
### 响应示例
#### 示例1FLAT + STEP 混合
```json
{
"code": 0,
"msg": null,
"sessionId": "NYW34327-f212fbacdd054dda8a509e846e300ebf",
"itineraries": [],
"ancillaries": [
{
"currency": "CAD",
"fareFamilyType": "BASIC",
"journeyDirection": "outbound",
"categoryList": [
{
"categoryCode": "CabinBaggageOverheadLocker",
"maxPieces": "1",
"pricingMode": "FLAT",
"categoryDetail": [
{ "pieceNo": 1, "weight": 10, "size": "55x40x23cm", "price": 39.00, "maxPieces": 1 },
{ "pieceNo": 1, "weight": 23, "size": "55x40x23cm", "price": 64.00, "maxPieces": 1 }
]
},
{
"categoryCode": "StandardCheckedBaggage",
"maxPieces": "5",
"pricingMode": "STEP",
"categoryDetail": [
{ "pieceNo": 1, "weight": 10, "size": "", "price": 39.00, "maxPieces": 1 },
{ "pieceNo": 1, "weight": 23, "size": "", "price": 64.00, "maxPieces": 1 },
{ "pieceNo": 2, "weight": 23, "size": "", "price": 64.00, "maxPieces": 1 },
{ "pieceNo": 3, "weight": 23, "size": "", "price": 99.00, "maxPieces": 1 }
]
}
]
},
{
"currency": "CAD",
"fareFamilyType": "BASIC",
"journeyDirection": "inbound",
"categoryList": [
{
"categoryCode": "CabinBaggageOverheadLocker",
"maxPieces": "1",
"pricingMode": "FLAT",
"categoryDetail": [
{ "pieceNo": 1, "weight": 10, "size": "55x40x23cm", "price": 35.00, "maxPieces": 1 }
]
}
]
}
]
}
```
#### 示例2TIERED 模式(按重量档位)
```json
{
"code": 0,
"msg": null,
"sessionId": "...",
"ancillaries": [
{
"currency": "CNY",
"fareFamilyType": "ECONOMY",
"journeyDirection": "outbound",
"categoryList": [
{
"categoryCode": "StandardCheckedBaggage",
"maxPieces": "1",
"pricingMode": "TIERED",
"categoryDetail": [
{ "pieceNo": 1, "weight": 20, "size": "", "price": 300.00, "maxPieces": 1 },
{ "pieceNo": 1, "weight": 25, "size": "", "price": 500.00, "maxPieces": 1 },
{ "pieceNo": 1, "weight": 30, "size": "", "price": 800.00, "maxPieces": 1 }
]
}
]
}
]
}
```
---
### categoryCode 说明
| categoryCode | 说明 |
|--------------|------|
| CabinBaggageOverheadLocker | 手提行李(头顶行李架) |
| CabinBaggageUnderSeat | 手提行李(座椅下) |
| StandardCheckedBaggage | 托运行李 |
### pricingMode 说明
| pricingMode | 说明 | 渲染建议 |
|-------------|------|----------|
| FLAT | 固定单价,每件独立产品 | 卡片网格展示,每个选项一张卡片 |
| STEP | 逐件定价,第 N 件对应不同价格 | 表格展示,按 pieceNo 分组,同 pieceNo 行用背景色合并 |
| TIERED | 按总重量档位选择,不计件 | 单选列表展示,互斥选项 |
> **提示**
> - `ancillaries` 数组按 `journeyDirection`outbound/inbound和 `fareFamilyType` 分组
> - 优先使用 `ancillaries`(新三级结构),`ancillaryList` 为兼容旧结构
> - 同一 `pricingMode` 内,`categoryDetail` 按 `pieceNo` 升序排列
> - STEP 模式下,相同 `pieceNo` 可能有多个重量/价格选项(如第 1 件可选 10kg 或 23kg

View File

@@ -1,354 +0,0 @@
<!-- mipuyun-api-doc: shopping-seat -->
# shopping/seat - 选座报价
## 用户使用场景
> 获取航班座位图信息,包括每个座位的位置、价格、状态(可用/已占)、以及座位特性(靠窗、靠过道、紧急出口等)
## 性能指标
> - 通过 sessionId 访问,或者 sessionId 有效,响应时间 < 10 秒
> - 接口成功率:> 90%
## 请求说明
| **请求地址** | https://${endpoint}/shopping/seat |
| --- | --- |
| **请求方法** | POST |
| **注意事项** | Header 必须带压缩请求 |
## 请求参数
### Header
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret |
### 请求体
```json
{
"sessionId": "743768c1-8bdc-46ed-a586-94fdbd0ce24a",
"journeyType": "OW",
"origin": "OTP",
"destination": "CAG",
"departureDate": "20260116",
"adults": 1,
"children": 0,
"infants": 0,
"agent": "AXZ",
"proxy": "res_mipu_bookxWdyg-it",
"outboundFareFamily": "BASIC",
"inboundFareFamily": "",
"outboundSegments": [
{
"carrier": "XZ",
"originAirport": "OTP",
"destinationAirport": "FCO",
"flightNumber": "XZ3115",
"departureDate": "20260116"
},
{
"carrier": "XZ",
"originAirport": "FCO",
"destinationAirport": "CAG",
"flightNumber": "XZ2341",
"departureDate": "20260116"
}
],
"inboundSegments": []
}
```
| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** |
| --- | --- | --- | --- | --- |
| sessionId | String | 否 | 743768c1-8bdc-46ed-a586-94fdbd0ce24a | 目的是为了复用前一步的 shopping/search 或 shopping/select 的 session提升速度 & 成功率。如带入 session请务必保障1. 出发到达/旅行日期/人数与前一步保持一致 |
| journeyType | String | 是 | "OW" | 行程为单程还是往返程<br>OW单程 OneWay<br>RT往返 RoundTrip |
| origin | String | 是 | "OTP" | 出发地,为 IATA 3 字码兼容城市或者机场 3 字码 |
| destination | String | 是 | "CAG" | 到达地,为 IATA 3 字码兼容城市或者机场 3 字码 |
| departureDate | String | 是 | 20260116 | 出发日期,格式为 `YYYYMMDD`(如 2024 年 5 月 1 日为 20240501 |
| returnDate | String | 否 | 20240423仅 RT 必填) | 返程日期,格式同 departureDate仅当 journeyType=RT 时必传OW 时可传空 |
| adults | Integer | 是 | 1 | 成人,乘机人数量 |
| children | Integer | 是 | 0 | 儿童,乘机人数量 |
| infants | Integer | 是 | 0 | 婴儿,乘机人数量 |
| agent | String | 是 | "AXZ" | Agent 代码 |
| proxy | String | 是 | "res_mipu_bookxWdyg-it" | 本次获取航司数据采用的代理方式。如果需要指定代理国家请在代理用户后面加上国家二字码。如 myproxy-USUS 代表美国 |
| outboundSegments | Array<FlightSegmentRequest> | 是 | 见示例 | 去程航段信息,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) |
| inboundSegments | Array<FlightSegmentRequest> | 否 | | 返程航段信息,往返行程为必须,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) |
| outboundFareFamily | String | 是 | "BASIC" | 去程 FareFamily 的名字 |
| inboundFareFamily | String | 否 | | 回程 FareFamily 的名字,仅往返程有值 |
| acceptCacheMinutes | Integer | 否 | 5 | 默认值5<br>**如无特殊情况,本参数不建议设置或调整。**为了避免频繁请求航司设置的缓存,用户可以指定缓存时长,不填写为 5 |
| async | Boolean | 是 | false | 是否启用 Webhook 异步模式。<br>- **false**: 同步模式,等待完整结果返回<br>- **true**: 异步模式,立即返回 202结果通过 Webhook 回调<br><br>**异步模式说明**<br>1. 系统会同步检查是否有新鲜缓存acceptCacheMinutes如有则立即返回<br>2. 无缓存时返回 202 Accepted包含 requestId 用于追踪<br>3. 航司数据返回后,通过预先配置的 Webhook 回调通知<br><br>**注意**:使用异步模式需提前配置 `shopping_response` 类型的 Webhook |
| callbackId | String | 否 | - | 异步模式下的业务回调标识,用于客户自行匹配业务。建议格式:`seat_{origin}_{destination}_{departureDate}_{adults}-{children}-{infants}_{flightNumber}`,最大 50 个字符 |
## 返回参数
### 响应示例
```json
{
"cached": false,
"code": 0,
"itineraries": [],
"msg": "success",
"seatMapList": [
{
"segmentIndex": 1,
"journeyDirection": "outbound",
"segment": {
"flightNumber": "DM200",
"dptAirport": "AUA",
"arrAirport": "CUR",
"departureTime": "202604261100",
"arrivalTime": "202604261150"
},
"cabins": [
{
"deck": "main",
"cabinType": "Business",
"cabinLayout": {
"columns": [
{ "designator": "A", "characteristics": "W" },
{ "designator": "C", "characteristics": "A" },
{ "designator": "D", "characteristics": "A" },
{ "designator": "G", "characteristics": "A" },
{ "designator": "J", "characteristics": "W" }
],
"rows": { "first": 1, "last": 8 },
"exitRowPositions": []
},
"rows": [
{
"number": 1,
"seats": [
{
"column": "A",
"currency": "EUR",
"price": 120.0,
"seatCharacteristics": ["W", "L"],
"seatStatus": "F"
}
]
}
]
},
{
"deck": "main",
"cabinType": "Economy",
"cabinLayout": {
"columns": [
{ "designator": "A", "characteristics": "W" },
{ "designator": "B", "characteristics": "M" },
{ "designator": "C", "characteristics": "A" },
{ "designator": "D", "characteristics": "A" },
{ "designator": "E", "characteristics": "M" },
{ "designator": "F", "characteristics": "W" }
],
"rows": { "first": 9, "last": 32 },
"exitRowPositions": [
{ "first": 16, "last": 16 },
{ "first": 17, "last": 17 }
]
},
"rows": [
{
"number": 9,
"seats": [
{
"column": "A",
"currency": "EUR",
"price": 21.0,
"seatCharacteristics": ["W", "IE", "E", "L"],
"seatStatus": "F"
}
]
}
]
}
]
}
],
"sessionId": "shopping-seat-fa149f406bcd4df9a1a318d230afc422"
}
```
### 响应字段说明
| 参数名称 | 类型 | 说明 |
| --- | --- | --- |
| code | Integer | 系统状态码0 = 成功,非 0 为失败 |
| msg | String | 系统消息:成功时为 null失败时返回具体系统提示信息 |
| cached | Boolean | 是否使用缓存数据 |
| sessionId | String | UUID与航司通信的 session 值,可以用于加速后续动作,如下单 |
| itineraries | Array<Itinerary> | 航线组合列表(本接口通常为空数组) |
| seatMapList | Array<SeatMap> | **座位图列表,按航段返回每个航段的座位信息** |
### 异步模式响应async=true
`async=true` 且无可用缓存时,系统立即返回 202 Accepted
| 参数名称 | 类型 | 说明 |
|----------|------|------|
| code | Integer | 202表示请求已接受正在处理中 |
| msg | String | "Accepted. Please wait for the webhook callback" |
| requestId | String | 请求IDUUID用于追踪和关联 Webhook 回调 |
| status | String | "PROCESSING"(处理中) |
完整结果将通过预先配置的 Webhook 回调返回。
### SeatMap座位图对象说明
seatMapList 数组中的每个元素代表一个航段的座位图,结构如下:
| 字段名 | 类型 | 说明 |
| --- | --- | --- |
| segmentIndex | Integer | 航段索引(从 1 开始),对应 outboundSegments 或 inboundSegments 中的位置 |
| journeyDirection | String | 行程方向:`outbound`(去程)/ `inbound`(返程) |
| segment | Object | 航段信息对象,包含航班号、出发/到达机场和时间 |
| cabins | Array\<Cabin\> | 客舱信息列表,按舱位等级排列(如商务舱在前、经济舱在后) |
#### Segment航段对象
| 字段名 | 类型 | 说明 |
| --- | --- | --- |
| flightNumber | String | 航班号,如 "DM200" |
| dptAirport | String | 出发机场 IATA 三字码,如 "AUA" |
| arrAirport | String | 到达机场 IATA 三字码,如 "CUR" |
| departureTime | String | 出发时间,格式 `YYYYMMDDHHmm`,如 "202604261100" |
| arrivalTime | String | 到达时间,格式 `YYYYMMDDHHmm`,如 "202604261150" |
#### Cabin机舱对象
| 字段名 | 类型 | 说明 |
| --- | --- | --- |
| deck | String | 甲板类型,如 "main" 表示主甲板 |
| cabinType | String | 客舱类型,可选值:`Economy`(经济舱)、`Business`(商务舱)、`FirstClass`(头等舱)、`PremiumEconomy`(高端经济舱) |
| cabinLayout | Object | 机舱布局信息 |
| rows | Array\<Row\> | 座位行列表 |
#### CabinLayout机舱布局对象
| 字段名 | 类型 | 说明 |
| --- | --- | --- |
| columns | Array<Column> | 列信息数组,描述每列的座位特性 |
| rows | Object | 行范围,包含 `first`(起始行号)和 `last`(结束行号) |
| exitRowPositions | Array<Object> | 紧急出口行位置数组,每个对象包含 `first``last` 行号 |
#### Column对象
| 字段名 | 类型 | 说明 |
| --- | --- | --- |
| designator | String | 列标识符,如 "A"、"B"、"C" 等 |
| characteristics | String | 列特性代码(见座位特性说明) |
#### Row座位行对象
| 字段名 | 类型 | 说明 |
| --- | --- | --- |
| number | Integer | 行号 |
| seats | Array<Seat> | 该行的座位列表 |
#### Seat座位对象
| 字段名 | 类型 | 示值 | 说明 |
| --- | --- | --- | --- |
| column | String | "A" | 列标识符A、B、C 等) |
| seatStatus | String | "F" | 座位状态<br>**F**Free可用<br>**O**Occupied已占 |
| seatCharacteristics | Array<String> | ["W", "I"] | 座位特性数组(见下文座位特性说明) |
| price | BigDecimal | 15.0 | 座位价格(该座位选座费用) |
| currency | String | "EUR" | 价格币种 |
### 座位特性说明
seatCharacteristics 数组中的特性代码:
| 代码 | 英文 | 中文说明 |
| --- | --- | --- |
| W | Window | 靠窗座位 |
| A | Aisle | 靠过道座位 |
| M | Middle | 中间座位 |
| I | Standard | 标准座位 |
| IE | ExitRow | 紧急出口排 |
| E | ExtraLegroom | 额外腿部空间 |
| L | LimitedRecline | 有限后倾 |
## 常见问题
### 如何理解 seatMapList 的结构?
每个航段Segment都有一个独立的座位图每个座位图可包含多个舱位cabins。例如
- 单程直飞seatMapList 长度为 1该航段可能包含 1 个或多个 cabins如商务舱 + 经济舱)
- 单程转机2 个航段seatMapList 长度为 2每个航段各有独立的 cabins
- 往返程直飞seatMapList 长度为 2去程 + 回程),每个航段各有独立的 cabins
segmentIndex 从 1 开始,与请求中的 outboundSegments/inboundSegments 数组索引对应。cabins 数组中每个 Cabin 对象代表一个舱位区域,通过 cabinType 区分舱位等级。
### 座位状态有哪些?
- **F (Free)**:座位可选,未被占用
- **O (Occupied)**:座位已被占用,不可选择
### 如何识别靠窗/靠过道座位?
通过 seatCharacteristics 数组判断:
- 包含 "W":靠窗座位
- 包含 "A":靠过道座位
- 包含 "M":中间座位(通常在 3-3 或 3-4-3 布局的中间列)
### 紧急出口座位如何识别?
包含以下特性的座位通常是紧急出口排:
- **IE (ExitRow)**:紧急出口排
- **E (ExtraLegroom)**:额外腿部空间
这些座位通常价格更高,腿部空间更大,但可能有年龄限制(如 14 岁以上)。
### 价格为 0 的座位是什么意思?
价格为 0 表示该座位免费可选,不需要额外付费。这种情况常见于:
- 高端舱位(商务舱、头等舱)
- 航司促销活动
- 某些会员权益
## 业务案例
### 单人单程选座查询
#### 请求Request
```json
{
"journeyType": "OW",
"origin": "OTP",
"destination": "CAG",
"departureDate": "20260116",
"adults": 1,
"children": 0,
"infants": 0,
"agent": "AXZ",
"outboundFareFamily": "BASIC",
"outboundSegments": [
{
"carrier": "XZ",
"originAirport": "OTP",
"destinationAirport": "CAG",
"flightNumber": "XZ3115",
"departureDate": "20260116"
}
]
}
```
#### 响应Response
响应包含完整的座位图,每个座位都有:
- 位置信息(行号 + 列号)
- 价格信息price + currency
- 状态信息seatStatusF/O
- 特性信息seatCharacteristics靠窗/靠过道/紧急出口等)
在线查看完整示例https://jsonhero.io/j/XmTLWK94Ae5X

View File

@@ -1,187 +0,0 @@
<!-- mipuyun-api-doc: async-query -->
# async/query - 异步查询
# 异步查询async/queryAPI 说明文档
## 用户使用场景
> 本接口目的是:
> 当异步调用API返回code:202时通过本接口查询异步请求的结果。
## 使用时机
> 当异步调用API返回 `code: 202` 时,表示请求已接收并正在后台处理。
> 此时响应中会包含 `requestId`,使用本接口查询处理结果。
## 性能指标
> - 响应速度:毫秒级(直接从缓存读取)
> - 请求频率QPS无限制
> - 数据保留时间24小时
## 请求说明
| **请求地址** | https://${endpoint}/async/query/{requestId} |
| --- | --- |
| **请求方法** | GET |
| **注意事项** | Header 必须带认证信息。 |
## 请求参数
### Header
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret |
### 路径参数
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| requestId | String | 是 | abc123-def456 | 异步请求返回的 requestId |
## 响应说明
### 有结果(请求已处理完成)
当异步请求已处理完成,**直接返回原始响应数据**(不包裹在统一响应结构中)。
**判断方式**:响应体中直接包含业务数据字段(如 `itineraries``bookingId` 等),无 `code` 字段包裹。
**示例 - 有结果(搜索)**
```json
{
"itineraries": [
{
"itineraryId": "xxx",
"outbound": {
"segments": [...]
},
"price": {
"total": 1200.00
}
}
],
"sessionId": "session-xxx"
}
```
**示例 - 有结果(预订)**
```json
{
"bookingId": "BK123456",
"status": "HOLD",
"expiryTime": "2025-01-20T12:00:00Z"
}
```
### 无结果(请求未找到或已过期)
当异步请求不存在或已过期时,返回 404 错误。
**判断方式**:响应体中 `code: 404`
**示例 - 无结果**
```json
{
"code": 404,
"msg": "Not found"
}
```
**可能原因**
1. `requestId` 不存在
2. 请求已过期(超过 24 小时)
3. 请求属于其他租户clientCode 不匹配)
### 请求仍在处理中
当异步请求仍在处理中时,会返回处理中的状态。
**示例 - 处理中**
```json
{
"code": 202,
"msg": "Processing",
"requestId": "abc123-def456"
}
```
## 完整示例
### 请求示例
```bash
curl -X GET "https://api.example.com/async/query/abc123-def456" \
-H "client-key: your-client-key" \
-H "client-secret: your-client-secret"
```
### 响应示例(有结果)
```json
{
"itineraries": [
{
"itineraryId": "itinerary-001",
"outbound": {
"segments": [
{
"carrier": "CA",
"flightNumber": "CA123",
"origin": "PEK",
"destination": "SHA",
"departureTime": "2025-01-20T08:00:00",
"arrivalTime": "2025-01-20T10:00:00"
}
]
},
"price": {
"currency": "CNY",
"total": 1200.00
}
}
],
"sessionId": "session-xxx"
}
```
### 响应示例(无结果)
```json
{
"code": 404,
"msg": "Not found"
}
```
## 常见问题
### Q: 如何获取 requestId
A: 当异步调用API返回 `code: 202` 时,响应中会包含 `requestId` 字段。
```json
{
"code": 202,
"msg": "Async request accepted",
"requestId": "abc123-def456"
}
```
### Q: 结果会保留多久?
A: 异步请求的结果会保留 24 小时,超时后自动删除。
### Q: 可以查询其他租户的请求吗?
A: 不可以。接口会根据认证信息中的 clientCode 进行租户隔离,只能查询本租户的请求。
### Q: 建议的轮询策略是什么?
A: 建议每隔 2-5 秒查询一次,最多查询 10 次。如果 10 次后仍未获取结果,可能是航司响应较慢,建议稍后重试。

View File

@@ -1,163 +0,0 @@
<!-- mipuyun-api-doc: booking-hold -->
# booking/hold - 下单
# 下单(booking/hold) API 说明文档
## 使用场景
> 1. 用户在航班验价通过后,确认预订意向,提交乘客与联系人信息以生成正式订单
## 错误场景(不应该使用本接口的场景)
> 1. 未通过验价接口checkRoute获取有效 offerId 时(此场景需先调用验价接口,确保价格与航线有效性)
> 1. 验价接口返回的 offerId 已过期(通常与支付截止时间关联,过期后需重新验价获取新 offerId
> 1. 乘客信息、证件信息未完整填写或格式错误时(应先校验信息合法性,避免接口调用失败)
## 性能指标
> - 响应速度98% 的请求响应速度 < 15000ms因需实时对接航司数据响应速度明显慢于搜索接口
## 请求说明
| **请求地址** | https://${endpoint}/booking/hold |
| --- | --- |
| **请求方法** | POST |
## 请求参数
### Header
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝。 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境secret |
###
### 请求体
```json
{
"orderId": "abbc4b57fb75",
"journeyType": "OW",
"origin": "MIL",
"destination": "CAG",
"departureDate": "20251118",
"adults": 1,
"children": 0,
"infants": 0,
"agent": "u2web",
"proxy": "myproxy-us",
"outboundFareFamily": "promo",
"inboundFareFamily": "",
"maximumFareThreshold": 15009.69,
"currency": "THB",
"outboundSegments": [
{
"carrier": "SL",
"originAirport": "PHS",
"destinationAirport": "DMK",
"flightNumber": "SL557",
"departureDate": "20251119"
}
],
"inboundSegments": [
],
"passengers": [
{
"firstName": "first",
"lastName": "last",
"passengerType": "ADT",
"dateOfBirth": "19970616",
"gender": "M",
"documentNumber": "E12343214",
"documentType": "PP",
"documentIssuePlace": "CN",
"documentExpirationDate": "20260731",
"nationality": "CN",
"mobile": "0086-18923726222",
"frequentFlyerNumber": "",
"frequentFlyerCarrier": ""
}
],
"contactInfo": {
"firstName": "san",
"lastName": "zhang",
"address": "dfdsaqqq",
"phoneCountryCode": "0086",
"phone": "18912345678",
"email": "san.zhang@gmail.com",
"postCode": "310006",
"city": "hangzhou",
"province": "zejiang",
"country": "CN"
}
}
```
#### 参数详情
| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** |
| --- | --- | --- | --- | --- |
| orderId | String | 是 | abbc4b57fb75 | 你系统内的订单号,返回的有效报价唯一标识,用于关联待下单的航线与价格 |
| journeyType | String | 是 | "OW" | 行程为单程还是往返程OW单程OneWayRT往返RoundTrip |
| origin | String | 是 | "CJJ" | 出发地为IATA 3字码兼容城市或者机场3字码 |
| destination | String | 是 | "SHA" | 到达地为IATA3字码兼容城市或者机场3字码 |
| departureDate | String | 是 | 20240326 | 出发日期,格式为 `YYYYMMDD`(如 2024 年 5 月 1 日为 20240501 |
| returnDate | String | 否 | 20240423仅 RT 必填) | 返程日期,格式同 departureDate仅当 journeyType=RT 时必传OW 时可传空 |
| adults | Integer | 是 | 2 | 成人,乘机人数量 |
| children | Integer | 是 | 1 | 儿童,乘机人数量 |
| infants | Integer | 是 | 0 | 婴儿,乘机人数量 |
| outboundSegments | Array<FlightSegmentRequest> | 是 | | 去程航段信息,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) |
| inboundSegments | Array<FlightSegmentRequest> | 否 | | 返程航段信息往返行程为必须,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) |
| outboundFareFamily | String | 是 | | 去程FareFamily的名字 |
| inboundFareFamily | String | 否 | | 回程FareFamily的名字仅往返程有值 |
| maximumFareThreshold | Number | **是** | 15009.69 | 最高票价阈值,超过此价格的报价将不会被接受(用于限制订单金额) |
| currency | String | **是** | "THB" | 出票币种代码遵循ISO 4217标准USD、CNY、THB、EUR等。<br>**注意**:必须为出票币种,系统不会进行任何汇率转换 |
| proxy | String | 是 | | 本次获取航司数据采用的代理方式。如果需要指定代理国家请在代理用户后面加上国家二字码。如myproxy-US,US代表美国 |
| passengers | Array | 是 | 请查看实体定义([Passenger](07-02_passenger.md)) | 乘客信息列表,支持 1-9 名乘客(具体数量受航司限制),每个数组元素为单个乘客详情 |
| contactInfo | Object | 是 | 请查看实体定义([ContactInfo](07-03_contact-info.md)) | 联系人信息,用于接收订单通知、行程单等 |
##
## 返回参数
探索一个响应实体https://jsonhero.io/j/pPOhukgKMcln/editor
| **参数名称** | **类型** | **示例值** | **说明** |
| --- | --- | --- | --- |
| code | Integer | 0 | 系统状态码0 = 成功,非 0 = 失败 |
| msg | String | null | 系统消息:成功时为 null失败时返回具体错误提示如 “证件有效期不足”“航司订单创建超时”) |
| pnr | String | 000000 | 仅同步模式会响应PNR信息注意对于不支持Hold的航司我们也会实施与航司进行交互并将动作完成在支付前一步这种情况也可以返回000000 |
| pnrExpiryTime | String | YYYYMMDDHHMMSS20251225121212 | hold到的PNR有效期此处时间为UTC 0时区格式。 |
| status | String | | * 异步模式,且进行中为进行中,失败,成功 * 同步模式,为完成,失败,成功 |
| orderId | String | | 入参给的orderId |
| feeItems | Array | 参考Feeitems实体 | 从航司处获取的报价细项拆分[feeItems 实体定义](08-06_fee-items.md) |
## 实体定义说明
本接口涉及的实体对象定义如下:
### 请求参数实体
| 实体名称 | 说明 | 链接 |
| --- | --- | --- |
| FlightSegmentRequest | 航段请求对象,用于描述单段航班的核心信息 | [查看详情](07-01_flight-segment-request.md) |
| Passenger | 乘客信息对象,包括身份信息、证件信息、常旅客信息等 | [查看详情](07-02_passenger.md) |
| ContactInfo | 联系人信息对象,用于接收订单通知、行程单等 | [查看详情](07-03_contact-info.md) |
### 返回参数实体
| 实体名称 | 说明 | 链接 |
| --- | --- | --- |
| Itinerary | 行程组合信息,包含去程和返程航段列表 | [查看详情](08-01_itinerary.md) |
| FlightFare | 票价信息对象,描述不同乘客类型的费用构成 | [查看详情](08-02_flight-fare.md) |
| SegmentElement | 航段响应对象,描述单段航班的详细信息 | [查看详情](08-03_segment-element.md) |
| FreeBaggage (freeAncillaryList) | 免费行李额元素,描述每个航段每种乘客类型的免费行李配额 | [查看详情](08-04_flight-policy.md) |
| RefundRule (refundRules) | 退改规则对象,描述航班的退改签政策(预留字段,暂未提供) | [查看详情](08-04_flight-policy.md) |
| FlightPolicy | 航班政策对象,包含行李、退改等政策信息 | [查看详情](08-04_flight-policy.md) |
| AncillaryProduct | 附加产品对象,描述付费行李等附加服务 | [查看详情](08-05_ancillary-product.md) |
| feeItems | 费用明细对象,拆分航班预订的各类费用 | [查看详情](08-06_fee-items.md) |

View File

@@ -1,302 +0,0 @@
<!-- mipuyun-api-doc: booking-payment -->
# booking/payment - 支付
# 更新支付信息(booking/payment) API 说明文档
## 使用场景
> 1. 客户完成 booking/hold 下单后,需要更新订单的支付信息(如支付方式、信用卡信息等)
> 2. 用于在支付流程中记录或更新支付相关信息
> 3. 使用 Apple Pay 支付时,可指定设备标签来选择执行任务的设备
## 错误场景(不应该使用本接口的场景)
> 1. 未完成 booking/hold 下单,订单不存在时
> 2. orderId 为空或未提供时
> 3. 订单不属于当前客户clientCode 不匹配)时
## 性能指标
> - 响应速度98% 的请求响应速度 < 2000ms
> - 接口成功率:> 99%
## 请求说明
| **请求地址** | https://${endpoint}/booking/payment |
| --- | --- |
| **请求方法** | POST |
## 请求参数
### Header
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝。 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret |
###
### 请求体
```json
{
"orderId": "abbc4b57fb75",
"proxy": "myproxy-us",
"paymentMethod": "CREDIT_CARD",
"retryCount": 0,
"creditCard": {
"number": "4111111111111111",
"CVV": "123",
"expiryMonth": "12",
"expiryYear": "25",
"lastName": "Doe",
"firstName": "John",
"country": "US"
}
}
```
#### 参数详情
| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** |
| --- | --- | --- | --- | --- |
| orderId | String | **是** | abbc4b57fb75 | 客户的订单号,必须是通过 booking/hold 创建的订单 |
| proxy | String | 是 | | 本次获取航司数据采用的代理方式。如果需要指定代理国家请在代理用户后面加上国家二字码。如myproxy-US,US代表美国 |
| paymentMethod | String | 否 | "CREDIT_CARD" | 支付方式代码:<br>**BALANCE**:余额支付<br>**CREDIT_CARD**:信用卡<br>**APPLE_PAY**Apple Pay支持设备标签 |
| retryCount | Integer | 否 | 0 | 支付重试次数,默认为 0不重试 |
| creditCard | Object | 条件必填 | | 信用卡信息对象,当 paymentMethod 为 CREDIT_CARD 时必填 |
| ├─ number | String | **条件必填** | "4111111111111111" | 信用卡号13-19 位数字) |
| ├─ CVV | String | **条件必填** | "123" | CVV 安全码3-4 位数字) |
| ├─ expiryMonth | String | **条件必填** | "12" | 有效期月份1-2 位数字) |
| ├─ expiryYear | String | **条件必填** | "25" | 有效期年份2 或 4 位数字) |
| ├─ lastName | String | **条件必填** | "Doe" | 持卡人姓 |
| ├─ firstName | String | **条件必填** | "John" | 持卡人名 |
| ├─ country | String | **条件必填** | "US" | 持卡人国家(二字码) |
| ├─ province | String | 否 | | 持卡人省份/州 |
| ├─ city | String | 否 | | 持卡人城市 |
| ├─ postCode | String | 否 | | 持卡人邮编 |
| ├─ address | String | 否 | | 持卡人地址 |
| ├─ phone | String | 否 | | 持卡人电话(实体卡时使用) |
| ├─ email | String | 否 | | 持卡人邮箱(实体卡时使用) |
| ├─ maximumPaymentAmount | BigDecimal | 否 | | 最大支付金额 |
| └─ reusable | Boolean | 否 | false | 是否为多次卡(默认单次卡) |
| deviceTag | String | 否 | "EUR" | 设备标签,仅当 paymentMethod 为 APPLE_PAY 时可选,用于指定执行任务的设备 |
### 设备标签说明 (deviceTag)
- **适用场景**:仅当 `paymentMethod``APPLE_PAY` 时有效
- **作用**:指定执行任务的设备标签,系统会优先分配有对应标签的设备
- **可选性**:留空则自动分配设备
- **示例**
```json
{
"orderId": "abbc4b57fb75",
"paymentMethod": "APPLE_PAY",
"deviceTag": "EUR"
}
```
## 返回参数
### 成功响应
```json
{
"code": 0,
"msg": "Payment info updated successfully",
"orderId": "abbc4b57fb75"
}
```
### 失败响应
#### 缺少 orderId
```json
{
"code": 400,
"msg": "orderId is required"
}
```
#### 订单不存在
```json
{
"code": 404,
"msg": "Booking not found",
"orderId": "abbc4b57fb75"
}
```
#### 更新失败
```json
{
"code": 500,
"msg": "Failed to update payment info",
"orderId": "abbc4b57fb75"
}
```
### 响应字段说明
| **参数名称** | **类型** | **示例值** | **说明** |
| --- | --- | --- | --- |
| code | Integer | 0 | 系统状态码<br>**0**:成功<br>**400**缺少必填参数orderId<br>**404**:订单不存在或不属于当前客户<br>**500**:更新支付信息失败 |
| msg | String | "Payment info updated successfully" | 系统消息:成功时返回成功提示,失败时返回具体错误信息 |
| orderId | String | "abbc4b57fb75" | 客户的订单号,与请求中的 orderId 一致 |
## 业务流程
### 支付信息更新流程
```
1. 客户完成航班搜索shopping/search
2. 客户选择航班并验价shopping/select
3. 客户提交订单booking/hold→ 获取 orderId
4. 客户选择支付方式调用支付接口booking/payment
5. 支付完成
```
## 常见问题
### 为什么 orderId 是必填的?
orderId 是订单的唯一标识符,系统需要通过它来:
1. 定位到具体的订单
2. 验证订单是否属于当前客户(通过 clientCode
3. 确保只有合法的订单才能更新支付信息
### paymentMethod 支持哪些支付方式?
系统支持三种支付方式:**BALANCE**(余额支付)、**CREDIT_CARD**(信用卡)、**APPLE_PAY**Apple Pay支持设备标签。详见上方参数表格中的 paymentMethod 说明。
### Apple Pay 中的 deviceTag 是什么?
`deviceTag` 是设备标签,用于指定执行 Apple Pay 支付任务的设备:
- 仅在 `paymentMethod` 为 `APPLE_PAY` 时有效
- 可以指定有特定标签的设备执行任务
- 留空则系统自动分配可用设备
- 示例:`"deviceTag": "EUR"` 表示优先使用欧洲地区的设备
### 信用卡信息安全吗?
本接口仅用于记录支付信息到系统中。实际的支付处理通常由第三方支付网关完成,建议:
1. 在前端不要存储真实的完整信用卡号
2. 使用 PCI DSS 合规的支付网关处理实际支付
3. 仅存储支付网关返回的支付令牌token
### 如果订单不存在会怎样?
接口会返回 `code: 404` 和 `msg: "Booking not found"`。这种情况通常由以下原因造成:
1. orderId 错误或不存在
2. 订单属于其他客户clientCode 不匹配)
3. 订单已过期或被删除
## 业务案例
### 更新信用卡支付信息
#### 请求Request
```json
{
"orderId": "abbc4b57fb75",
"paymentMethod": "CREDIT_CARD",
"creditCard": {
"number": "4111111111111111",
"CVV": "123",
"expiryMonth": "12",
"expiryYear": "25",
"lastName": "Doe",
"firstName": "John",
"country": "US"
}
}
```
#### 响应Response
```json
{
"code": 0,
"msg": "Payment info updated successfully",
"orderId": "abbc4b57fb75"
}
```
### 使用 Apple Pay 并指定设备标签
#### 请求Request
```json
{
"orderId": "abbc4b57fb75",
"paymentMethod": "APPLE_PAY",
"deviceTag": "EUR"
}
```
#### 响应Response
```json
{
"code": 0,
"msg": "Payment info updated successfully",
"orderId": "abbc4b57fb75"
}
```
### 缺少 orderId 的错误请求
#### 请求Request
```json
{
"paymentMethod": "CREDIT_CARD"
}
```
#### 响应Response
```json
{
"code": 400,
"msg": "orderId is required"
}
```
### 订单不存在的错误请求
#### 请求Request
```json
{
"orderId": "nonexistent-order-id",
"paymentMethod": "CREDIT_CARD"
}
```
#### 响应Response
```json
{
"code": 404,
"msg": "Booking not found",
"orderId": "nonexistent-order-id"
}
```
## 错误码说明
| 错误码 | 说明 | 处理建议 |
| --- | --- | --- |
| 0 | 成功 | 支付信息已成功更新 |
| 400 | 请求参数错误 | 检查是否提供了必填的 orderId |
| 404 | 订单不存在 | 确认订单 ID 是否正确,且订单属于当前客户 |
| 500 | 服务器内部错误 | 系统处理异常,请稍后重试或联系技术支持 |

View File

@@ -1,424 +0,0 @@
<!-- mipuyun-api-doc: booking-query -->
# booking/query - 订单查询
# 订单查询(booking/query) API 说明文档
## 使用场景
> 1. 查询订单的详细信息和当前状态
> 2. 在支付完成后查询订单确认状态
> 3. 在客服系统中查询订单详情
> 4. 获取订单的完整行程、乘客和费用信息
## 错误场景(不应该使用本接口的场景)
> 1. 查询不存在的订单 ID
> 2. 查询不属于当前客户的订单clientCode 不匹配)
## 性能指标
> - 响应速度98% 的请求响应速度 < 500ms
> - 接口成功率:> 99.5%
## 请求说明
| **请求地址** | https://${endpoint}/booking/query |
| --- | --- |
| **请求方法** | POST |
## 请求参数
### Header
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret |
###
### 请求体
```json
{
"orderId": "abbc4b57fb75"
}
```
#### 参数详情
| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** |
| --- | --- | --- | --- | --- |
| orderId | String | **是** | abbc4b57fb75 | 客户的订单号,用于查询订单详情 |
## 返回参数
### 成功响应
```json
{
"orderId": "abbc4b57fb75",
"requestId": "req-12345",
"agentCode": "u2web",
"status": "CONFIRMED",
"payStatus": "PAID",
"origin": "MIL",
"destination": "CAG",
"journeyType": "OW",
"pnr": "ABC123",
"pnrExpiryTime": "2025-12-25T12:00:00Z",
"feeItems": {
"baseFare": 100.00,
"taxes": 50.50,
"total": 150.50,
"currency": "EUR"
},
"passengers": [
{
"lastName": "Rossi",
"firstName": "Mario",
"passengerType": "ADT",
"adult": true,
"child": false,
"infant": false,
"dateOfBirth": "1980-01-15",
"gender": "M",
"documentType": "PASSPORT",
"documentNumber": "AA1234567",
"ancillaries": []
}
],
"segments": [
{
"airline": "AZ",
"flightNo": "AZ1234",
"dptAirport": "MIL",
"arrAirport": "CAG",
"dptTime": "2025-11-18T10:30:00Z",
"arrTime": "2025-11-18T12:15:00Z",
"equipment": "A320"
}
],
"credential": null,
"promoCode": "SAVE10",
"createTime": "2024-11-15T10:00:00Z",
"updateTime": "2024-11-15T10:30:00Z",
"retryCount": 3,
"retriedCount": 0
}
```
## 错误码说明
本接口可能返回以下错误码:
| **错误码** | **错误名称** | **说明** | **处理建议** |
| --- | --- | --- | --- |
| 42005 | BOOKING_NOT_FOUND | 订单不存在 | 请检查 orderId 是否正确,或订单是否属于当前客户 |
| 41003 | PRICE_CHANGED | 票价高于预期 | 航司侧价格已变动,请检查响应中的 feeItems 获取最新价格,引导用户确认是否继续 |
### 失败响应
#### 订单不存在 (42005)
```json
{
"code": 42005,
"msg": "Booking not found"
}
```
#### 票价变动 (41003)
```json
{
"code": 41003,
"msg": "Price changed. Please check the latest price in feeItems.",
"data": {
"orderId": "abbc4b57fb75",
"feeItems": {
"baseFare": 110.00,
"taxes": 52.50,
"total": 162.50,
"currency": "EUR"
}
}
}
```
### 响应字段说明
#### 基本字段
| **参数名称** | **类型** | **说明** |
| --- | --- | --- |
| orderId | String | 客户的订单号 |
| requestId | String | 请求 ID |
| agentCode | String | 代理商代码 |
| status | String | 订单状态(见下方状态说明) |
| payStatus | String | 支付状态(见下方支付状态说明) |
| origin | String | 出发地 IATA 3 字码 |
| destination | String | 到达地 IATA 3 字码 |
| journeyType | String | 行程类型<br>**OW**:单程<br>**RT**:往返 |
#### PNR 相关
| **参数名称** | **类型** | **说明** |
| --- | --- | --- |
| pnr | String | 航司 PNR 码(订座记录编号) |
| pnrExpiryTime | String | PNR 有效期ISO 8601 格式UTC 0 时区) |
#### 费用信息
| **参数名称** | **类型** | **说明** |
| --- | --- | --- |
| feeItems | Map | 费用明细(原始 JSON 对象)<br>详见:[feeItems 实体定义](/api-doc?doc=entity-fee-items) |
#### 乘客和航班
| **参数名称** | **类型** | **说明** |
| --- | --- | --- |
| passengers | Array\<BookingPassenger\> | 乘客信息列表(见 BookingPassenger 定义) |
| segments | Array\<BookingSegment\> | 航班段列表(见 BookingSegment 定义) |
#### 其他信息
| **参数名称** | **类型** | **说明** |
| --- | --- | --- |
| credential | Object | 订单凭据信息 |
| promoCode | String | 促销码 |
| createTime | DateTime | 订单创建时间ISO 8601 格式 |
| updateTime | DateTime | 订单最后更新时间ISO 8601 格式 |
| retryCount | Integer | 可重试的次数 |
| retriedCount | Integer | 已经重试的次数 |
## 状态说明
### 订单状态 (status)
| **状态** | **说明** |
| --- | --- |
| PENDING | 订单处理中,等待航司确认 |
| CONFIRMED | 订单已确认PNR 已生成 |
| CANCELLED | 订单已取消 |
| FAILED | 订单失败 |
### 支付状态 (payStatus)
| **状态** | **说明** |
| --- | --- |
| UNPAID | 未支付 |
| PENDING | 支付处理中 |
| PAID | 已支付 |
| REFUNDED | 已退款 |
| FAILED | 支付失败 |
## 数据来源说明
本接口返回的数据优先从 `responseJson` 字段解析,如果 `responseJson` 为空或解析失败code 不是 0 且 feeItems/passengers/segments 均为空),则从 `holdResponse` 字段回退获取。
## 业务流程
### 订单查询场景
```
1. 客户下单booking/hold→ 获取 orderId
2. 客户选择支付方式并支付booking/payment
3. 查询订单状态booking/query→ 确认订单状态
4. 如需要可以取消订单booking/cancel
```
## 常见问题
### 如果订单不存在会返回什么?
接口会返回错误响应code 为 40401msg 为 "Booking not found"。这种情况通常由以下原因造成:
1. orderId 错误或不存在
2. 订单属于其他客户clientCode 不匹配)
3. 订单已过期或被删除
### status 和 payStatus 有什么区别?
- **status**订单的整体状态PENDING/CONFIRMED/CANCELLED/FAILED
- **payStatus**订单的支付状态UNPAID/PENDING/PAID/REFUNDED/FAILED
### feeItems 返回什么结构?
`feeItems` 是一个原始的 Map 对象,直接从航司响应的 JSON 中解析。
详细的字段说明请参考:[feeItems 实体定义](/api-doc?doc=entity-fee-items)
### passengers 和 segments 的详细定义是什么?
#### BookingPassenger乘客信息
| **参数名称** | **类型** | **说明** |
| --- | --- | --- |
| lastName | String | 乘客姓 |
| firstName | String | 乘客名 |
| passengerType | String | 乘客类型ADT/CHD/INF |
| adult | Boolean | 是否为成人 |
| child | Boolean | 是否为儿童 |
| infant | Boolean | 是否为婴儿 |
| dateOfBirth | String | 出生日期 |
| gender | String | 性别M/F |
| documentType | String | 证件类型(如 PASSPORT |
| documentNumber | String | 证件号码 |
| ancillaries | Array | 附加服务列表 |
#### BookingSegment航班段信息
| **参数名称** | **类型** | **说明** |
| --- | --- | --- |
| airline | String | 航司代码IATA 2 字码) |
| flightNo | String | 航班号(如 AZ1234 |
| dptAirport | String | 出发机场 IATA 3 字码 |
| arrAirport | String | 到达机场 IATA 3 字码 |
| dptTime | String | 出发时间ISO 8601 格式 |
| arrTime | String | 到达时间ISO 8601 格式 |
| equipment | String | 机型(如 A320、B737 |
### 什么时候会从 holdResponse 回退数据?
`responseJson` 满足以下任一条件时,会从 `holdResponse` 回退 feeItems/passengers/segments
1. `responseJson` 为空
2. `responseJson` 解析失败
3. `code` 不是 0 且 feeItems/passengers/segments 均为空
这确保了即使订单最终状态失败,也能获取到 hold 阶段的数据。
### 遇到票价变动错误 (PRICE_CHANGED) 怎么办?
当接口返回错误码 41003 (PRICE_CHANGED) 时,表示航司侧票价高于预期。这种情况通常发生在:
1. 下单后到支付前,航司调整了票价
2. 库存紧张导致票价上涨
3. 促销活动价格已过期
**处理流程:**
```
1. 接收到 41003 错误响应
2. 从响应的 data.feeItems 中获取最新价格
3. 向用户展示价格变动信息
4. 询问用户是否继续
5a. 用户同意 → 重新调用 booking/hold 使用新价格
5b. 用户拒绝 → 取消订单流程
```
**关键点:**
- 错误响应中的 `feeItems` 包含最新的价格明细
- 需要用户明确确认后才能继续交易
## 业务案例
### 查询已确认的订单
#### 请求Request
```json
{
"orderId": "abbc4b57fb75"
}
```
#### 响应Response
```json
{
"orderId": "abbc4b57fb75",
"agentCode": "u2web",
"status": "CONFIRMED",
"payStatus": "PAID",
"origin": "MIL",
"destination": "CAG",
"journeyType": "OW",
"pnr": "ABC123",
"passengers": [
{
"lastName": "Rossi",
"firstName": "Mario",
"passengerType": "ADT"
}
],
"segments": [
{
"airline": "AZ",
"flightNo": "AZ1234",
"dptAirport": "MIL",
"arrAirport": "CAG",
"dptTime": "2025-11-18T10:30:00Z",
"arrTime": "2025-11-18T12:15:00Z"
}
],
"createTime": "2024-11-15T10:00:00Z",
"updateTime": "2024-11-15T10:30:00Z"
}
```
### 查询往返程订单
#### 请求Request
```json
{
"orderId": "rt-order-12345"
}
```
#### 响应Response
```json
{
"orderId": "rt-order-12345",
"journeyType": "RT",
"origin": "MIL",
"destination": "CAG",
"status": "CONFIRMED",
"payStatus": "PAID",
"pnr": "XYZ789",
"passengers": [
{
"lastName": "Rossi",
"firstName": "Mario",
"passengerType": "ADT"
},
{
"lastName": "Bianchi",
"firstName": "Luca",
"passengerType": "ADT"
}
],
"segments": [
{
"airline": "AZ",
"flightNo": "AZ1234",
"dptAirport": "MIL",
"arrAirport": "CAG",
"dptTime": "2025-11-18T10:30:00Z",
"arrTime": "2025-11-18T12:15:00Z"
},
{
"airline": "AZ",
"flightNo": "AZ5678",
"dptAirport": "CAG",
"arrAirport": "MIL",
"dptTime": "2025-11-25T14:00:00Z",
"arrTime": "2025-11-25T15:45:00Z"
}
],
"createTime": "2024-11-15T10:00:00Z",
"updateTime": "2024-11-15T10:30:00Z"
}
```

View File

@@ -1,343 +0,0 @@
<!-- mipuyun-api-doc: booking-cancel -->
# booking/cancel - 取消订单
# 取消订单(booking/cancel) API 说明文档
## 使用场景
> 1. 客户主动取消未支付的订单
> 2. 客户因行程变更取消已确认的订单(需符合退改规则)
> 3. 系统自动取消过期未支付的订单
> 4. 客服代客取消订单
## 错误场景(不应该使用本接口的场景)
> 1. 订单已不可取消(如已起飞航班)
> 1. 订单不属于当前客户clientCode 不匹配)
> 1. 重复取消已取消的订单
## 性能指标
> - 响应速度98% 的请求响应速度 < 2000ms
> - 接口成功率:> 95%
## 请求说明
| **请求地址** | https://${endpoint}/booking/cancel |
| --- | --- |
| **请求方法** | POST |
## 请求参数
### Header
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝。 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret |
###
### 请求体
```json
{
"orderId": "abbc4b57fb75",
"cancelReason": "Customer request - flight change"
}
```
#### 参数详情
| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** |
| --- | --- | --- | --- | --- |
| orderId | String | **是** | abbc4b57fb75 | 客户的订单号,用于取消订单 |
| cancelReason | String | **是** | "Customer request" | 取消原因,用于记录和审计<br>**客户主动取消**Customer request<br>**行程变更**Flight change<br>**重复订单**Duplicate booking<br>**支付超时**Payment timeout<br>**其他**Other reason |
## 返回参数
### 成功响应
```json
{
"code": 0,
"msg": "Booking cancelled successfully",
"historyId": 789
}
```
### 失败响应
#### 订单不存在
```json
{
"code": 1,
"msg": "Failed to cancel booking"
}
```
### 响应字段说明
| **参数名称** | **类型** | **示例值** | **说明** |
| --- | --- | --- | --- |
| code | Integer | 0 | 系统状态码<br>**0**:取消成功<br>**1**:取消失败 |
| msg | String | "Booking cancelled successfully" | 系统消息:成功或失败的详细信息 |
| historyId | Long | 789 | 历史记录 ID用于追踪取消操作记录 |
## 业务流程
### 订单取消流程
```
1. 客户下单booking/hold→ 获取 orderId
2. 订单状态变化
├─ 未支付超时 → 系统自动取消
├─ 客户主动取消 → 调用取消接口
└─ 行程变更取消 → 调用取消接口
3. 调用取消接口booking/cancel→ 提供取消原因
4a. 成功code=0订单状态更新为 CANCELLED
4b. 失败code=1返回错误信息
```
### 取消后状态变化
```
取消前:
- 状态PENDING / CONFIRMED
- PNR已生成如已确认
取消后:
- 状态CANCELLED
- PNR被航司释放
- 历史记录:保存取消操作记录
```
## 常见问题
### 取消订单后可以恢复吗?
通常**不可以**。订单一旦取消:
1. PNR 被释放,座位被释放回库存
2. 如果需要恢复,需要重新下单
3. 某些航司可能支持恢复,但需要联系客服处理
### 哪些订单可以取消?
可以取消的订单:
1. **状态为 PENDING**:处理中的订单
2. **状态为 CONFIRMED**:已确认但未起飞的订单
3. **符合退改规则**:在退改规则允许的时间范围内
不可取消的订单:
1. **已起飞**:航班已经起飞
2. **已取消**:订单已经被取消过
3. **已使用**:乘客已办理登机
### cancelReason 有什么用?
cancelReason 用于:
1. **审计追踪**:记录取消原因,便于后续分析
2. **统计分析**:统计取消原因分布,优化产品
3. **客服支持**:客服可以查看取消原因
4. **退款处理**:根据取消原因判断是否退款及退款金额
### 取消成功后如何退款?
取消成功后:
1. 系统会自动触发退款流程
2. 退款金额根据**退改规则**和**取消时间**决定
3. 退款通常需要 3-7 个工作日到账
4. 可以通过 **booking/query** 查询退款状态
### 重复取消会怎样?
如果订单已经是 CANCELLED 状态:
1. 再次调用取消接口会返回失败code=1
2. 不会重复处理退款
3. 建议取消前先通过 **booking/query** 查询订单状态
## 业务案例
### 客户主动取消订单
#### 请求Request
```json
{
"orderId": "abbc4b57fb75",
"cancelReason": "Customer request - flight change"
}
```
#### 响应Response
```json
{
"code": 0,
"msg": "Booking cancelled successfully",
"historyId": 789
}
```
### 行程变更取消订单
#### 请求Request
```json
{
"orderId": "abbc4b57fb75",
"cancelReason": "Itinerary change - customer requested new dates"
}
```
#### 响应Response
```json
{
"code": 0,
"msg": "Booking cancelled successfully",
"historyId": 790
}
```
### 重复订单取消
#### 请求Request
```json
{
"orderId": "abbc4b57fb75",
"cancelReason": "Duplicate booking - same itinerary booked twice"
}
```
#### 响应Response
```json
{
"code": 0,
"msg": "Booking cancelled successfully",
"historyId": 791
}
```
### 取消不存在的订单
#### 请求Request
```json
{
"orderId": "nonexistent-order-id",
"cancelReason": "Test cancellation"
}
```
#### 响应Response
```json
{
"code": 1,
"msg": "Failed to cancel booking"
}
```
## 取消原因建议
### 标准取消原因
| 场景 | 建议的 cancelReason | 说明 |
| --- | --- | --- |
| 客户主动取消 | "Customer request" | 客户主动要求取消 |
| 行程变更 | "Flight change" | 客户需要更改行程 |
| 重复预订 | "Duplicate booking" | 同一航班重复预订 |
| 支付超时 | "Payment timeout" | 支付时间超过限制 |
| 价格问题 | "Price issue" | 客户认为价格不合理 |
| 个人原因 | "Personal reason" | 客户个人原因 |
| 系统错误 | "System error" | 系统导致的错误 |
| 其他原因 | "Other reason" | 其他未列出的原因 |
### 详细取消原因
建议在标准原因后添加详细说明:
```json
{
"orderId": "abbc4b57fb75",
"cancelReason": "Customer request - found better price elsewhere"
}
```
## 取消最佳实践
### 1. 取消前先查询状态
```javascript
// 1. 先查询订单状态
const order = await bookingQuery({ orderId: "xxx" });
// 2. 确认订单可以取消后再取消
if (order.status === "CONFIRMED" || order.status === "PENDING") {
const result = await bookingCancel({
orderId: "xxx",
cancelReason: "Customer request"
});
if (result.code === 0) {
console.log("取消成功");
}
}
```
### 2. 记录取消原因
始终提供清晰、准确的取消原因:
```json
{
"cancelReason": "Customer request - passenger name change needed"
}
```
而不是:
```json
{
"cancelReason": "Cancel"
}
```
### 3. 处理取消失败
```javascript
const result = await bookingCancel({
orderId: "xxx",
cancelReason: "Customer request"
});
if (result.code !== 0) {
// 记录错误日志
console.error("取消失败:", result.msg);
// 联系客服处理
await contactSupport({
orderId: "xxx",
issue: "Cancel failed",
message: result.msg
});
}
```
## 错误处理
### 常见错误及处理方式
| 错误场景 | code | msg | 处理建议 |
| --- | --- | --- | --- |
| 订单不存在 | 1 | "Failed to cancel booking" | 确认 orderId 是否正确 |
| 订单已取消 | 1 | "Failed to cancel booking" | 先查询订单状态 |
| 订单不可取消 | 1 | "Failed to cancel booking" | 检查退改规则和航班状态 |
| 系统错误 | 1 | "Failed to cancel booking" | 稍后重试或联系技术支持 |

View File

@@ -1,342 +0,0 @@
<!-- mipuyun-api-doc: booking-retry -->
# booking/retry - 订单重试
# 订单重试(booking/retry) API 说明文档
## 使用场景
> 1. 当订单创建失败时,可以通过本接口重试订单
> 2. 用于自动或手动恢复失败的订单
> 3. 支持指定重试次数,避免无限重试
## 错误场景(不应该使用本接口的场景)
> 1. 订单已成功时status=CONFIRMED不需要重试
> 1. 订单已取消时不能重试
> 1. 重试次数已达上限时继续重试
## 性能指标
> - 响应速度98% 的请求响应速度 < 3000ms包含重试处理时间
> - 接口成功率:> 95%
## 请求说明
| **请求地址** | https://${endpoint}/booking/retry |
| --- | --- |
| **请求方法** | POST |
## 请求参数
### Header
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝。 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret |
###
### 请求体
```json
{
"orderId": "abbc4b57fb75",
"retryCount": 1
}
```
#### 参数详情
| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** |
| --- | --- | --- | --- | --- |
| orderId | String | **是** | abbc4b57fb75 | 客户的订单号,用于重试失败的订单 |
| retryCount | Integer | 否 | 1 | 重试次数,用于追踪当前是第几次重试<br>**不提供**:系统将自动重试<br>**提供**:使用指定的重试次数 |
## 返回参数
### 成功响应
```json
{
"id": 12345,
"orderId": "abbc4b57fb75",
"journeyType": "OW",
"origin": "MIL",
"destination": "CAG",
"departureDate": "20251118",
"returnDate": null,
"adults": 1,
"children": 0,
"infants": 0,
"status": "PENDING",
"pnr": null,
"pnrExpiryTime": null,
"agent": "u2web",
"outboundFareFamily": "promo",
"inboundFareFamily": null,
"totalPrice": 150.50,
"currency": "EUR",
"createTime": "20241115100000",
"updateTime": "20241115101500",
"passengers": [
{
"firstName": "first",
"lastName": "last",
"passengerType": "ADT"
}
],
"contactInfo": {
"firstName": "san",
"lastName": "zhang",
"email": "san.zhang@gmail.com",
"phone": "0086-18912345678"
}
}
```
### 失败响应
#### 订单不存在
```json
null
```
### 响应字段说明
| **参数名称** | **类型** | **示例值** | **说明** |
| --- | --- | --- | --- |
| id | Long | 12345 | 系统内部订单 ID仅用于调试 |
| orderId | String | "abbc4b57fb75" | 客户的订单号 |
| journeyType | String | "OW" | 行程类型<br>**OW**:单程<br>**RT**:往返 |
| origin | String | "MIL" | 出发地 IATA 3 字码 |
| destination | String | "CAG" | 到达地 IATA 3 字码 |
| departureDate | String | "20251118" | 出发日期,格式 YYYYMMDD |
| returnDate | String/null | null | 返程日期,格式 YYYYMMDD仅往返程有值 |
| adults | Integer | 1 | 成人数量 |
| children | Integer | 0 | 儿童数量 |
| infants | Integer | 0 | 婴儿数量 |
| status | String | "PENDING" | 订单状态<br>**PENDING**:处理中(重试中)<br>**CONFIRMED**:已确认<br>**FAILED**:失败 |
| pnr | String/null | null | 航司 PNR 码(订座记录编号),重试中可能为空 |
| pnrExpiryTime | String/null | null | PNR 有效期,格式 YYYYMMDDHHmmssUTC 0 时区) |
| agent | String | "u2web" | Agent 代码 |
| outboundFareFamily | String | "promo" | 去程票价族 |
| inboundFareFamily | String/null | null | 回程票价族(仅往返程有值) |
| totalPrice | BigDecimal | 150.50 | 订单总价 |
| currency | String | "EUR" | 币种代码 |
| createTime | String | "20241115100000" | 订单创建时间,格式 YYYYMMDDHHmmss |
| updateTime | String | "20241115101500" | 订单最后更新时间,格式 YYYYMMDDHHmmss |
| passengers | Array<PassengerInfo> | | 乘客信息列表 |
| contactInfo | Object | | 联系人信息 |
## 业务流程
### 订单重试流程
```
1. 客户下单booking/hold→ 订单创建失败
2. 系统或客户检测到失败状态
3. 调用重试接口booking/retry→ 指定 retryCount
4. 系统重新处理订单
5a. 成功status=CONFIRMED返回订单详情
5b. 失败status=FAILED可以再次重试
```
### 自动重试策略
建议的自动重试策略:
```
第 1 次重试retryCount=1立即重试
第 2 次重试retryCount=2等待 30 秒
第 3 次重试retryCount=3等待 60 秒
第 4 次重试retryCount=4等待 120 秒
超过 4 次:停止重试,联系客服
```
## 常见问题
### 什么时候应该使用重试接口?
当订单状态为 **FAILED** 时,可以考虑使用重试接口:
1. 航司系统临时不可用
2. 网络超时
3. 航司返回临时错误
### retryCount 的作用是什么?
retryCount 用于:
1. **追踪重试次数**:记录当前是第几次重试
2. **防止无限重试**:可以设置最大重试次数上限
3. **系统监控**:用于监控重试频率和成功率
### 如果订单不存在会返回什么?
接口会返回 `null`。这种情况通常由以下原因造成:
1. orderId 错误或不存在
2. 订单属于其他客户clientCode 不匹配)
### 重试后订单状态仍然是 FAILED 怎么办?
如果多次重试后仍然失败:
1. 检查错误信息和日志
2. 确认订单参数是否正确
3. 检查航司系统是否正常
4. 联系技术支持
### 可以重试已确认的订单吗?
不建议。如果订单状态已经是 **CONFIRMED**,重试没有意义。建议先调用 **booking/query** 查询订单状态,确认订单确实失败后再重试。
## 业务案例
### 第 1 次重试失败的订单
#### 请求Request
```json
{
"orderId": "abbc4b57fb75",
"retryCount": 1
}
```
#### 响应Response
```json
{
"id": 12345,
"orderId": "abbc4b57fb75",
"journeyType": "OW",
"origin": "MIL",
"destination": "CAG",
"departureDate": "20251118",
"adults": 1,
"status": "PENDING",
"pnr": null,
"totalPrice": 150.50,
"currency": "EUR",
"createTime": "20241115100000",
"updateTime": "20241115101500"
}
```
### 第 2 次重试(等待后)
#### 请求Request
```json
{
"orderId": "abbc4b57fb75",
"retryCount": 2
}
```
#### 响应Response
```json
{
"id": 12345,
"orderId": "abbc4b57fb75",
"status": "CONFIRMED",
"pnr": "ABC123",
"pnrExpiryTime": "20251225120000",
"updateTime": "20241115102000"
}
```
### 重试不存在的订单
#### 请求Request
```json
{
"orderId": "nonexistent-order-id",
"retryCount": 1
}
```
#### 响应Response
```json
null
```
### 不指定 retryCount 的重试
#### 请求Request
```json
{
"orderId": "abbc4b57fb75"
}
```
#### 响应Response
```json
{
"orderId": "abbc4b57fb75",
"status": "PENDING",
"updateTime": "20241115103000"
}
```
**说明**:不提供 retryCount 时,系统将自动管理重试次数。
## 重试最佳实践
### 1. 指数退避策略
```json
{
"retryCount": 1,
"waitTime": 0
}
```
→ 失败后立即重试
```json
{
"retryCount": 2,
"waitTime": 30
}
```
→ 失败后等待 30 秒
```json
{
"retryCount": 3,
"waitTime": 60
}
```
→ 失败后等待 60 秒
### 2. 最大重试次数限制
建议设置最大重试次数为 **3-5 次**,超过后:
- 停止自动重试
- 通知客服介入
- 记录错误日志
### 3. 重试前先查询状态
```javascript
// 1. 先查询订单状态
const order = await bookingQuery({ orderId: "xxx" });
// 2. 确认订单失败后再重试
if (order.status === "FAILED") {
await bookingRetry({
orderId: "xxx",
retryCount: 1
});
}
```

View File

@@ -1,23 +0,0 @@
<!-- mipuyun-api-doc: flight-segment-request -->
# FlightSegmentRequest
# FlightSegmentRequest对象定义
Segment 对象用于描述单段航班的核心信息(告知我们定位到具体的行程),定义如下:
| **字段名** | **数据类型** | **示例值** | **说明** |
| --- | --- | --- | --- |
| carrier | String | "VJ" | 承运航司 IATA 二字码(如 VJ = 越南航空),**与 flightNumber 前缀的航司码一致**(示例中 flightNumber="VJ884"carrier="VJ" 符合规则) |
| flightNumber | String | "VJ884" | 航班号,格式为 "航司二字码 + 数字" |
| originAirport | String | "BOM" / "SGN" | 出发机场 IATA 三字码(如 BOM = 孟买贾特拉帕蒂・希瓦吉国际机场SGN = 胡志明市新山一国际机场) |
| destinationAirport | String | "SGN" / "SYD" | 到达机场 IATA 三字码(如 SYD = 悉尼金斯福德・史密斯机场) |
| stopCities | String | "CDG/MAD" | 经停地城市三字码,多个城市用 `/` 分隔(可选字段,用于支持包含经停点的航班查询) |
| departureDate | String | "20251027" | 起飞日期日期,固定格式 `YYYYMMDD`20251027表示 2025 年 10 月 27 日) |
# 备注
1. **请求体内的旅行时间是departureDate不同于响应体的departureTime**:原因是航司会出现出发时间的调整,为了保障成功率,请求只需要告知日期即可。
1. 本实体兼容FlightSegmentResponse, 你可以把shopping/search中获得实体直接引用。
1. 如入参为departureTime我们会抛弃时间只采用日期。
1. **stopCities 为可选字段**,仅在航班包含经停点时需要填写。格式为多个城市三字码用 `/` 分隔,例如 "CDG/MAD" 表示经停巴黎和马德里。

View File

@@ -1,24 +0,0 @@
<!-- mipuyun-api-doc: passenger -->
# Passenger
# Passenger 实体定义
Passenger 实体用于描述航班预订中单个乘客的核心信息,包括身份信息、证件信息、常旅客信息及附加服务选择,是订单创建、值机办理等流程的关键数据依据。
| **字段名** | **数据类型** | **示例值** | **说明** |
| --- | --- | --- | --- |
| firstName | String | San | 乘客名,需与证件名完全一致,国际航司不可填入中文 |
| lastName | String | Zhang | 乘客姓,需与证件姓完全一致,国际航司不可填入中文 |
| passengerType | String | "ADT"、"CHD"、"INF" | 乘客类型,固定枚举值,用于区分票价规则与服务权益:- "ADT" = 成人Adult通常指 12 周岁及以上乘客)- "CHD" = 儿童Child通常指 2-11 周岁乘客,需单独购票,部分航司可享折扣)- "INF" = 婴儿Infant通常指 2 周岁以下乘客,需与成人乘客绑定预订,票价规则特殊) |
| dateOfBirth | String | "20251017" | 乘客出生日期,固定格式为 **YYYYMMDD**(如 "20180520" 表示 2018 年 5 月 20 日) |
| gender | String | "M"、"F" | 乘客性别,固定枚举值:- "M" = 男Male- "F" = 女Female |
| documentNumber | String | "E12345678" | 证件号码需与证件类型documentType匹配 |
| documentType | String | "PP"、"ID" | 证件类型,固定枚举值,需与航司支持的证件类型一致:- "PP" = 护照Passport国际航班必填- "ID" = 身份证Identity Card |
| documentIssuePlace | String | "CN"、"US" | 证件签发国家 / 地区代码,遵循 **ISO 3166-1 alpha-2** 标准2 位字母编码):- "CN" = 中国内地- "US" = 美国 |
| documentExpirationDate | String | "20281030" | 证件有效期截止日期,固定格式为 **YYYYMMDD**(如 "20301231" 表示 2030 年 12 月 31 日),需满足 “有效期截止日期> 航班返程日期”(单程航班需满足 “> 出发日期”),否则会导致预订失败 |
| nationality | String | "CN"、"US" | 乘客国籍代码,遵循 **ISO 3166-1 alpha-2** 标准,与乘客护照 / 身份证上的国籍信息一致,用于航司核验出入境资格,不可为空 |
| mobile | String | 0086-13200112233 | 乘客手机号码注意格式采用xxxx-xxxxxxxxxxx前面4位为国家码。 |
| frequentFlyerCarrier | String | "CA"、"MU" | 常旅客所属航司代码,遵循 IATA 二字码标准,需与 ffpCardNo 对应的航司一致:- "CA" = 中国国际航空- "MU" = 中国东方航空无会员卡时填写 null非必选 |
| frequentFlyerNumber | String | "CA1234567890" | 常旅客卡号Frequent Flyer Program Card Number即乘客在某航司的会员卡编号如国航 “凤凰知音” 卡号 "CA1234567890"),无会员卡时填写 null非必选 |
| ancillaries | Array | Ancillary | 复用shopping/baggage中的行李响应字段详见 [Ancillary 实体定义](07-04_ancillary.md) |

View File

@@ -1,37 +0,0 @@
<!-- mipuyun-api-doc: contact-info -->
# ContanctInfo
# ContactInfo 实体定义
Contact 实体用于描述航班订单的联系人信息,是航司与用户沟通的核心渠道,用于发送订单确认通知、电子行程单、航班变动提醒等关键信息。
```objective-c
"contactInfo": {
"firstName": "san",
"lastName": "zhang",
"address": "dfdsaqqq",
"phoneCountryCode": "0086",
"phone": "18912345678",
"email": "san.zhang@gmail.com",
"postCode": "310006",
"city": "hangzhou",
"province": "zejiang",
"country": "CN"
```
| **字段名** | **数据类型** | 是否必选 | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| firstName | String | 是 | San | 乘客名,需与证件名完全一致,国际航司不可填入中文 |
| lastName | String | 是 | Zhang | 乘客姓,需与证件姓完全一致,国际航司不可填入中文 |
| address | String | 否 | No. 88 Jianguo Road, Chaoyang District, Beijing | 联系人详细地址,用英语(字母)填写。是否必选取决于航司是否需要这个信息。 |
| phoneCountryCode | String | 是 | 0086 | 国家码 |
| phone | String | 是 | 18912345678 | 联系电话号码 |
| postcode | String | 否 | "100022" | 联系人地址对应的邮编(邮政编码)是否必选取决于航司是否需要这个信息。 |
| email | String | 是 | "name@emaildomain.com" | 联系人邮箱地址,需符合标准邮箱格式(如 "用户名 @域名"用于接收电子行程单ETicket、订单确认邮件、航班变动通知是订单关键凭证的主要发送渠道不可为空且需确保邮箱可正常接收邮件 |
| city | String | 否 | hangzhou | 联系地址城市 |
| province | String | 否 | zhejiang | 联系地址省/州 |
| country | String | 否 | CN | 联系地址国家码 |

View File

@@ -1,174 +0,0 @@
<!-- mipuyun-api-doc: ancillary -->
# Ancillary
# Ancillary 实体定义
## 重要约定
| **字段名** | **数据类型** | **示例值** | **说明** |
|------------------| --- | --- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| journeyDirection | String | outbound | **行程方向**- outbound: 去程- inbound: 回程<br>**用途**:区分往返行程的去程和回程方向 |
| categoryCode | String | StandardCheckedBaggage | 值举例:<BR> - 托运行李StandardCheckedBaggage <BR> - 随身行李CabinBaggageOverheadLocker <BR> - 选座产品Seat |
| productCode | String | SCI_BAG_20KG | 值举例:<BR> - 托运行李SCI_BAG_20KG <BR> - 随身行李CBOL_BAG_15KG <BR> - 选座产品SEAT_16A |
| price | Number | 21.22 | 预计采购价格,选填 |
| currency | String | USD | 预计采购价对应的货币,选填 |
| baggage | Object | {"piece":0,"weight":20,"size":"","allWeight":true} | 行李附加信息:仅当行李类产品时非空,描述行李的重量、件数等属性 |
| ├─ piece | Integer | 1 | 行李最多件数1最多限一件2最多限两件 |
| ├─ weight | Integer | 20 | 行李总重量限制单位为公斤KG允许的行李总总量。20表示为20KG |
| ├─ size | String | "" | 行李尺寸限制:格式为 "长*宽*高"(如 "56*36***23cm"),空字符串表示无特殊尺寸限制(需遵循航司通用规则) |
| seat | Object | {"segmentIndex":1,"row":"12","column":"A"} | 座位附加信息:仅当`categoryCode=Seat`(座位类产品)时非空,描述座位的位置信息 |
| ├─ segmentIndex | Integer | 1 | **航段序号**:从 1 开始,在整个行程中连续编号<br>• 单程OW去程从 1 开始<br>• 往返RT去程从 1 开始,**回程 = 去程航段数 + 1**<br>**示例**<br>- 单程直飞segmentIndex=1<br>- 单程2段转机segmentIndex=1, 2<br>- 往返直飞:去程 segmentIndex=1回程 segmentIndex=2<br>- 往返2段转机去程 segmentIndex=1,2回程 segmentIndex=3,4 |
| ├─ row | String | "12" | 座位行号:标识座位所在的行(如 "12" 表示第12排 |
| ├─ column | String | "A" | 座位列号:标识座位所在的列(如 "A"、"B"、"C" 等) |
# 二、行李与选座的行程处理规则
## 1. segmentIndex 的计算规则
**重要**seat 对象中的 `segmentIndex` 在整个行程中**连续编号**,不是每个方向重新从 1 开始。
**计算规则:**
- **单程OW**:去程从 1 开始连续编号
- 直飞segmentIndex = 1
- 2段转机segmentIndex = 1, 2
- 3段转机segmentIndex = 1, 2, 3
- **往返RT**:去程从 1 开始,回程紧接去程的编号
- 往返直飞:去程 segmentIndex = 1回程 segmentIndex = 2
- 去程2段+回程1段去程 segmentIndex = 1, 2回程 segmentIndex = 3
- 去程2段+回程2段去程 segmentIndex = 1, 2回程 segmentIndex = 3, 4
**计算公式:**
- 去程第 N 段segmentIndex = N
- 回程第 M 段segmentIndex = 去程航段数 + M
## 2. 往返行程的区分
往返行程通过 `journeyDirection` 字段来区分:
- **outbound**:去程
- **inbound**:回程
对于选座产品(`categoryCode=Seat`),每个座位通过 `segmentIndex` 标识其对应的航段。
## 3. 行李产品的特殊规则
### 转机行程的行李规格要求
在单向的转机行程中(即同一个 `journeyDirection` 下有多个航段),行李规格(重量、件数)**必须保持一致**。
### 规则说明
- 如果去程有3个航段BOM→SGN→SYD→MEL整个去程购买的行李规格必须相同
- 例如不能第1段购买20KG第2段购买25KG
- 必须所有航段都购买20KG或都购买25KG
### 为什么需要相同规格?
- 转机时行李会自动转运到下一航段,不需要重新托运
- 航司系统要求整个行程使用相同的行李规格,以确保行李正常转运
### 往返行程的行李
- 去程和回程(不同的 `journeyDirection`)可以购买不同规格的行李
- 例如去程购买20KG回程购买25KG是允许的
## 4. 选座产品的特殊规则
### 每个航段独立选座
与行李不同,选座是**每个航段独立进行的**。
### 规则说明
- 每个航段都需要单独选择座位(通过 seat 对象中的 segmentIndex 区分)
- 即使是转机行程,不同航段可以选择不同的座位
- 例如第1段选 12A第2段选 15F
### 为什么需要独立选座?
- 每个航段的航班座位图不同
- 机型、座位布局可能不同
- 乘客可能希望在不同航段选择不同位置(如去程靠窗,回程靠过道)
### 选座对象中的字段
- `segmentIndex`标识选座对应的航段seat 专用字段)
- `row`:座位行号(如 "12"
- `column`:座位列号(如 "A"、"F"
**示例 - 转机行程选座:**
往返直飞去程1段+回程1段
```json
// 去程座位
{
"categoryCode": "Seat",
"productCode": "SEAT_12A",
"journeyDirection": "outbound",
"seat": {
"segmentIndex": 1,
"row": "12",
"column": "A"
}
}
// 回程座位
{
"categoryCode": "Seat",
"productCode": "SEAT_8F",
"journeyDirection": "inbound",
"seat": {
"segmentIndex": 2,
"row": "8",
"column": "F"
}
}
```
往返转机去程2段+回程2段
```json
// 去程第1段座位
{
"categoryCode": "Seat",
"productCode": "SEAT_12A",
"journeyDirection": "outbound",
"seat": {
"segmentIndex": 1,
"row": "12",
"column": "A"
}
}
// 去程第2段座位
{
"categoryCode": "Seat",
"productCode": "SEAT_15F",
"journeyDirection": "outbound",
"seat": {
"segmentIndex": 2,
"row": "15",
"column": "F"
}
}
// 回程第1段座位segmentIndex = 去程航段数 + 1 = 3
{
"categoryCode": "Seat",
"productCode": "SEAT_8A",
"journeyDirection": "inbound",
"seat": {
"segmentIndex": 3,
"row": "8",
"column": "A"
}
}
// 回程第2段座位segmentIndex = 去程航段数 + 2 = 4
{
"categoryCode": "Seat",
"productCode": "SEAT_10F",
"journeyDirection": "inbound",
"seat": {
"segmentIndex": 4,
"row": "10",
"column": "F"
}
}
```

View File

@@ -1,76 +0,0 @@
<!-- mipuyun-api-doc: itinerary -->
# Itinerary - 行程实体对象
Itinerary 实体(行程组合信息,与实际响应字段一致)
| **参数名称** | **类型** | **说明** |
| --- | --- | --- |
| outboundSegments | Array<[SegmentElement](08-03_segment-element.md)> | 去程航段列表单程OW和往返RT场景均返回按行程顺序排列 |
| inboundSegments | Array<[SegmentElement](08-03_segment-element.md)> | 返程航段列表仅往返RT场景返回字段含义同 outboundSegments单程OW场景为空数组 |
| fares | Array<[FlightFare](08-02_flight-fare.md)> | 该行程下的可选票价方案列表,每个方案包含去程/回程的票价、税费、座位数、舱位类型及航班政策信息 |
## 实体轮廓
```json
{
"outboundSegments": [
{
"index": 1,
"carrier": "VJ",
"flightNumber": "VJ884",
"operatingCarrier": "",
"operatingFlightnumber": "",
"originAirport": "BOM",
"destinationAirport": "SGN",
"departureTime": "202510270040",
"arrivalTime": "202510270705",
"departureTerminal": "",
"arrivalTerminal": "",
"stopCities": "",
"duration": 295,
"aircraftCode": "330",
"codeShare": false
}
],
"inboundSegments": [
{
// 同 outboundSegments 结构
}
],
"fares": [
{
"fareBasis": "",
"rtnFareBasis": "",
"fareFamily": "Lion Economy",
"rtnFareFamily": "Lion Promo",
"currency": "USD",
"bookingCode": "Y,Y",
"rtnBookingCode": "Y,Y",
"availableSeats": 7,
"rtnAvailableSeats": 2,
"flightPolicy": {
"airlineCode": "SL",
"fareFamilyType": "Lion Economy",
"description": "",
"freeAncillaryList": [
{
"categoryCode": "CabinBaggageOverheadLocker",
"segmentIndex": 1,
"paxType": "ADT",
"piece": 1,
"weight": 7,
"size": ""
}
],
"refundRules": [], // 预留字段,暂未提供
"changeRules": [] // 预留字段,暂未提供
},
"rtnFlightPolicy": {
// 同 flightPolicy 结构
}
}
]
}
```
> `RuleElement` 结构详见 [FlightPolicy](08-04_flight-policy.md)

View File

@@ -1,32 +0,0 @@
<!-- mipuyun-api-doc: flight-fare -->
# FlightFare
FlightFare 实体用于描述不同乘客类型(成人 / 儿童 / 婴儿)的费用构成,拆分基础票价与税费等明细项,同时包含去程和返程的价格、政策信息,字段与实际响应完全对应。
> **说明**:带 `rtn` 前缀的字段为回程字段,仅往返程有值。
| **参数名称** | **类型** | **示例值** | **说明** |
| --- | --- | --- | --- |
| fareBasis | String | | 去 fareBasis 信息(如有) |
| rtnFareBasis | String | | 回程 fareBasis 信息(如有) |
| fareFamily | String | "Lion Economy" | 去程票价舱/产品类型名称 |
| rtnFareFamily | String | "Lion Promo" | 回程票价舱/产品类型名称 |
| currency | String | "USD" | 币种编码,遵循 ISO 4217 标准(如 USD = 美元CNY = 人民币) |
| adultFare | BigDecimal | 6115.00 | 去程成人票价(不含税) |
| rtnAdultFare | BigDecimal | 6100.00 | 回程成人票价(不含税) |
| adultTax | BigDecimal | 13714.00 | 去程成人税费 |
| rtnAdultTax | BigDecimal | 8540.00 | 回程成人税费 |
| childFare | BigDecimal | 0.00 | 去程儿童票价(不含税),儿童定义为 2-12 周岁 |
| rtnChildFare | BigDecimal | 0.00 | 回程儿童票价(不含税) |
| childTax | BigDecimal | 0.00 | 去程儿童税费 |
| rtnChildTax | BigDecimal | 0.00 | 回程儿童税费 |
| infantFare | BigDecimal | 0.00 | 去程婴儿票价(不含税),婴儿定义为 0-2 周岁(无独立座位) |
| rtnInfantFare | BigDecimal | 0.00 | 回程婴儿票价(不含税) |
| infantTax | BigDecimal | 0.00 | 去程婴儿税费 |
| rtnInfantTax | BigDecimal | 0.00 | 回程婴儿税费 |
| bookingCode | String | "Y,Y" | 去程舱位代码(如 Y、B、M、W 等),廉航领域不一定存在,可通过系统设置-参数设置-默认舱位编码指定默认值。<br>**格式说明**<br>- 单程直飞:单个代码,如 `"Y"`<br>- 单程转机:逗号分隔多段,如 `"B,Y"` 表示第一段 B 舱,第二段 Y 舱 |
| rtnBookingCode | String | "Y,Y" | 回程舱位代码(如 Y、B、M、W 等),廉航领域不一定存在。<br>**格式说明**:同 bookingCode单段为单个代码多段用逗号分隔 |
| availableSeats | int | 7 | 去程剩余座位数 |
| rtnAvailableSeats | int | 2 | 回程剩余座位数 |
| flightPolicy | [FlightPolicy](08-04_flight-policy.md) | | 去程航班政策(包含去程免费行李额、退改规则等) |
| rtnFlightPolicy | [FlightPolicy](08-04_flight-policy.md) | | 回程航班政策(包含回程免费行李额、退改规则等) |

View File

@@ -1,30 +0,0 @@
<!-- mipuyun-api-doc: segment-element -->
# FlightSegmentResponse
# FlightSegment 对象定义
Segment 对象用于描述单段航班的核心信息(如在搜索结果中的去程航段 `outboundSegments`、返程航段 `inboundSegments`),具体定义如下:
| **字段名** | **数据类型** | **示例值** | **说明** |
| --- | --- | --- | --- |
| index | Integer | 1 / 2 | 航段序号,按行程顺序递增(一般用于转机行程,第一段去程为 1第二段去程为 2非空且唯一 |
| carrier | String | "VJ" | 承运航司 IATA 二字码(如 VJ = 越南航空),**与 flightNumber 前缀的航司码一致**(示例中 flightNumber="VJ884"carrier="VJ" 符合规则) |
| flightNumber | String | "VJ884" | 航班号,格式为 “航司二字码 + 数字”: |
| operatingFlightnumber | String | "" | 实际承运航班号:- 代码共享航班时,填写实际承运航司的航班号- 非代码共享航班时,空字符串或与 flightNumber 一致(示例中为非代码共享,故为空) |
| operatingCarrier | String | "" | 实际承运航司 IATA 二字码:- 代码共享航班codeShare=true需填写实际承运航司如 carrier="CA"operatingCarrier="MU"- 非代码共享航班codeShare=false空字符串或与 carrier 一致(示例中为非代码共享,故为空) |
| originAirport | String | "BOM" / "SGN" | 出发机场 IATA 三字码(如 BOM = 孟买贾特拉帕蒂・希瓦吉国际机场SGN = 胡志明市新山一国际机场) |
| departureTime | String | "202510270040" | 起飞日期时间,固定格式 `YYYYMMDDHHMM`202510270040 表示 2025 年 10 月 27 日 00:40 |
| destinationAirport | String | "SGN" / "SYD" | 到达机场 IATA 三字码(如 SYD = 悉尼金斯福德・史密斯机场) |
| arrivalTime | String | "202510270705" | 到达日期时间,格式同 depTime202510280805 表示 2025 年 10 月 28 日 08:05 |
| stopCities | String | "" | 经停地,空字符串表示直飞 |
| duration | int | 295 / 515 | 飞行时长单位为分钟295 分钟 = 4 小时 55 分钟515 分钟 = 8 小时 35 分钟) |
| codeShare | boolean | false | 代码共享标识:- true = 代码共享航班(由其他航司实际承运)- false = 非代码共享航班(由 carrier 航司直接承运) |
| cabin | String | "" | 舱位等级代码(如 "Y"= 经济舱、"C"= 公务舱),空字符串表示未明确具体舱位代码 |
| aircraftCode | String | "330" | 机型编码(如 "330"= 空客 A330"738"= 波音 737-800用于标识执飞客机型号 |
| departureTerminal | String | "T1" | 起飞机场航站楼(如 "T2"=2 号航站楼),空字符串表示未明确航站楼信息(需以机场实时通知为准) |
| arrivalTerminal | String | "T1" | 到达机场航站楼,规则同 departureTerminal |
# 特殊业务规则备注
1. **航班号前 0 规则**响应体内的航班号会按照航司接口中获取数据原样返回请注意处理是否带0的情况。

View File

@@ -1,43 +0,0 @@
<!-- mipuyun-api-doc: flight-policy -->
# FlightPolicy
# FlightPolicy 实体定义(基于实际响应)
该实体用于描述航班的附加产品信息(如免费行李额、退改规则等),字段与最新响应数据完全对齐。
> **注意**:去程和回程的免费行李额分别存储在 `FlightFare.flightPolicy` 和 `FlightFare.rtnFlightPolicy` 中,因此 `freeBaggageList` 不再包含 `journeyDirection` 字段来区分方向。
| **字段名** | **数据类型** | **示例值** | **说明** |
| --- | --- | --- | --- |
| airlineCode | String | "SL" | 航司代码 |
| fareFamilyType | String | "Lion Economy" | 舱位/子舱位类型名称 |
| description | String | | 政策描述 |
| freeAncillaryList | List\<FreeAncillary\> | | 免费行李额列表(详见下方说明) |
| refundRules | List\<RefundRule\> | | 退票规则列表(预留字段,暂未提供) |
| changeRules | List\<ChangeRule\> | | 改签规则列表(预留字段,暂未提供) |
## freeAncillaryListFreeAncillary
免费行李额元素,描述每个航段、每种乘客类型的免费行李配额。
| **字段名** | **数据类型** | **示例值** | **说明** |
| --- | --- | --- | --- |
| categoryCode | String | "StandardCheckedBaggage" | 辅营类型代码:`StandardCheckedBaggage` = 托运行李,`CabinBaggageUnderSeat` = 客舱行李(座椅下),`CabinBaggageOverheadLocker` = 客舱行李(行李架) |
| segmentIndex | Integer | 1 | 航段序号,从 1 开始 |
| paxType | String | "ADT" | 乘客类型:`ADT` = 成人,`CHD` = 儿童,`INF` = 婴儿 |
| piece | Integer | 1 | 行李件数PC |
| weight | Integer | 20 | 行李重量KG |
| size | String | | 行李尺寸 |
**示例:**
```json
{
"freeAncillaryList": [
{ "categoryCode": "CabinBaggageOverheadLocker", "segmentIndex": 1, "paxType": "ADT", "piece": 1, "weight": 7 },
{ "categoryCode": "StandardCheckedBaggage", "segmentIndex": 1, "paxType": "ADT", "piece": 1, "weight": 20 },
{ "categoryCode": "CabinBaggageOverheadLocker", "segmentIndex": 2, "paxType": "ADT", "piece": 1, "weight": 7 },
{ "categoryCode": "StandardCheckedBaggage", "segmentIndex": 2, "paxType": "ADT", "piece": 1, "weight": 20 }
]
}
```

View File

@@ -1,121 +0,0 @@
<!-- mipuyun-api-doc: ancillary-product -->
# AncillaryProduct辅营产品
## 数据结构总览
辅营产品采用三级嵌套结构:
```
AncillaryGroup分组层
└─ AncillaryCategory类别层
└─ BaggageOption选项层
```
---
## AncillaryGroup分组层
**航程方向 + 运价家族** 分组的最外层容器。
| 字段名 | 类型 | 示例值 | 说明 |
|--------|------|--------|------|
| currency | String | "CAD" | 售价币种ISO 4217 |
| fareFamilyType | String | "BASIC" | 运价家族类型,如 `BASIC``SMART``PROMO` |
| journeyDirection | String | "outbound" | 航程方向:`outbound`(去程)/ `inbound`(回程) |
| categoryList | Array\<AncillaryCategory\> | - | 辅营类别列表 |
---
## AncillaryCategory类别层
按行李类别分组,携带定价模式和购买限制。
| 字段名 | 类型 | 示例值 | 说明 |
|--------|------|--------|------|
| categoryCode | String | "StandardCheckedBaggage" | 类别代码,见下方 categoryCode 说明 |
| maxPieces | String | "5" | 该类别最多可购买件数 |
| pricingMode | String | "STEP" | 定价模式:`FLAT` / `STEP` / `TIERED`,见下方说明 |
| categoryDetail | Array\<BaggageOption\> | - | 该类别下的选项列表 |
### categoryCode 说明
| categoryCode | 说明 |
|--------------|------|
| CabinBaggageOverheadLocker | 手提行李(头顶行李架) |
| CabinBaggageUnderSeat | 手提行李(座椅下) |
| StandardCheckedBaggage | 托运行李 |
### pricingMode 说明
| pricingMode | 说明 |
|-------------|------|
| FLAT | 固定单价每件为独立产品pieceNo 固定为 1 |
| STEP | 逐件定价pieceNo 表示第 N 件,同 pieceNo 可有多个重量选项 |
| TIERED | 按总重量档位选择不计件pieceNo 固定为 1 |
---
## BaggageOption选项层
具体的行李规格和价格。
| 字段名 | 类型 | 示例值 | 说明 |
|--------|------|--------|------|
| pieceNo | Integer | 1 | 第 N 件。STEP 模式下为序号1, 2, 3...FLAT/TIERED 模式下为 1 |
| weight | BigDecimal | 23 | 行李额重量KG |
| size | String | "55x40x23cm" | 尺寸限制,空字符串表示无特殊限制 |
| price | BigDecimal | 64.00 | 该选项/该件价格 |
| maxPieces | Integer | 1 | 该选项可购买数量 |
---
## 响应示例
### STEP 模式示例
```json
{
"currency": "CAD",
"fareFamilyType": "BASIC",
"journeyDirection": "outbound",
"categoryList": [
{
"categoryCode": "StandardCheckedBaggage",
"maxPieces": "5",
"pricingMode": "STEP",
"categoryDetail": [
{ "pieceNo": 1, "weight": 10, "size": "", "price": 39.00, "maxPieces": 1 },
{ "pieceNo": 1, "weight": 23, "size": "", "price": 64.00, "maxPieces": 1 },
{ "pieceNo": 2, "weight": 23, "size": "", "price": 64.00, "maxPieces": 1 },
{ "pieceNo": 3, "weight": 23, "size": "", "price": 99.00, "maxPieces": 1 }
]
}
]
}
```
> **STEP 模式解读**:第 1 件可选 10kg$39或 23kg$64第 2 件 23kg$64第 3 件 23kg$99
### TIERED 模式示例
```json
{
"currency": "CNY",
"fareFamilyType": "ECONOMY",
"journeyDirection": "outbound",
"categoryList": [
{
"categoryCode": "StandardCheckedBaggage",
"maxPieces": "1",
"pricingMode": "TIERED",
"categoryDetail": [
{ "pieceNo": 1, "weight": 20, "size": "", "price": 300.00, "maxPieces": 1 },
{ "pieceNo": 1, "weight": 25, "size": "", "price": 500.00, "maxPieces": 1 },
{ "pieceNo": 1, "weight": 30, "size": "", "price": 800.00, "maxPieces": 1 }
]
}
]
}
```
> **TIERED 模式解读**按重量档位互斥选择20kg¥300、25kg¥500或 30kg¥800只能选一个。

View File

@@ -1,230 +0,0 @@
<!-- mipuyun-api-doc: fee-items -->
# feeItems 实体定义
该实体用于拆分航班预订费用,字段与最新响应数据完全对齐,同时补充业务含义说明:
### **字段结构**
```plaintext
feeItems
├── amount (总金额)
├── currency (货币代码)
├── fare (票价)
│ ├── amount (票价总金额)
│ ├── name (费用名称)
│ ├── remark (费用备注)
│ └── passengers (按乘机人拆分)
│ └── 每个乘客的票价明细
├── tax (税费)
│ ├── amount (税费总金额)
│ ├── name (费用名称)
│ ├── remark (费用备注)
│ ├── passengers (按乘机人拆分)
│ │ └── 每个乘客的税费明细
│ │ └── fees (税费细项)
│ │ └── 每个税费的明细
│ └── fees (税费总细项)
│ └── 每个税费的明细
├── cabinBaggage (手提行李)
│ ├── amount (手提行李总金额)
│ ├── name (费用名称)
│ ├── remark (费用备注)
│ └── passengers (按乘机人拆分)
│ └── 每个乘客的手提行李费用明细
├── checkedBaggage (托运行李)
│ ├── amount (托运行李总金额)
│ ├── name (费用名称)
│ ├── remark (费用备注)
│ └── passengers (按乘机人拆分)
│ └── 每个乘客的托运行李费用明细
├── seat (座位)
│ ├── amount (座位总金额)
│ ├── name (费用名称)
│ ├── remark (费用备注)
│ └── passengers (按乘机人拆分)
│ └── 每个乘客的座位费用明细
├── payment (支付手续费)
│ ├── amount (支付手续费总金额)
│ ├── name (费用名称)
│ └── remark (费用备注)
└── other (其他额外费用)
├── amount (其他额外费用总金额)
├── name (费用名称)
```
| 字段名 | 数据类型 | 示例值 | 说明 |
| --- | --- | --- | --- |
| totalPrice | Decimal | "850.00" | 总金额:航班预订的全部费用合计,单位与 currency 一致 |
| currency | String | "CNY" | 货币代码:标识费用的货币类型(如 CNY 为人民币、USD 为美元) |
| ticketPrice | Object | {"amount":"200.00","name":"票价",...} | 票价对象包含票价总金额、费用名称、备注及可选的乘机人拆分明细PassengerFee 数组) |
| tax | Object | {"amount":"250.00","name":"税",...} | 税费对象包含税费总金额、费用名称、备注可选乘机人拆分明细PassengerFee 数组和税费细项FeeDetail 数组) |
| cabinBaggage | Object | {"amount":"50.00","name":"手提行李",...} | 手提行李费用对象包含总金额、费用名称、备注及可选的乘机人拆分明细PassengerFee 数组),可选字段 |
| checkedBaggage | Object | {"amount":"150.00","name":"托运行李",...} | 托运行李费用对象包含总金额、费用名称、备注及可选的乘机人拆分明细PassengerFee 数组),可选字段 |
| seat | Object | {"amount":"100.00","name":"座位",...} | 座位费用对象包含总金额、费用名称、备注及可选的乘机人拆分明细PassengerFee 数组),可选字段 |
| payment | Object | {"amount":"50.00","name":"支付手续费",...} | 支付手续费对象:包含总金额、费用名称、备注,无乘机人拆分,可选字段 |
| other | Object | {"amount":"0.00","name":"其他费用",...} | 其他额外费用对象:包含总金额、费用名称、备注,无乘机人拆分,可选字段 |
## 嵌套实体定义
### PassengerFee乘机人费用明细
| 字段名 | 数据类型 | 示例值 | 说明 |
| --- | --- | --- | --- |
| amount | Decimal | "100.00" | 乘机人对应费用金额:单个乘客在该费用类型下的应付金额 |
| name | String | "willow/yang" | 乘机人姓名:对应乘客的姓名标识 |
| remark | String | "成人票价" | 备注:说明该乘客费用的相关信息(如乘客类型、费用用途) |
| fees | Array[FeeDetail] | [{"amount":"100.00","name":"机场税",...}] | 税费细项数组:仅税费对象的乘机人明细中可选,包含该乘客的具体税费拆分,可选字段 |
### FeeDetail费用细项明细
| 字段名 | 数据类型 | 示例值 | 说明 |
| --- | --- | --- | --- |
| amount | Decimal | "170.00" | 细项金额:单个费用细项的总金额或乘机人对应金额 |
| name | String | "机场税" | 细项名称:描述费用细项的类型(如机场税、燃油附加费) |
| remark | String | "机场税" | 备注:补充说明费用细项的相关信息,与名称语义一致 |
| code | String | - | 费用细项编码:唯一标识该费用细项类型,可选字段 |
### **响应示例**
```json
{
"success": true,
"feeItems": {
"totalPrice": "850.00",
"currency": "CNY",
"ticketPrice": {
"amount": "200.00",
"name": "票价",
"remark": "票价",
"passengers": [
{
"amount": "100.00",
"name": "willow/yang",
"remark": "成人票价"
},
{
"amount": "100.00",
"name": "jasen/yang",
"remark": "成人票价"
}
]
},
"tax": {
"amount": "250.00",
"name": "税",
"remark": "税费",
"passengers": [
{
"amount": "150.00",
"name": "willow/yang",
"remark": "成人税价",
"fees": [
{
"amount": "100.00",
"name": "机场税",
"remark": "机场税"
},
{
"amount": "50.00",
"name": "燃油附加费",
"remark": "燃油附加费"
}
]
},
{
"amount": "100.00",
"name": "jasen/yang",
"remark": "成人税价",
"fees": [
{
"amount": "70.00",
"name": "机场税",
"remark": "机场税"
},
{
"amount": "30.00",
"name": "燃油附加费",
"remark": "燃油附加费"
}
]
}
],
"fees": [
{
"amount": "170.00",
"name": "机场税",
"remark": "机场税"
},
{
"amount": "80.00",
"name": "燃油附加费",
"remark": "燃油附加费"
}
]
},
"cabinBaggage": {
"amount": "50.00",
"name": "手提行李",
"remark": "手提行李费",
"passengers": [
{
"amount": "25.00",
"name": "willow/yang",
"remark": "手提行李费"
},
{
"amount": "25.00",
"name": "jasen/yang",
"remark": "手提行李费"
}
]
},
"checkedBaggage": {
"amount": "150.00",
"name": "托运行李",
"remark": "托运行李费",
"passengers": [
{
"amount": "75.00",
"name": "willow/yang",
"remark": "托运行李费"
},
{
"amount": "75.00",
"name": "jasen/yang",
"remark": "托运行李费"
}
]
},
"seat": {
"amount": "100.00",
"name": "座位",
"remark": "选座费",
"passengers": [
{
"amount": "50.00",
"name": "willow/yang",
"remark": "选座费"
},
{
"amount": "50.00",
"name": "jasen/yang",
"remark": "选座费"
}
]
},
"payment": {
"amount": "50.00",
"name": "支付手续费",
"remark": "支付手续费"
},
"other": {
"amount": "0.00",
"name": "其他费用",
"remark": "其他费用"
}
}
}
```

View File

@@ -1,141 +0,0 @@
<!-- mipuyun-api-doc: device-list -->
# device/list - 设备列表
## 概述
设备列表API用于查询当前客户的所有设备信息包括设备状态、绑定的VCC卡、任务状态等。
### 用户使用场景
本接口用于:
- 查看所有可用设备及其在线状态
- 检查设备的VCC卡绑定情况
- 查看设备的任务处理状态
## 请求说明
| 项目 | 值 |
|------|-----|
| **请求地址** | `https://${endpoint}/device/list` |
| **请求方法** | GET |
| **注意事项** | 无需请求体从Header中获取clientCode |
### Header 参数
| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 |
|----------|------|----------|--------|------|
| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret |
## 返回参数
成功时返回设备信息列表:
```json
[
{
"deviceId": "device-001",
"status": "ONLINE",
"vccCardNumber": "424242******4242",
"bindingVccCardNumber": "555555******5555",
"lastHeartbeatTime": 1704140800000,
"taskStatus": "PROCESSING",
"processingTask": {
"taskId": "task-123",
"taskType": "BIND_VCC",
"status": "PROCESSING",
"createTime": "2025-01-15T10:30:00Z",
"updateTime": "2025-01-15T10:35:00Z",
"deviceId": "device-001"
},
"pendingTasks": []
},
{
"deviceId": "device-002",
"status": "OFFLINE",
"vccCardNumber": "378222******2222",
"bindingVccCardNumber": null,
"lastHeartbeatTime": 1704130000000,
"taskStatus": "IDLE",
"processingTask": null,
"pendingTasks": [
{
"taskId": "task-124",
"taskType": "UNBIND_VCC",
"status": "PENDING",
"createTime": "2025-01-15T11:00:00Z",
"updateTime": "2025-01-15T11:00:00Z",
"deviceId": "device-002"
}
]
}
]
```
| 参数名称 | 类型 | 说明 |
|----------|------|------|
| deviceId | String | 设备编号 |
| status | String | 设备状态ONLINE=在线OFFLINE=离线 |
| vccCardNumber | String | 当前绑定的VCC卡号脱敏显示 |
| bindingVccCardNumber | String | 正在绑定的VCC卡号脱敏显示绑定任务进行中时存在 |
| lastHeartbeatTime | Long | 最后心跳时间(时间戳毫秒) |
| taskStatus | String | 任务状态IDLE=空闲PROCESSING=处理中PENDING=有等待任务 |
| processingTask | Object | 正在执行的任务信息 |
| pendingTasks | Array | 等待队列中的任务列表 |
### TaskInfo 任务信息
| 参数名称 | 类型 | 说明 |
|----------|------|------|
| taskId | String | 任务ID |
| taskType | String | 任务类型BIND_VCC=绑定VCC卡UNBIND_VCC=解绑VCC卡PAYMENT=支付 |
| status | String | 任务状态PENDING=等待中PROCESSING=处理中COMPLETED=已完成FAILED=失败 |
| createTime | String | 创建时间ISO 8601格式 |
| updateTime | String | 更新时间ISO 8601格式 |
| deviceId | String | 设备ID |
## 业务案例
### 查询所有设备状态
### 请求
```bash
curl -X GET "https://${endpoint}/device/list" \
-H "client-key: your_key" \
-H "client-secret: your_secret"
```
### 响应
```json
[
{
"deviceId": "device-001",
"status": "ONLINE",
"vccCardNumber": "424242******4242",
"bindingVccCardNumber": null,
"lastHeartbeatTime": 1704140800000,
"taskStatus": "IDLE",
"processingTask": null,
"pendingTasks": []
}
]
```
## 常见问题
### 设备状态ONLINE和OFFLINE的区别是什么
- **ONLINE**:设备在线,可以接收和处理任务
- **OFFLINE**:设备离线,无法处理任务
### taskStatus有几种状态
- **IDLE**:设备空闲,没有正在处理的任务
- **PROCESSING**:设备正在执行任务
- **PENDING**:设备有待处理的任务队列
### VCC卡号为什么要脱敏显示
为了保护支付安全VCC卡号只显示前6位和后4位中间用*替换。

View File

@@ -1,143 +0,0 @@
<!-- mipuyun-api-doc: device-bind -->
# device/bind - VCC卡绑定
## 概述
VCC卡绑定API用于向设备提交VCC卡绑定请求。绑定过程是异步的提交后会返回任务ID可以通过任务查询接口查询绑定进度。
### 用户使用场景
本接口用于:
- 为在线设备绑定新的VCC卡
- 替换设备当前绑定的VCC卡
## 请求说明
| 项目 | 值 |
|------|-----|
| **请求地址** | `https://${endpoint}/device/bind` |
| **请求方法** | POST |
| **注意事项** | 绑定过程是异步的,需要轮询任务状态 |
### Header 参数
| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 |
|----------|------|----------|--------|------|
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret |
### 请求体参数
```json
{
"deviceId": "device-001",
"creditCard": {
"cardNumber": "4242424242424242",
"cvv": "123",
"expiryDate": "12/25"
}
}
```
| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 |
|----------|------|----------|--------|------|
| deviceId | String | 是 | "device-001" | 设备编号 |
| creditCard | Object | 是 | - | 信用卡信息 |
| creditCard.cardNumber | String | 是 | "4242424242424242" | 卡号 |
| creditCard.cvv | String | 是 | "123" | CVV码 |
| creditCard.expiryDate | String | 是 | "12/25" | 有效期格式MM/YY |
## 返回参数
成功时返回任务信息:
```json
{
"success": true,
"message": "VCC卡绑定任务已提交",
"taskId": "task-bind-123456"
}
```
| 参数名称 | 类型 | 说明 |
|----------|------|------|
| success | Boolean | 是否成功提交 |
| message | String | 结果消息 |
| taskId | String | 任务ID用于查询任务状态 |
失败时返回:
```json
{
"success": false,
"message": "设备离线,无法执行绑定任务",
"taskId": null
}
```
## 业务案例
### 绑定VCC卡
### 请求
```bash
curl -X POST "https://${endpoint}/device/bind" \
-H "Content-Type: application/json" \
-H "client-key: your_key" \
-H "client-secret: your_secret" \
-d '{
"deviceId": "device-001",
"creditCard": {
"cardNumber": "4242424242424242",
"cvv": "123",
"expiryDate": "12/25"
}
}'
```
### 响应
```json
{
"success": true,
"message": "VCC卡绑定任务已提交",
"taskId": "task-bind-123456"
}
```
### 查询绑定任务进度
使用返回的taskId查询任务状态
```bash
curl -X GET "https://${endpoint}/device/task/task-bind-123456" \
-H "client-key: your_key" \
-H "client-secret: your_secret"
```
## 常见问题
### 绑定是同步还是异步的?
绑定是**异步操作**。提交绑定请求后会立即返回任务ID需要通过任务查询接口轮询任务状态。
### 绑定失败的可能原因?
- 设备离线status=OFFLINE
- 设备正在执行其他任务taskStatus=PROCESSING
- 信用卡信息格式错误
- 信用卡已过期
### 如何判断绑定是否完成?
通过任务查询接口检查任务状态:
- **PENDING**:任务在队列中等待
- **PROCESSING**:正在绑定中
- **COMPLETED**:绑定完成
- **FAILED**:绑定失败
### 绑定完成后如何验证?
再次调用设备列表接口,查看设备的 `vccCardNumber` 字段是否已更新为新卡号。

View File

@@ -1,128 +0,0 @@
<!-- mipuyun-api-doc: device-unbind -->
# device/unbind - VCC卡解绑
## 概述
VCC卡解绑API用于向设备提交VCC卡解绑请求。解绑过程是异步的提交后会返回任务ID可以通过任务查询接口查询解绑进度。
### 用户使用场景
本接口用于:
- 移除设备当前绑定的VCC卡
- 更换VCC卡前先解绑旧卡
## 请求说明
| 项目 | 值 |
|------|-----|
| **请求地址** | `https://${endpoint}/device/unbind` |
| **请求方法** | POST |
| **注意事项** | 解绑过程是异步的,需要轮询任务状态 |
### Header 参数
| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 |
|----------|------|----------|--------|------|
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret |
### 请求体参数
```json
{
"deviceId": "device-001"
}
```
| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 |
|----------|------|----------|--------|------|
| deviceId | String | 是 | "device-001" | 设备编号 |
## 返回参数
成功时返回任务信息:
```json
{
"success": true,
"message": "VCC卡解绑任务已提交",
"taskId": "task-unbind-123456"
}
```
| 参数名称 | 类型 | 说明 |
|----------|------|------|
| success | Boolean | 是否成功提交 |
| message | String | 结果消息 |
| taskId | String | 任务ID用于查询任务状态 |
失败时返回:
```json
{
"success": false,
"message": "设备没有绑定的VCC卡",
"taskId": null
}
```
## 业务案例
### 解绑VCC卡
### 请求
```bash
curl -X POST "https://${endpoint}/device/unbind" \
-H "Content-Type: application/json" \
-H "client-key: your_key" \
-H "client-secret: your_secret" \
-d '{
"deviceId": "device-001"
}'
```
### 响应
```json
{
"success": true,
"message": "VCC卡解绑任务已提交",
"taskId": "task-unbind-123456"
}
```
### 查询解绑任务进度
使用返回的taskId查询任务状态
```bash
curl -X GET "https://${endpoint}/device/task/task-unbind-123456" \
-H "client-key: your_key" \
-H "client-secret: your_secret"
```
## 常见问题
### 解绑是同步还是异步的?
解绑是**异步操作**。提交解绑请求后会立即返回任务ID需要通过任务查询接口轮询任务状态。
### 解绑失败的可能原因?
- 设备离线status=OFFLINE
- 设备正在执行其他任务taskStatus=PROCESSING
- 设备没有绑定的VCC卡vccCardNumber为空
### 如何判断解绑是否完成?
通过任务查询接口检查任务状态:
- **PENDING**:任务在队列中等待
- **PROCESSING**:正在解绑中
- **COMPLETED**:解绑完成
- **FAILED**:解绑失败
### 解绑完成后如何验证?
再次调用设备列表接口,查看设备的 `vccCardNumber` 字段是否已为空。

View File

@@ -1,165 +0,0 @@
<!-- mipuyun-api-doc: device-task -->
# device/task/{taskId} - 查询任务
## 概述
任务查询API用于查询设备任务的执行状态和详情。通过绑定/解绑接口获取的任务ID可以使用本接口查询任务进度。
### 用户使用场景
本接口用于:
- 查询VCC卡绑定任务的状态
- 查询VCC卡解绑任务的状态
- 查询支付任务的状态
- 获取任务的详细信息和设备状态
## 请求说明
| 项目 | 值 |
|------|-----|
| **请求地址** | `https://${endpoint}/device/task/{taskId}` |
| **请求方法** | GET |
| **注意事项** | taskId从绑定/解绑接口返回中获取 |
### Header 参数
| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 |
|----------|------|----------|--------|------|
| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret |
### URL 参数
| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 |
|----------|------|----------|--------|------|
| taskId | String | 是 | "task-bind-123456" | 任务ID从绑定/解绑接口返回 |
## 返回参数
成功时返回任务详情:
```json
{
"deviceId": "device-001",
"deviceStatus": "ONLINE",
"task": {
"taskId": "task-bind-123456",
"taskType": "BIND_VCC",
"status": "PROCESSING",
"createTime": "2025-01-15T10:30:00Z",
"updateTime": "2025-01-15T10:35:00Z",
"deviceId": "device-001",
"orderId": null,
"creditCard": {
"cardNumber": "424242******4242",
"cvv": "***",
"expiryDate": "12/25"
},
"orderDetail": null
}
}
```
| 参数名称 | 类型 | 说明 |
|----------|------|------|
| deviceId | String | 执行任务的设备ID |
| deviceStatus | String | 设备状态ONLINE=在线OFFLINE=离线 |
| task | Object | 任务详细信息 |
### Task 任务信息
| 参数名称 | 类型 | 说明 |
|----------|------|------|
| taskId | String | 任务ID |
| taskType | String | 任务类型BIND_VCC=绑定VCC卡UNBIND_VCC=解绑VCC卡PAYMENT=支付 |
| status | String | 任务状态PENDING=等待中PROCESSING=处理中COMPLETED=已完成FAILED=失败 |
| createTime | String | 创建时间ISO 8601格式 |
| updateTime | String | 更新时间ISO 8601格式 |
| deviceId | String | 设备ID |
| orderId | String | 订单ID仅PAYMENT类型任务 |
| creditCard | Object | 信用卡信息仅BIND_VCC类型任务脱敏显示 |
| orderDetail | String | 订单详情仅PAYMENT类型任务 |
## 业务案例
### 查询绑定任务状态
### 请求
```bash
curl -X GET "https://${endpoint}/device/task/task-bind-123456" \
-H "client-key: your_key" \
-H "client-secret: your_secret"
```
### 响应 - 任务处理中
```json
{
"deviceId": "device-001",
"deviceStatus": "ONLINE",
"task": {
"taskId": "task-bind-123456",
"taskType": "BIND_VCC",
"status": "PROCESSING",
"createTime": "2025-01-15T10:30:00Z",
"updateTime": "2025-01-15T10:35:00Z",
"deviceId": "device-001",
"creditCard": {
"cardNumber": "424242******4242",
"cvv": "***",
"expiryDate": "12/25"
}
}
}
```
### 响应 - 任务完成
```json
{
"deviceId": "device-001",
"deviceStatus": "ONLINE",
"task": {
"taskId": "task-bind-123456",
"taskType": "BIND_VCC",
"status": "COMPLETED",
"createTime": "2025-01-15T10:30:00Z",
"updateTime": "2025-01-15T10:36:00Z",
"deviceId": "device-001"
}
}
```
### 响应 - 任务不存在
```json
{
"deviceId": null,
"deviceStatus": null,
"task": null
}
```
## 常见问题
### 任务状态有哪几种?
- **PENDING**:任务在队列中等待执行
- **PROCESSING**:任务正在执行中
- **COMPLETED**:任务执行完成
- **FAILED**:任务执行失败
### 任务查询不到是什么原因?
- 任务ID错误
- 任务已完成且已被清理(根据系统配置,任务可能会在完成后一段时间被清理)
- 任务ID来自其他客户
### 为什么要轮询查询任务状态?
因为绑定/解绑操作是异步的设备需要时间完成操作。建议间隔3-5秒轮询一次直到任务状态变为 COMPLETED 或 FAILED。
### 信用卡信息为什么要脱敏?
为了保护支付安全任务详情中的信用卡信息只显示脱敏后的卡号和掩码CVV。

View File

@@ -1,62 +0,0 @@
<!-- mipuyun-api-doc: 10_error-codes -->
# 错误码说明
## 完整错误码列表
| 错误码 | 枚举名称 | 消息 | 可重试 | 说明 |
|--------|----------|------|--------|------|
| 100 | - | 鉴权失败 | false | 请检查 client-key, client-secret 信息是否正确 |
| 102 | - | 超过限流 | true | 请求频率超过限制,请稍后重试 |
| 103 | - | Proxy error | true | 代理服务器失败,请检查代理服务器 URL 是否正确,或者代理 IP 用量是否足够。如果使用平台代理且刚刚修改过命名,请等待 5 分钟平台缓存同步再试 |
| 104 | - | Agent not exist or agent is disabled | false | 检查 agent code 是否正确agent 是否被停用。如果刚刚创建运行器agent请等待 5 分钟平台缓存同步再试 |
| 40013 | AGENT_MISSED_CREDENTIAL | Agent credential not configured | false | Agent 凭证未配置,请检查 Agent 的认证信息是否正确配置 |
| 42010 | PRICE_CHANGED | Fare price has changed, please check latest price from feeitem | false | 价格变动,请检查响应中的 feeItems 获取最新价格 |
| 41007 | FLIGHT_NOT_AVAILABLE | Flight not available | true | 航班不存在/已售罄 |
| 42011 | INSUFFICIENT_SEATS | Insufficient seats | true | 剩余座位不足 |
| 42000 | ORDER_ID_REQUIRED | Order ID is required | false | 缺少订单 ID |
| 42001 | ORDER_ID_DUPLICATE | Order ID already exists | false | 订单 ID 重复 |
| 42002 | PASSENGER_MISMATCH | Passenger information mismatch | false | 乘客信息不匹配 |
| 42003 | BOOKING_FAILED | Failed to create booking | true | 预订失败 |
| 42004 | HOLD_EXECUTION_FAILED | Hold execution failed | true | Hold 执行失败 |
| 42005 | BOOKING_NOT_FOUND | Booking not found | false | 预订未找到 |
| 42006 | DUPLICATE_PASSENGER_NAME | Duplicate passenger name in booking | false | 乘客姓名重复 |
| 42007 | DUPLICATE_BOOKING | Duplicate booking detected | false | 重复预订 |
| 42008 | INVALID_PASSENGER_INFO | Invalid passenger information | false | 乘客信息错误 |
| 42009 | DOCUMENT_ISSUE | Document validation failed | false | 证件问题 |
| 42012 | SEAT_NOT_AVAILABLE | Selected seat is not available | false | 所选座位不可用 |
| 49000 | SYSTEM_ERROR | Internal system error | true | 系统内部错误 |
| 49003 | QUOTA_EXCEEDED | Daily quota exceeded. Please upgrade your plan or try again tomorrow. | false | 日配额已超限,请升级套餐或明天再试 |
## PRICE_CHANGED (42010) 详细说明
**触发场景:**
当航司侧票价高于预期时,接口会返回错误码 42010。
**响应结构:**
```json
{
"code": 42010,
"msg": "Fare price has changed, please check latest price from feeitem",
"timestamp": "2024-11-15T10:30:00Z",
"data": {
"orderId": "abbc4b57fb75",
"feeItems": {
"baseFare": 110.00,
"taxes": 52.50,
"total": 162.50,
"currency": "EUR"
}
}
}
```
**处理流程:**
1. 接收到 42010 错误响应
2. 从响应的 `data.feeItems` 中获取最新价格
3. 向用户展示价格变动信息
4. 询问用户是否接受新价格
5. 如用户同意,重新调用接口
6. 如用户拒绝,取消订单流程

View File

@@ -1,90 +0,0 @@
<!-- mipuyun-api-doc: mailbox-domain-list -->
# mailbox/domain/list - 域名列表
## 概述
查询当前客户已绑定的所有邮箱域名列表。
### 使用场景
- 查看当前客户已绑定的所有邮箱域名列表
- 管理员需要了解企业邮箱的域名配置情况
## 请求说明
| 项目 | 值 |
|------|-----|
| **请求地址** | `https://${endpoint}/mailbox/domain/list` |
| **请求方法** | POST |
| **注意事项** | 无需请求体,系统自动从请求上下文中提取 clientCode |
### Header 参数
| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 |
|----------|------|----------|--------|------|
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret |
## 返回参数
返回 `MailboxDomain` 对象数组:
| 参数名称 | 类型 | 示例值 | 说明 |
|----------|------|--------|------|
| domainId | Long | 123 | 域名配置主键 |
| domainName | String | example.com | 域名全称 |
| status | Integer | 1 | 状态0-待审核1-已启用2-已禁用3-已删除 |
| statusText | String | 已启用 | 状态文本描述 |
| verifyStatus | Integer | 2 | 验证状态0-未验证1-验证中2-验证通过3-验证失败 |
| verifyStatusText | String | 验证通过 | 验证状态文本 |
| verifyToken | String | a1b2c3d4 | DNS验证令牌 |
| verifyHost | String | _mailbox-verify-a1b2c3d4 | DNS验证主机名 |
| verifyTime | String | 2025-01-01T10:00:00Z | 验证通过时间ISO 8601格式 |
| createTime | String | 2025-01-01T10:00:00Z | 创建时间ISO 8601格式 |
| updateTime | String | 2025-01-02T10:00:00Z | 更新时间ISO 8601格式 |
| remark | String | 备注信息 | 备注 |
## 返回示例
```json
[
{
"domainId": 123,
"domainName": "example.com",
"status": 1,
"statusText": "已启用",
"verifyStatus": 2,
"verifyStatusText": "验证通过",
"verifyToken": "a1b2c3d4",
"verifyHost": "_mailbox-verify-a1b2c3d4",
"verifyTime": "2025-01-01T10:00:00Z",
"createTime": "2025-01-01T10:00:00Z",
"updateTime": "2025-01-02T10:00:00Z",
"remark": "主域名"
},
{
"domainId": 124,
"domainName": "company.com",
"status": 0,
"statusText": "待审核",
"verifyStatus": 0,
"verifyStatusText": "未验证",
"verifyToken": "e5f6g7h8",
"verifyHost": "_mailbox-verify-e5f6g7h8",
"verifyTime": null,
"createTime": "2025-01-03T10:00:00Z",
"updateTime": "2025-01-03T10:00:00Z",
"remark": "备用域名"
}
]
```
## 错误码说明
| 错误码 | 说明 |
|--------|------|
| 401 | 认证失败client-key 或 client-secret 无效 |
| 403 | 权限不足,无权访问该客户的域名列表 |
| 500 | 服务器内部错误 |

View File

@@ -1,90 +0,0 @@
<!-- mipuyun-api-doc: mailbox-domain-apply -->
# mailbox/domain/apply - 申请绑定域名
# 申请绑定域名(mailbox/domain/apply) API 说明文档
## 使用场景
> 1. 企业客户需要为自己的邮箱系统绑定自定义域名
> 1. 配置企业邮箱后,员工可以使用 @company.com 格式的邮箱地址
## 错误场景(不应该使用本接口的场景)
> 1. 域名已被其他客户绑定
> 1. 域名格式不正确(如包含非法字符)
> 1. 客户已达到域名绑定数量上限
## 性能指标
> - 响应速度98% 的请求响应速度 < 1000ms
## 请求说明
| **请求地址** | https://${endpoint}/mailbox/domain/apply |
| --- | --- |
| **请求方法** | POST |
## 请求参数
### Header
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境secret |
### 请求体
```json
{
"domainName": "example.com"
}
```
#### 参数详情
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| domainName | String | 是 | example.com | 要绑定的域名名称,必须是有效的域名格式 |
## 返回参数
| **参数名称** | **类型** | **示例值** | **说明** |
| --- | --- | --- | --- |
| domainId | Long | 123 | 域名唯一标识 |
| domainName | String | example.com | 域名名称 |
| status | String | PENDING | 域名状态PENDING=待DNS验证 |
| dnsVerified | Boolean | false | DNS验证状态新申请的域名需要验证 |
| createTime | String | 2025-01-02T10:00:00Z | 域名创建时间ISO 8601格式 |
## 返回示例
```json
{
"domainId": 123,
"domainName": "example.com",
"status": "PENDING",
"dnsVerified": false,
"createTime": "2025-01-02T10:00:00Z"
}
```
## 后续步骤
申请域名绑定后需要完成DNS验证才能激活邮箱服务
1. 在域名DNS管理中添加相应的MX记录
2. 调用 `mailbox/domain/verify` 接口验证DNS配置
3. 验证通过后,域名状态将变为 ACTIVE
## 错误码说明
| **错误码** | **说明** |
| --- | --- |
| 400 | 请求参数错误,域名格式不正确 |
| 401 | 认证失败client-key 或 client-secret 无效 |
| 409 | 域名已被其他客户绑定 |
| 429 | 已达到域名绑定数量上限 |
| 500 | 服务器内部错误 |

View File

@@ -1,87 +0,0 @@
<!-- mipuyun-api-doc: mailbox-domain-detail -->
# mailbox/domain/detail - 域名详情
# 查询域名详情(mailbox/domain/detail) API 说明文档
## 使用场景
> 1. 查看指定域名的详细配置信息
> 1. 管理员需要了解域名的DNS配置和验证状态
## 错误场景(不应该使用本接口的场景)
> 1. 查询的域名ID不存在
> 1. 查询的域名不属于当前客户(权限不足)
## 性能指标
> - 响应速度98% 的请求响应速度 < 500ms
## 请求说明
| **请求地址** | https://${endpoint}/mailbox/domain/detail |
| --- | --- |
| **请求方法** | POST |
## 请求参数
### Header
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境secret |
### 请求体
```json
{
"domainId": 123
}
```
#### 参数详情
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| domainId | Long | 是 | 123 | 域名ID |
## 返回参数
| **参数名称** | **类型** | **示例值** | **说明** |
| --- | --- | --- | --- |
| domainId | Long | 123 | 域名唯一标识 |
| domainName | String | example.com | 域名名称 |
| status | String | ACTIVE | 域名状态 |
| dnsVerified | Boolean | true | DNS验证状态 |
| createTime | String | 2025-01-01T10:00:00Z | 域名创建时间 |
| updateTime | String | 2025-01-02T10:00:00Z | 域名更新时间 |
| mxRecord | String | mail.example.com | MX记录如已配置 |
| txtRecord | String | v=spf1 include:... | SPF记录如已配置 |
## 返回示例
```json
{
"domainId": 123,
"domainName": "example.com",
"status": "ACTIVE",
"dnsVerified": true,
"createTime": "2025-01-01T10:00:00Z",
"updateTime": "2025-01-02T10:00:00Z",
"mxRecord": "mail.example.com",
"txtRecord": "v=spf1 include:_spf.example.com ~all"
}
```
## 错误码说明
| **错误码** | **说明** |
| --- | --- |
| 400 | 请求参数错误domainId 格式不正确 |
| 401 | 认证失败client-key 或 client-secret 无效 |
| 403 | 权限不足,无权访问该域名信息 |
| 404 | 域名不存在 |
| 500 | 服务器内部错误 |

View File

@@ -1,95 +0,0 @@
<!-- mipuyun-api-doc: mailbox-domain-delete -->
# mailbox/domain/delete - 删除域名
# 删除域名(mailbox/domain/delete) API 说明文档
## 使用场景
> 1. 企业不再需要使用某个域名作为邮箱域名
> 1. 域名配置错误,需要删除后重新申请
## 错误场景(不应该使用本接口的场景)
> 1. 域名下仍有活跃的邮箱账户(需要先删除邮箱账户)
> 1. 域名不属于当前客户(权限不足)
> 1. 域名ID不存在
## 性能指标
> - 响应速度98% 的请求响应速度 < 1000ms
## 请求说明
| **请求地址** | https://${endpoint}/mailbox/domain/delete |
| --- | --- |
| **请求方法** | POST |
## 请求参数
### Header
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境secret |
### 请求体
```json
{
"domainId": 123
}
```
#### 参数详情
| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** |
| --- | --- | --- | --- | --- |
| domainId | Long | 是 | 123 | 要删除的域名ID |
## 返回参数
| **参数名称** | **类型** | **示例值** | **说明** |
| --- | --- | --- | --- |
| code | Integer | 0 | 状态码0=成功1=失败 |
| msg | String | Domain deleted successfully | 操作结果消息 |
| domainId | Long | 123 | 被删除的域名ID |
## 返回示例
成功响应:
```json
{
"code": 0,
"msg": "Domain deleted successfully",
"domainId": 123
}
```
失败响应:
```json
{
"code": 1,
"msg": "Failed to delete domain",
"domainId": 123
}
```
## 注意事项
> - 删除域名是不可逆操作,请谨慎操作
> - 删除前请确保该域名下没有关联的邮箱账户
> - 删除后,该域名的所有邮箱服务将停止
## 错误码说明
| **错误码** | **说明** |
| --- | --- |
| 400 | 请求参数错误domainId 格式不正确 |
| 401 | 认证失败client-key 或 client-secret 无效 |
| 403 | 权限不足,无权删除该域名 |
| 404 | 域名不存在 |
| 409 | 域名下仍有活跃的邮箱账户,无法删除 |
| 500 | 服务器内部错误 |

View File

@@ -1,400 +0,0 @@
<!-- mipuyun-api-doc: mailbox-mail-search -->
# mailbox/mail/search - 搜索邮件
## 概述
根据关键词、邮件类型等条件搜索邮件,返回分页结果。
### 使用场景
- 用户需要在邮箱中查找特定邮件
- 根据关键词、邮件类型等条件筛选邮件
- 分页浏览邮件列表
## 请求说明
| 项目 | 值 |
|------|-----|
| **请求地址** | `https://${endpoint}/mailbox/mail/search` |
| **请求方法** | POST |
### Header 参数
| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 |
|----------|------|----------|--------|------|
| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON |
| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 |
| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key |
| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret |
### 请求体参数
| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 |
|----------|------|----------|--------|------|
| keyword | String | 否 | 机票 | 搜索关键词,支持邮件主题、发件人等 |
| mailType | Integer | 否 | 1 | 邮件类型1-收件2-发件 |
| domainName | String | 否 | example.com | 域名过滤 |
| hasAttachment | Boolean | 否 | true | 是否有附件 |
| readStatus | Integer | 否 | 0 | 阅读状态0-未读1-已读 |
| startTime | String | 否 | 2025-01-01T00:00:00Z | 起始时间ISO 8601格式**必须使用UTC时区**,以`Z`结尾) |
| endTime | String | 否 | 2025-01-31T23:59:59Z | 结束时间ISO 8601格式**必须使用UTC时区**,以`Z`结尾) |
| airlineCode | String | 否 | CA | 航空代码CA-国航、MU-东航、CZ-南航等) |
| pageIndex | Integer | 是 | 1 | 页码从1开始 |
| pageSize | Integer | 是 | 20 | 每页数量建议10-50最大100 |
| orderBy | String | 否 | sendTime | 排序字段,支持:`sendTime`(发送时间)、`receiveTime`(接收时间)、`subject`(主题) |
| orderDirection | String | 否 | DESC | 排序方向:`ASC`=升序,`DESC`=降序 |
#### 请求体示例
```json
{
"keyword": "机票",
"mailType": 1,
"airlineCode": "CA",
"pageIndex": 1,
"pageSize": 20,
"orderBy": "sendTime",
"orderDirection": "DESC"
}
```
## 返回参数
返回分页结果 `PageResult<MailInfo>`
| 参数名称 | 类型 | 示例值 | 说明 |
|----------|------|--------|------|
| pageIndex | Integer | 1 | 当前页码 |
| pageSize | Integer | 20 | 每页数量 |
| total | Long | 150 | 总记录数 |
| records | Array\<MailInfo\> | - | 邮件列表 |
> **注意**
> - 请求和响应统一使用 `pageIndex`
> - 总页数 `totalPages` 字段已移除,前端需自行计算:`totalPages = Math.ceil(total / pageSize)`
### MailInfo 对象字段
| 参数名称 | 类型 | 示例值 | 说明 |
|----------|------|--------|------|
| mailId | String | MSG_001 | 邮件ID |
| subject | String | 机票预订确认 | 邮件主题 |
| fromAddress | String | airline@example.com | 发件人地址 |
| toAddresses | String\[\] | ["user@example.com"] | 收件人列表 |
| ccAddresses | String\[\] | ["cc@example.com"] | 抄送列表 |
| mailType | Integer | 1 | 邮件类型1-收件2-发件 |
| mailTypeText | String | 收件 | 类型文本 |
| hasAttachment | Boolean | true | 是否有附件 |
| attachmentCount | Integer | 2 | 附件数量 |
| mailSize | Long | 1024000 | 邮件大小(字节) |
| mailSizeText | String | 1.0 MB | 人性化大小 |
| readStatus | Integer | 0 | 阅读状态0-未读1-已读 |
| receiveTime | String | 2025-01-02T10:00:00Z | 接收时间 |
| sendTime | String | 2025-01-02T10:00:00Z | 发送时间 |
| emlDownloadUrl | String | https://... | 原始EML文件下载地址临时链接1小时有效期 |
## 返回示例
```json
{
"pageIndex": 1,
"pageSize": 20,
"total": 150,
"records": [
{
"mailId": "MSG_001",
"subject": "机票预订确认",
"fromAddress": "airline@example.com",
"toAddresses": ["user@example.com"],
"ccAddresses": ["cc@example.com"],
"mailType": 1,
"mailTypeText": "收件",
"hasAttachment": true,
"attachmentCount": 2,
"mailSize": 1024000,
"mailSizeText": "1.0 MB",
"readStatus": 0,
"receiveTime": "2025-01-02T10:00:00Z",
"sendTime": "2025-01-02T10:00:00Z",
"emlDownloadUrl": "https://cdn.example.com/download/eml/MSG_001?token=xxx"
},
{
"mailId": "MSG_002",
"subject": "航班变更通知",
"fromAddress": "airline@example.com",
"toAddresses": ["user@example.com"],
"ccAddresses": [],
"mailType": 1,
"mailTypeText": "收件",
"hasAttachment": false,
"attachmentCount": 0,
"mailSize": 51200,
"mailSizeText": "50.0 KB",
"readStatus": 1,
"receiveTime": "2025-01-02T09:00:00Z",
"sendTime": "2025-01-02T09:00:00Z",
"emlDownloadUrl": "https://cdn.example.com/download/eml/MSG_002?token=yyy"
}
]
}
```
## 时区说明
### UTC 时区要求
本接口要求所有时间参数**必须使用 UTC 时区**(协调世界时)。
**什么是 UTC**
- UTCCoordinated Universal Time是世界标准时间不受夏令时影响
- UTC 是全球统一的时间基准,与地理位置无关
- 北京时间 = UTC + 8小时纽约时间 = UTC - 5小时东部标准时间
**为什么使用 UTC**
1. **避免时区混淆**:不同地区的服务器和客户端使用不同时区会导致时间解释错误
2. **跨时区一致**:确保全球用户查询结果一致
3. **简化逻辑**:无需处理复杂的时区转换和夏令时问题
**时间格式要求:**
```
ISO 8601 格式2025-01-15T00:00:00Z
^^^^^^^^^^^ ^^^^^^^ ^ ^
日期 时间 时区标识UTC
```
### 时间转换示例
| 本地时间 | UTC 时间(需要发送) | 说明 |
|----------|---------------------|------|
| 2025-01-15 00:00:00 (北京) | 2025-01-14T16:00:00Z | 北京时间 UTC+8减8小时 |
| 2025-01-15 00:00:00 (纽约) | 2025-01-15T05:00:00Z | 纽约时间 UTC-5加5小时 |
| 2025-01-15 00:00:00 (伦敦) | 2025-01-15T00:00:00Z | 伦敦时间 UTC+0冬季 |
| 2025-01-15 00:00:00 (东京) | 2025-01-14T15:00:00Z | 东京时间 UTC+9减9小时 |
> **重要**:发送时间前,请务必将本地时间转换为 UTC 时间!
### 代码示例
#### Java 示例
```java
import java.time.*;
import java.time.format.DateTimeFormatter;
import okhttp3.*;
import com.google.gson.Gson;
public class MailSearchExample {
private static final String API_ENDPOINT = "https://api.example.com/mailbox/mail/search";
private static final String CLIENT_KEY = "your-client-key";
private static final String CLIENT_SECRET = "your-client-secret";
public static void main(String[] args) {
// 方式1: 使用当前时间自动转换为UTC
ZonedDateTime nowUtc = ZonedDateTime.now(ZoneOffset.UTC);
ZonedDateTime oneDayAgoUtc = nowUtc.minusDays(1);
// 方式2: 从本地时间转换到UTC
ZonedDateTime localTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime utcTime = localTime.withZoneSameInstant(ZoneOffset.UTC);
// 方式3: 指定特定时间例如北京时间2025-01-15 00:00:00
ZonedDateTime beijingTime = ZonedDateTime.of(
2025, 1, 15, 0, 0, 0, 0,
ZoneId.of("Asia/Shanghai")
);
ZonedDateTime startTimeUtc = beijingTime.withZoneSameInstant(ZoneOffset.UTC);
ZonedDateTime endTimeUtc = beijingTime.plusDays(1).minusSeconds(1)
.withZoneSameInstant(ZoneOffset.UTC);
// 格式化为ISO 8601格式带Z后缀
DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT;
String startTime = startTimeUtc.format(formatter); // 2025-01-14T16:00:00Z
String endTime = endTimeUtc.format(formatter); // 2025-01-15T15:59:59Z
// 构建请求体
MailSearchRequest request = new MailSearchRequest();
request.setKeyword("机票");
request.setMailType(1);
request.setStartTime(startTime);
request.setEndTime(endTime);
request.setPageIndex(1);
request.setPageSize(20);
// 设置排序(按发送时间降序)
request.setOrderBy("sendTime");
request.setOrderDirection("DESC");
// 发送请求
sendRequest(request);
}
static class MailSearchRequest {
private String keyword;
private Integer mailType;
private String startTime;
private String endTime;
private Integer pageIndex;
private Integer pageSize;
private String orderBy;
private String orderDirection;
// getters and setters...
}
private static void sendRequest(MailSearchRequest request) throws Exception {
Gson gson = new Gson();
String json = gson.toJson(request);
OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(
json,
MediaType.parse("application/json; charset=utf-8")
);
Request httpRequest = new Request.Builder()
.url(API_ENDPOINT)
.post(body)
.addHeader("Content-Type", "application/json")
.addHeader("Accept-Encoding", "gzip, deflate, br")
.addHeader("client-key", CLIENT_KEY)
.addHeader("client-secret", CLIENT_SECRET)
.build();
try (Response response = client.newCall(httpRequest).execute()) {
System.out.println(response.body().string());
}
}
}
```
#### Python 示例
```python
import requests
from datetime import datetime, timezone, timedelta
from zoneinfo import ZoneInfo # Python 3.9+
import json
API_ENDPOINT = "https://api.example.com/mailbox/mail/search"
CLIENT_KEY = "your-client-key"
CLIENT_SECRET = "your-client-secret"
def search_mails():
# 方式1: 使用当前UTC时间
now_utc = datetime.now(timezone.utc)
one_day_ago_utc = now_utc - timedelta(days=1)
# 方式2: 从本地时间转换到UTC例如北京时间
beijing_tz = ZoneInfo("Asia/Shanghai")
local_time = datetime(2025, 1, 15, 0, 0, 0, tzinfo=beijing_tz)
start_time_utc = local_time.astimezone(timezone.utc)
end_time_utc = (local_time + timedelta(days=1) - timedelta(seconds=1)).astimezone(timezone.utc)
# 方式3: 直接创建UTC时间
start_time_utc = datetime(2025, 1, 14, 16, 0, 0, tzinfo=timezone.utc)
end_time_utc = datetime(2025, 1, 15, 15, 59, 59, tzinfo=timezone.utc)
# 格式化为ISO 8601格式带Z后缀
start_time = start_time_utc.isoformat()
end_time = end_time_utc.isoformat()
# 结果: "2025-01-14T16:00:00+00:00" 或 "2025-01-14T16:00:00Z"
# 确保以Z结尾某些库可能使用+00:00
if not start_time.endswith('Z'):
start_time = start_time.replace('+00:00', 'Z').replace('+00:00:00', 'Z')
if not end_time.endswith('Z'):
end_time = end_time.replace('+00:00', 'Z').replace('+00:00:00', 'Z')
# 构建请求体
payload = {
"keyword": "机票",
"mailType": 1,
"startTime": start_time, # "2025-01-14T16:00:00Z"
"endTime": end_time, # "2025-01-15T15:59:59Z"
"pageIndex": 1,
"pageSize": 20,
"orderBy": "sendTime",
"orderDirection": "DESC"
}
# 发送请求
headers = {
"Content-Type": "application/json",
"Accept-Encoding": "gzip, deflate, br",
"client-key": CLIENT_KEY,
"client-secret": CLIENT_SECRET
}
response = requests.post(
API_ENDPOINT,
data=json.dumps(payload),
headers=headers
)
print(f"Status Code: {response.status_code}")
print(f"Response: {response.json()}")
return response.json()
# 常用时区转换工具函数
def convert_to_utc(local_time_str, local_timezone="Asia/Shanghai"):
"""
将本地时间字符串转换为UTC时间字符串
Args:
local_time_str: 本地时间,如 "2025-01-15 00:00:00"
local_timezone: 本地时区,默认 "Asia/Shanghai"
Returns:
UTC时间字符串"2025-01-14T16:00:00Z"
"""
local_tz = ZoneInfo(local_timezone)
local_dt = datetime.strptime(local_time_str, "%Y-%m-%d %H:%M:%S")
local_dt = local_dt.replace(tzinfo=local_tz)
utc_dt = local_dt.astimezone(timezone.utc)
return utc_dt.isoformat().replace('+00:00', 'Z')
# 使用示例
if __name__ == "__main__":
# 北京时间 2025-01-15 00:00:00 转换为 UTC
utc_time = convert_to_utc("2025-01-15 00:00:00", "Asia/Shanghai")
print(f"UTC Time: {utc_time}") # 输出: 2025-01-14T16:00:00Z
search_mails()
```
#### cURL 示例
```bash
curl -X POST "https://api.example.com/mailbox/mail/search" \
-H "Content-Type: application/json" \
-H "Accept-Encoding: gzip, deflate, br" \
-H "client-key: your-client-key" \
-H "client-secret: your-client-secret" \
-d '{
"keyword": "机票",
"mailType": 1,
"startTime": "2025-01-14T16:00:00Z",
"endTime": "2025-01-15T15:59:59Z",
"pageIndex": 1,
"pageSize": 20,
"orderBy": "sendTime",
"orderDirection": "DESC"
}'
```
## 搜索技巧
- 支持模糊匹配,关键词可以匹配邮件主题和发件人
- mailType 为可选参数,不传则搜索所有类型邮件
- 建议 pageSize 设置为 20-50 以获得最佳性能
- **时间参数必须使用 UTC 时区**,发送前请确保时间已正确转换
## 错误码说明
| 错误码 | 说明 |
|--------|------|
| 400 | 请求参数错误pageIndex、pageSize、orderBy 或 orderDirection 不合法 |
| 401 | 认证失败client-key 或 client-secret 无效 |
| 500 | 服务器内部错误 |

View File

@@ -1,55 +0,0 @@
<!-- mipuyun-api-doc: 11_proxy-conventions -->
# Proxy约定说明
用户可在luban系统(my.mipuyun.com)管理我们提供的高质量住宅代理。
为了更有效的使用代理对于Proxy字段的发送提供以下约定
---
## 1. 指定代理地区/国家
在proxy名后拼接 `-国家码(地区码)`myproxy是代理命名举例如下
| 地区(国家) | 码 | Proxy命名举例 |
|-----------|---|--------------|
| 美国 | us | myproxy-us |
| 法国 | fr | myproxy-fr |
| 英国 | uk | myproxy-uk |
| **欧洲** | **eu** | **myproxy-eu** |
---
## 2. 利用多个代理提升agent质量
为提升agent对接的质量支持在proxy字段输入多个代理并提供串行和并发来满足各种场景
### 拼接符号介绍
| 符号 | 说明 |
|------|------|
| **竖线 \|** | 多个代理随机选择 |
| **逗号 ,** | 多个代理串行选择,希望提升成功率,但可以接受响应速度略慢的场景 |
| **加号 +** | 多个代理并发执行,应用于有较高成功率&较低响应速度的场景 |
### 代理使用示例
| 代理举例 | 适合场景 | 效果 |
|----------|---------|------|
| myproxy-us | 所有场景 | 指定US地区进行agent执行 |
| myproxy-us\|myproxy-uk | 所有场景 | 从US, UK两个地区随机选择一个进行agent执行 |
| myproxy-us\|myproxy-uk,myproxy-us\|myproxy-eu | **shopping/search** | 仅对shpping/search生效可接受较大的响应时长等待第一轮采用myproxy-us\|myproxy-uk进行对接。如果成功立即返回结束业务。如果失败采用myproxy-us\|myproxy-eu启动第二轮对接。 |
| myproxy-us + myproxy-uk | **shopping/select**<br>**shopping/baggage**<br>**shopping/seat** | 采用多线程并发模式线程a采用myproxy-us,线程b采用myproxy-uk, 同时向航司发起业务请求。 |
| myproxy-us\|myproxy-eu + myproxy-uk\|myproxy-uk | **shopping/select**<br>**shopping/baggage**<br>**shopping/seat** | 采用多线程并发模式线程a采用myproxy-us\|myproxy-eu,线程b采用myproxy-uk\|myproxy-uk, 同时向航司发起业务请求。 |
---
## 3. 异常处理
### 如果将符号用在了不支持的场景怎么办?
系统会最大限度接受异常的代理串输入,但会选择最保守的执行方式,我们会采集最小的代理信息进行执行。
**示例**
- 并发场景(select/baggage/seat)收到逗号:`myproxy-us,myproxy-uk`
- 那么执行层会变成:`myproxy-us`

View File

@@ -1,157 +0,0 @@
<!-- mipuyun-api-doc: 12_brightdata-proxy-advanced -->
# BrightData 代理串高级用法
# 📄 BrightData 代理 URL 使用指南
**适用于BrightData Residential / Datacenter / Mobile Proxies**
---
## 🔗 1. 代理 URL 格式解析
示例 URL
```plaintext
http://brd-customer-hl_333c2-zone-myproxy-country-us-session-11111vy0c
```
### ✅ 结构拆解:
| **部分** | **说明** |
| --- | --- |
| brd-customer-hl_665225c2 | 客户 ID固定由 BrightData 分配) |
| zone=myproxy | 代理子账号名称 |
| country=us | 目标国家代码ISO 3166-1 alpha-2如 us, gb, jp |
| session=11111vy0c | 会话 ID用于控制 IP 是否复用) |
| proxypassword | 代理子账号密码 |
| brd.superproxy.io:22225 | BrightData 全球统一代理网关 |
> 💡 **关键参数均通过用户名username中的字段传递**,这是 BrightData 的特殊设计。
---
## 🌍 2. 如何指定国家Country
只需修改 `country=xx` 部分即可。
### 示例:
```plaintext
# 美国 IP
...-country-us-...
# 英国 IP
...-country-gb-...
# 日本 IP
...-country-jp-...
# 德国 IP
```
> ✅ 支持所有 BrightData 覆盖的国家([完整国家列表](https%3A%2F%2Fbrightdata.com%2Fcountries)
> ⚠️ 如果指定的国家无可用 IP请求可能失败或返回默认地区 IP取决于 Zone 设置)
---
## 🔁 3. 如何更换 IP获取新 IP
BrightData 使用 `**session**`** 参数控制 IP 复用**
- **相同 **`**session**`** 值 → 复用同一个出口 IP**(通常维持 5 分钟以上)
- **不同 **`**session**`** 值 → 获取全新 IP**
### ✅ 方法:修改 `session` 字段
#### 示例:
```plaintext
# Session A: 固定 IP #1
...-session-abc123@...
# Session B: 固定 IP #2与 abc123 不同)
```
> 🔑 **只要 **`**session**`** 值不同,就会分配新 IP**(即使其他参数相同)
### 💡 编程建议:
在代码中动态生成随机 session ID 来每次获取新 IP
```
import random
import string
def generate_session_id(length=10):
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
session = generate_session_id()
```
---
## 🔒 4. 如何保持 IP 不变(固定 IP
只需 **在多次请求中使用相同的 **`**session**`** 值**。
### ✅ 规则:
- 同一个 `session`**5 分钟内有效**
- 在此期间,所有使用该 session 的请求将通过 **同一个出口 IP**
- 超过 5 分钟无活动IP 可能被回收
### 示例(固定美国 IP
```plaintext
http://brd-customer-hl_665225c2-zone-myproxy-country-us-session-my_fixed_ip_001:xxxxy0c@brd.superproxy.io:22225/
```
> 🕒 建议:如果需要长时间固定 IP每 5~8 分钟发一次心跳请求以保持会话活跃。
---
## 🛠️ 5. 完整使用示例Python requests
```
import requests
# 固定美国 IPsession = "my_session_1"
proxy_url = "http://brd-customer-hl_665225c2-zone-myproxy-country-us-session-my_session_1:xxxxy0c@brd.superproxy.io:22225/"
proxies = {
"http": proxy_url,
"https": proxy_url
}
try:
resp = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(resp.json()) # 显示出口 IP
except Exception as e:
```
---
## ⚙️ 6. 其他高级参数(可选)
你还可以在 username 中添加更多参数(用 `-` 连接):
[电子表格]
> 📌 完整参数文档:[BrightData Proxy Parameters](https%3A%2F%2Fdocs.brightdata.com%2Fapi-reference%2Fproxy%2Fgeolocation-targeting%23city-targeting)
---
## ❗ 注意事项
1. **密码(**`**proxypassword**`**)是敏感信息**,切勿泄露或提交到 Git。
1. **不要硬编码 session** 用于大规模爬虫,避免 IP 被目标网站封禁。
---
## ✅ 总结:核心操作速查
[电子表格]
---

View File

@@ -1,29 +0,0 @@
<!-- mipuyun-api-doc: overview -->
# 米普云 - 航司接口转换器API文档
## 什么是航司接口转换器
航司接口转换器是一种中间服务工具用于将不同航空公司的预订、出票、查询等系统接口进行标准化和适配。由于各航司的系统架构、数据格式、认证方式各不相同直接对接成本高、开发周期长。航司接口转换器通过统一的接入层帮助第三方平台如OTA、旅行社、企业差旅系统等快速、高效地与多个航司系统对接。
该转换器的核心功能包括:
- **协议转换**将不同航司的私有API或B2B协议转换为通用标准
- **数据映射**:将航司返回的原始数据结构转换为易于理解和处理的统一格式
- **身份代理**通过Agent机制自动完成登录、鉴权、会话维持等操作
- **稳定性保障**:提供重试机制、错误处理、日志记录等功能,提升对接稳定性
## 如何使用航司接口转换器
### 1. 选择需要转换的航司&渠道
访问 https://my.mipuyun.com登录后选择航司列表菜单在你需要转换的航司上面选择对应的渠道一般渠道有 Web(官网)/B2B/API。
### 2. 创建Agent
### 3. 在线测试
创建好 Agent 后,可在控制台中选择该 Agent 并点击"运行"按钮,立即发起一次测试请求。
### 4. 通过API与我们对接
当测试成功后,你可以调用我们的开放 API 接口,实现全流程自动化。

8
sdk/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
__pycache__/
*.pyc
*.pyo
dist/
build/
*.egg-info/
.eggs/
config.json

21
sdk/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 MipuYun
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

126
sdk/README.md Normal file
View File

@@ -0,0 +1,126 @@
# mipu-requests
米普代理 HTTP 客户端,兼容 `requests` 接口。
所有 HTTP 请求通过米普 API 转发,接口与 `requests` 完全一致,迁移只需改 `import``proxies`
## 安装
```bash
pip install mipu-requests
```
## 快速开始
```python
import mipu_requests
s = mipu_requests.session(
base_url="https://api-eu.mipuyun.com",
client_key="your_client_key",
client_secret="your_client_secret",
agent="JQ",
)
s.proxies = "res_mipu_bookxWdyg"
resp = s.get("https://example.com/page")
resp.raise_for_status()
data = resp.json()
```
## 从 requests 迁移
```python
# ── 迁移前 ──────────────────────────
import requests
s = requests.Session()
s.proxies = {"https": "http://proxy:8080"}
resp = s.get("https://example.com/api")
# ── 迁移后 ──────────────────────────
import mipu_requests
s = mipu_requests.session(
base_url="https://api-eu.mipuyun.com",
client_key="your_key",
client_secret="your_secret",
agent="JQ",
)
s.proxies = "res_mipu_bookxWdyg"
resp = s.get("https://example.com/api")
```
只需两步:
1. `import requests``import mipu_requests`
2. 构造时传入凭据,`proxies` 改为字符串
其余代码(`.get()`, `.post()`, `.json()`, `.raise_for_status()` 等)无需改动。
## 请求方法
```python
resp = s.get(url, params={"key": "value"}, headers={...})
resp = s.post(url, data={"key": "value"}, headers={...})
resp = s.post(url, json={"key": "value"}, headers={...})
resp = s.put(url, data=body, headers={...})
resp = s.delete(url, headers={...})
```
## 异步模式
默认启用异步轮询,防止长连接断连:
```python
# 默认异步
s = mipu_requests.session(..., async_mode=True)
# 关闭异步
s = mipu_requests.session(..., async_mode=False)
# 自定义轮询参数
s = mipu_requests.session(
...,
poll_interval=3, # 轮询间隔(秒)
poll_max=20, # 最大轮询次数
submit_timeout=60, # 提交超时(秒)
poll_timeout=(5, 30), # 轮询超时 (connect, read)
)
```
## Response 对象
`requests.Response` 完全兼容:
```python
resp.status_code # int HTTP 状态码
resp.text # str 响应体文本
resp.content # bytes 响应体字节
resp.headers # dict 响应头
resp.ok # bool status_code 在 200-399
resp.url # str 请求 URL
resp.json() # dict 解析 JSON
resp.raise_for_status() # >=400 抛异常
```
## 异常处理
```python
from mipu_requests import RequestException, HTTPError, APIError, PollTimeoutError
try:
resp = s.get(url)
resp.raise_for_status()
except HTTPError as e:
print(f"目标站错误: {e.response.status_code}")
except APIError as e:
print(f"API 错误: {e}")
except PollTimeoutError as e:
print(f"轮询超时: {e}")
except RequestException as e:
print(f"请求异常: {e}")
```
## License
MIT

View File

@@ -0,0 +1,175 @@
# mipu_requests — 从 requests 迁移指南
## 快速对比
```python
# ── requests ──────────────────────────────────────
import requests
s = requests.Session()
s.proxies = {"https": "http://proxy:8080"}
resp = s.get("https://example.com/page")
resp.raise_for_status()
data = resp.json()
# ── mipu_requests ─────────────────────────────────
import mipu_requests
s = mipu_requests.session(
base_url="https://api-eu.mipuyun.com",
client_key="your_client_key",
client_secret="your_client_secret",
agent="JQ",
)
s.proxies = "res_mipu_bookxWdyg"
resp = s.get("https://example.com/page")
resp.raise_for_status()
data = resp.json()
```
## 安装
```bash
pip install mipu-requests
```
## 创建 Session
```python
import mipu_requests
# Agent 模式 — 通过指定 agent 走 /unlocker/agent 端点
s = mipu_requests.session(
base_url="https://api-eu.mipuyun.com",
client_key="your_key",
client_secret="your_secret",
agent="JQ",
)
# Request 模式 — 走 /unlocker/request 端点(无需 agent
s = mipu_requests.session(
base_url="https://api-eu.mipuyun.com",
client_key="your_key",
client_secret="your_secret",
mode="request",
)
```
## 设置代理
`requests` 唯一**必须修改**的地方:
```python
# requests — 传 dict
s.proxies = {"http": "http://proxy:8080", "https": "http://proxy:8080"}
# mipu_requests — 传字符串(米普 proxy 标识)
s.proxies = "res_mipu_bookxWdyg"
# 单次请求覆盖代理
resp = s.get(url, proxy="another_proxy")
```
## 请求方法
完全一致,无需改动:
```python
resp = s.get(url, params={"key": "value"}, headers={...})
resp = s.post(url, data={"key": "value"}, headers={...})
resp = s.post(url, json={"key": "value"}, headers={...})
resp = s.put(url, data=body, headers={...})
resp = s.delete(url, headers={...})
```
## Response 对象
属性和方法与 `requests.Response` 一致:
```python
resp.status_code # int HTTP 状态码
resp.text # str 响应体文本
resp.content # bytes 响应体字节
resp.headers # dict 响应头
resp.ok # bool status_code 在 200-399
resp.url # str 请求 URL
resp.encoding # str 编码(默认 utf-8
resp.cookies # dict 空 dict兼容属性
resp.reason # str 状态码原因短语
resp.json() # 解析 JSON
resp.raise_for_status() # >=400 抛异常
resp.iter_content(chunk_size) # 按块迭代
```
## 异常处理
```python
try:
resp = s.get(url)
resp.raise_for_status()
except mipu_requests.HTTPError as e:
print(f"目标站错误: {e.response.status_code}")
except mipu_requests.APIError as e:
print(f"API 错误: {e}")
except mipu_requests.RequestException as e:
print(f"请求异常: {e}")
```
## 异步模式
默认开启异步轮询防止长连接断连:
```python
# 默认异步
s = mipu_requests.session(...)
# 关闭异步
s = mipu_requests.session(..., async_mode=False)
# 自定义参数
s = mipu_requests.session(
...,
poll_interval=3,
poll_max=20,
submit_timeout=60,
poll_timeout=(5, 30),
)
```
## 完整迁移示例
```python
# ── 迁移前 (requests) ────────────────────────────
import requests
s = requests.Session()
s.headers.update({"user-agent": "Mozilla/5.0"})
s.proxies = {"https": "http://proxy:8080"}
resp = s.get("https://example.com/api", params={"page": 1})
resp.raise_for_status()
data = resp.json()
# ── 迁移后 (mipu_requests) ───────────────────────
import mipu_requests
s = mipu_requests.session(
base_url="https://api-eu.mipuyun.com",
client_key="your_key",
client_secret="your_secret",
agent="JQ",
)
s.headers = {"user-agent": "Mozilla/5.0"}
s.proxies = "res_mipu_bookxWdyg"
resp = s.get("https://example.com/api", params={"page": 1})
resp.raise_for_status()
data = resp.json()
```
**迁移只需 2 步:**
1. 构造 `mipu_requests.session()` 时传入 `base_url`/`client_key`/`client_secret`/`agent`
2. `s.proxies = {...}``s.proxies = "proxy_name"`
其余代码无需改动。

View File

@@ -0,0 +1,41 @@
"""
mipu_requests — 通过米普代理转发 HTTP 请求的 requests 兼容客户端
API 与 requests 完全一致,用户可平滑切换:
import mipu_requests
s = mipu_requests.session(
base_url="https://api-eu.mipuyun.com",
client_key="your_key",
client_secret="your_secret",
agent="JQ",
)
s.proxies = "res_mipu_bookxWdyg"
resp = s.get(url, headers={...}, params={...})
resp = s.post(url, data=form, headers={...})
resp.raise_for_status()
data = resp.json()
"""
from .session import session, Session
from .response import Response
from .exceptions import (
RequestException,
HTTPError,
APIError,
PollTimeoutError,
)
__version__ = "1.0.0"
__all__ = [
"session",
"Session",
"Response",
"RequestException",
"HTTPError",
"APIError",
"PollTimeoutError",
]

View File

@@ -0,0 +1,33 @@
"""mipu_requests 异常类 — 兼容 requests 异常命名"""
class RequestException(Exception):
"""基异常(对应 requests.exceptions.RequestException"""
pass
class APIError(RequestException):
"""Bluebird API 返回非零 code"""
def __init__(self, response):
self.response = response
code = response.get("code", "?") if isinstance(response, dict) else "?"
msg = response.get("msg", "") if isinstance(response, dict) else str(response)
super().__init__(f"API error: code={code}, msg={msg}")
class HTTPError(RequestException):
"""目标网站返回 HTTP 错误状态码(对应 requests.exceptions.HTTPError"""
def __init__(self, response):
self.response = response
super().__init__(f"{response.status_code} for {response.url}")
class PollTimeoutError(RequestException):
"""异步轮询超时"""
def __init__(self, agent_request_id, max_attempts=0):
self.agent_request_id = agent_request_id
self.max_attempts = max_attempts
super().__init__(f"Poll timeout after {max_attempts} attempts for {agent_request_id}")

View File

@@ -0,0 +1,72 @@
"""
Response — 兼容 requests.Response 的响应对象
属性/方法与 requests.Response 一致:
.status_code .text .content .headers .ok .url .encoding
.json() .raise_for_status() .iter_content()
"""
import json as _json
class Response:
"""兼容 requests.Response 的响应对象。
额外属性:
session_id: Bluebird 返回的 sessionId
raw: Bluebird 原始响应 dict
"""
def __init__(self, status_code=0, text="", headers=None, url="", raw=None):
self.status_code = int(status_code) if status_code else 0
self._text = text or ""
self.headers = dict(headers) if headers else {}
self.url = url
self.encoding = "utf-8"
self.raw = raw or {}
self.content = self._text.encode(self.encoding) if self._text else b""
self.ok = 200 <= self.status_code < 400
self.cookies = {}
@property
def text(self):
return self._text
def json(self, **kwargs):
return _json.loads(self._text, **kwargs)
def raise_for_status(self):
"""status_code >= 400 时抛出 HTTPError。"""
if not self.ok:
from .exceptions import HTTPError
raise HTTPError(self)
def iter_content(self, chunk_size=1):
"""兼容 requests.Response.iter_content()。"""
if self.content:
for i in range(0, len(self.content), chunk_size):
yield self.content[i:i + chunk_size]
@property
def session_id(self):
return self.raw.get("sessionId", "")
@property
def reason(self):
"""兼容 requests.Response.reason。"""
reasons = {
200: "OK", 201: "Created", 202: "Accepted",
301: "Moved Permanently", 302: "Found",
400: "Bad Request", 401: "Unauthorized", 403: "Forbidden",
404: "Not Found", 500: "Internal Server Error",
}
return reasons.get(self.status_code, "Unknown")
def __repr__(self):
return f"<Response [{self.status_code}]>"
def __bool__(self):
return self.ok
def __len__(self):
return len(self.content)

View File

@@ -0,0 +1,261 @@
"""
Session — 兼容 requests.Session 的米普代理客户端
用法与 requests.Session 一致:
import mipu_requests
s = mipu_requests.Session(
base_url="https://api-eu.mipuyun.com",
client_key="your_key",
client_secret="your_secret",
agent="JQ",
)
s.proxies = "res_mipu_bookxWdyg"
resp = s.get("https://example.com")
resp = s.post(url, data={"key": "val"}, headers={...})
resp.raise_for_status()
data = resp.json()
"""
import json as _json
import time
from urllib.parse import urlencode
import requests as _requests
from .exceptions import APIError, PollTimeoutError
from .response import Response
class Session:
"""兼容 requests.Session 的米普代理客户端。
Args:
base_url: 米普 API 地址 (如 "https://api-eu.mipuyun.com")
client_key: 客户端 key
client_secret: 客户端 secret
mode: "agent" → /unlocker/agent | "request" → /unlocker/request
agent: Agent 编码 (mode="agent" 时必传)
async_mode: 异步轮询 (默认 True)
poll_interval: 轮询间隔秒 (默认 3)
poll_max: 最大轮询次数 (默认 20)
submit_timeout:提交超时秒 (默认 60)
poll_timeout: 轮询超时 (connect, read) (默认 (5, 30))
"""
def __init__(
self,
base_url,
client_key,
client_secret,
mode="agent",
agent=None,
async_mode=True,
poll_interval=3,
poll_max=20,
submit_timeout=60,
poll_timeout=(5, 30),
):
self.base_url = base_url.rstrip("/")
self.client_key = client_key
self.client_secret = client_secret
if mode not in ("agent", "request"):
raise ValueError(f"mode 必须是 'agent''request',收到: {mode!r}")
if mode == "agent" and not agent:
raise ValueError("mode='agent' 时 agent 不能为空")
self.mode = mode
self._api_path = "/unlocker/agent" if mode == "agent" else "/unlocker/request"
self.agent = agent or None
self.async_mode = async_mode
self.poll_interval = poll_interval
self.poll_max = poll_max
self.submit_timeout = submit_timeout
self.poll_timeout = poll_timeout
self.proxies = None
self.headers = {}
self.session_id = None
self._http = _requests.Session()
# ── 核心请求方法 ────────────────────────────────────────────────
def request(self, method, url, *, params=None, data=None, json=None,
headers=None, content_type=None, proxy=None):
"""发送请求(兼容 requests.Session.request
Args:
method: HTTP 方法
url: 目标 URL
params: URL 查询参数 (dict)
data: 请求体 (dict → form-encoded, str → raw)
json: 请求体 (dict → JSON)
headers: 请求头 (dict)
content_type: Content-Type 覆盖
proxy: 本次请求代理覆盖
Returns:
Response
"""
proxy = proxy or self.proxies
if not proxy:
raise ValueError("未设置 proxy请通过 session.proxies = 'xxx' 或 request(proxy='xxx') 指定")
# 拼接 params
target_url = url
if params:
sep = "&" if "?" in target_url else "?"
target_url = target_url + sep + urlencode(params)
# 序列化 body
body = ""
ct = content_type
if json is not None:
body = _json.dumps(json, ensure_ascii=False)
ct = ct or "application/json"
elif data is not None:
if isinstance(data, dict):
body = urlencode(data)
ct = ct or "application/x-www-form-urlencoded"
else:
body = str(data)
# 合并 headers: session 默认 + 本次传入
merged_headers = {**self.headers, **(headers or {})}
# 构建 payload
payload = {
"url": target_url,
"method": method.upper(),
"proxy": proxy,
"async": self.async_mode,
}
if self.mode == "agent":
payload["agent"] = self.agent
if self.session_id:
payload["sessionId"] = self.session_id
if body:
payload["body"] = body
if ct:
payload["contentType"] = ct
if merged_headers:
payload["headers"] = merged_headers
# 调用米普 API
api_url = f"{self.base_url}{self._api_path}"
hdrs = {
"Content-Type": "application/json",
"client-key": self.client_key,
"client-secret": self.client_secret,
}
resp = self._http.post(api_url, json=payload, headers=hdrs,
timeout=self.submit_timeout)
api_resp = resp.json()
code = api_resp.get("code")
# 异步轮询
if code == 202 and self.async_mode:
agent_request_id = api_resp.get("agentRequestId", "")
time.sleep(2)
api_resp = self._poll_result(agent_request_id)
code = api_resp.get("code")
# 检查错误
if code != 0:
raise APIError(api_resp)
# 锁定 sessionId
sid = api_resp.get("sessionId", "")
if sid and not self.session_id:
self.session_id = sid
return Response(
status_code=api_resp.get("statusCode", 0),
text=api_resp.get("responseBody", ""),
headers=api_resp.get("responseHeaders", {}),
url=target_url,
raw=api_resp,
)
def get(self, url, **kwargs):
return self.request("GET", url, **kwargs)
def post(self, url, **kwargs):
return self.request("POST", url, **kwargs)
def put(self, url, **kwargs):
return self.request("PUT", url, **kwargs)
def delete(self, url, **kwargs):
return self.request("DELETE", url, **kwargs)
def head(self, url, **kwargs):
return self.request("HEAD", url, **kwargs)
def options(self, url, **kwargs):
return self.request("OPTIONS", url, **kwargs)
def patch(self, url, **kwargs):
return self.request("PATCH", url, **kwargs)
# ── 异步轮询 ────────────────────────────────────────────────────
def _poll_result(self, agent_request_id):
query_url = f"{self.base_url}/async/query/{agent_request_id}"
hdrs = {
"client-key": self.client_key,
"client-secret": self.client_secret,
"Connection": "close",
}
for attempt in range(1, self.poll_max + 1):
try:
s = _requests.Session()
s.headers.update(hdrs)
resp = s.get(query_url, timeout=self.poll_timeout)
s.close()
result = resp.json()
if result.get("code") == 0:
return result
except _requests.exceptions.RequestException:
pass
except ValueError:
pass
time.sleep(self.poll_interval)
raise PollTimeoutError(agent_request_id, self.poll_max)
# ── 生命周期 ────────────────────────────────────────────────────
def close(self):
self._http.close()
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def __repr__(self):
mode_info = f"agent={self.agent!r}" if self.mode == "agent" else "mode=request"
return (f"<Session({self.base_url!r}, {mode_info}, "
f"proxies={self.proxies!r})>")
def session(base_url, client_key, client_secret, **kwargs):
"""创建 Session 实例(与 requests.session() 用法一致)。
Args:
base_url: 米普 API 地址
client_key: 客户端 key
client_secret: 客户端 secret
**kwargs: 传递给 Session 的其他参数 (mode, agent, async_mode 等)
Returns:
Session
"""
return Session(base_url, client_key, client_secret, **kwargs)

38
sdk/pyproject.toml Normal file
View File

@@ -0,0 +1,38 @@
[build-system]
requires = ["setuptools>=64", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "mipu-requests"
version = "1.0.0"
description = "米普代理 HTTP 客户端,兼容 requests 接口"
readme = "README.md"
license = "MIT"
requires-python = ">=3.8"
authors = [
{ name = "MipuYun", email = "support@mipuyun.com" },
]
keywords = ["mipu", "requests", "proxy", "http", "api"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Internet :: WWW/HTTP",
]
dependencies = [
"requests>=2.20",
]
[project.urls]
Repository = "https://git.addhh.com/willow/mipu-open"
[tool.setuptools.packages.find]
include = ["mipu_requests*"]
[tool.setuptools.package-data]
mipu_requests = ["py.typed"]