# 店铺订单对接 API 文档 > 文档对象:发货系统技术人员 > 版本:v1 > 更新说明:鉴权为 **ApiKey + HMAC 签名**,请按本文实现,勿只传 Key。 --- ## 0. 你们要做什么(一句话) 1. 用**我们店铺的订单号**(`order_no`)调【订单查询】,确认商品 `sku`、是否可发货 `can_ship` 2. 可发则在你们系统发货 3. 发货结果调【发货推送】告诉我们,我们同步订单状态 发货记录、积分消耗在你们后台给店铺看;我们这边以订单状态为准。 --- ## 1. 环境信息 | 项 | 值(示例 / 请替换) | |----|---------------------| | **API 根地址 Base URL** | `https://221329.cc.cd` 开发临时地址 | | **X-Api-Key** | 测试使用,如 `sk_source_dev_key_3ad3d1bbacda0e54` | | **api_secret** | 测试使用,**只用于本地算签名,不要写在 URL/Header**, `sk_source_dev_secret_5a43ec55cd020d83` | | **时间偏差** | 默认允许 ±300 秒 | 统一响应格式: ```json { "code": 0, "message": "ok", "data": {} } ``` - `code = 0`:成功 - `code != 0`:失败,看 `message` - 时间字段统一使用 RFC3339 秒级北京时间,例如 `2026-07-30T17:53:58+08:00`;请求侧仍接受合法 RFC3339 时间。 --- ## 2. 鉴权(每次请求必带) | Header | 必填 | 说明 | |--------|------|------| | `X-Api-Key` | 是 | 店铺分配的 Key | | `X-Timestamp` | 是 | 当前 Unix **秒**时间戳 | | `X-Nonce` | 是 | 随机串,长度 8~64;同一 Key 在有效期内不可重复 | | `X-Sign` | 是 | 见下方算法 | ### 2.1 签名字符串(参数字典序 + `&` 拼接) 参与签名的参数: | 参数名 | 说明 | |--------|------| | `api_key` | 与 Header `X-Api-Key` 相同 | | `timestamp` | 与 Header `X-Timestamp` 相同 | | `nonce` | 与 Header `X-Nonce` 相同 | | `method` | 大写:`GET` / `POST` | | `path` | 仅路径,**不要**域名、**不要** query。例:`/api/open/v1/orders/O123` | | `body` | 原始 HTTP body;GET 用**空字符串** | 规则: 1. value **原样拼接,不做 URL encode** 2. 按参数名 **ASCII 字典序** 排序 3. 拼成:`k1=v1&k2=v2&k3=v3...` 4. `X-Sign = hex( HMAC-SHA256( api_secret, 签名字符串 ) )`,**小写**十六进制 排序后参数名顺序固定为: ```text api_key, body, method, nonce, path, timestamp ``` **GET 示例**(body 为空): ```text api_key=sk_xxx&body=&method=GET&nonce=a1b2c3d4e5f67890&path=/api/open/v1/orders/O202607201550038000×tamp=1721450000 ``` **POST 示例**: ```text api_key=sk_xxx&body={"order_no":"O202607201550038000","ship_status":"success"}&method=POST&nonce=a1b2c3d4e5f67890&path=/api/open/v1/orders/ship-notify×tamp=1721450000 ``` ### 2.2 Python 参考实现 ```python import hmac, hashlib, time, uuid, requests API_KEY = "请替换为店铺下发的 key" API_SECRET = "请替换为店铺下发的 secret" BASE = "https://api.example.com" # 请替换 def build_sign_string(api_key: str, timestamp: str, nonce: str, method: str, path: str, body: str = "") -> str: params = { "api_key": api_key, "body": body, "method": method.upper(), "nonce": nonce, "path": path, "timestamp": timestamp, } # 字典序 + & 拼接,value 不 encode return "&".join(f"{k}={params[k]}" for k in sorted(params.keys())) def sign_headers(method: str, path: str, body: str = "") -> dict: ts = str(int(time.time())) nonce = uuid.uuid4().hex raw = build_sign_string(API_KEY, ts, nonce, method, path, body) sign = hmac.new(API_SECRET.encode(), raw.encode(), hashlib.sha256).hexdigest() return { "X-Api-Key": API_KEY, "X-Timestamp": ts, "X-Nonce": nonce, "X-Sign": sign, } # —— 查询订单 —— path = "/api/open/v1/orders/O202607201550038000" # 换成真实店铺订单号 print(requests.get(BASE + path, headers=sign_headers("GET", path)).json()) # —— 推送发货成功(body 必须与签名用的 body 完全一致)—— path = "/api/open/v1/orders/ship-notify" body = '{"order_no":"O202607201550038000","ship_status":"success","provider_order_no":"SRC001"}' headers = {"Content-Type": "application/json", **sign_headers("POST", path, body)} print(requests.post(BASE + path, headers=headers, data=body.encode("utf-8")).json()) ``` **注意:** - POST 时签名用的 `body` 必须与实际发送的 body **字节级一致**(不要一边签名一边再改空格/字段顺序)。 - value **不要** URL encode。 - 签名失败常见原因:secret 错、path 多了 query、body 不一致、时间戳过期、nonce 重复、参数未按字典序拼接。 --- ## 3. 接口一:订单查询(发货前置) ### 3.1 请求 ```http GET /api/open/v1/orders/{order_no} ``` `order_no`:店铺订单号(由买家/店铺提供,格式类似 `O202607201550038000`)。 ### 3.2 成功示例 ```json { "code": 0, "message": "ok", "data": { "order_no": "O202607201550038000", "status": "paid", "can_ship": true, "cannot_ship_reason": "", "product": { "name": "套装-糯粉咩咩", "sku": "suit_pink_sheep", "game": "和平精英" }, "buyer_name": "测试买家", "amount": 0, "provider_order_no": "", "created_at": "2026-07-20T15:50:03+08:00", "shipped_at": null, "ship_fail_reason": "" } } ``` ### 3.3 字段说明 | 字段 | 说明 | |------|------| | `order_no` | 店铺订单号 | | `status` | 订单状态,见下表 | | **`can_ship`** | **是否允许发货,发货前必须为 true** | | `cannot_ship_reason` | 不可发原因(can_ship=false 时) | | **`product.sku`** | **发货商品标识(英文固定码),请按此发货** | | `product.name` | 中文名(展示用) | | `product.game` | 游戏,如「和平精英」 | | `buyer_name` | 买家备注 | | `amount` | 金额 | | `provider_order_no` | 若已回传过你们的单号 | | `shipped_at` | 发货成功时间 | | `ship_fail_reason` | 最近失败原因 | ### 3.4 订单状态与 can_ship | status | 含义 | can_ship | |--------|------|----------| | `paid` | 已支付 | **true** ← 正常可发 | | `delivering` | 发货中 | false | | `delivered` | 已交付 | false | | `ship_failed` | 发货失败 | **true** ← 可重试 | | `cancelled` | 已取消 | false | ### 3.5 错误 | HTTP / code | 含义 | |-------------|------| | 401 | 鉴权失败(Key/签名/时间/Nonce) | | 404 | 订单号不存在 | | 400 | 参数错误 | --- ## 4. 接口二:发货结果推送 > 当前接口只接受 `success` / `failed` 两种最终结果;当 `ship_status=failed` 时,`fail_reason` 必须填写详细失败原因。 ### 4.1 请求 ```http POST /api/open/v1/orders/ship-notify Content-Type: application/json ``` ```json { "order_no": "O202607201550038000", "ship_status": "success", "provider_order_no": "SRC20260720001", "shipped_at": "2026-07-20T16:00:00+08:00", "fail_reason": "" } ``` | 字段 | 必填 | 说明 | |------|------|------| | `order_no` | 是 | 店铺订单号 | | `ship_status` | 是 | 仅 `success` / `failed` | | `provider_order_no` | 否 | 你们系统的发货单号 | | `shipped_at` | 否 | RFC3339 时间,建议秒级北京时间;success 未传则用服务端时间 | | `fail_reason` | 失败时是 | `failed` 时必填,填写详细失败原因 | ### 4.2 ship_status → 我们订单状态 | ship_status | 订单变为 | 说明 | |-------------|---------|------| | `success` | `delivered` | 发货成功 | | `failed` | `ship_failed` | 失败,允许之后再次查询并重试 | ### 4.3 幂等与限制 - 订单已是 `delivered`,再推 `success` → **仍返回成功**,不重复处理 - 订单已是 `delivered`,推 `failed` → 拒绝 - 订单 `cancelled` → 拒绝更新 ### 4.4 成功响应示例 ```json { "code": 0, "message": "ok", "data": { "order_no": "O202607201550038000", "status": "delivered", "message": "发货成功,订单已交付" } } ``` --- ## 5. 推荐调用顺序 ```text 拿到店铺订单号 order_no ↓ GET 订单查询 ↓ can_ship == false ? → 停止,展示 cannot_ship_reason ↓ true 按 product.sku 发货 ↓ POST ship_status=success 或 failed ``` **发货请以 `product.sku` 为准**,不要只依赖中文名。 --- ## 6. 联调检查清单 - [ ] 只带 Key 不带签名 → 401 - [ ] Secret 错误 → 401「签名校验失败」 - [ ] 错误/过期 Timestamp、重复 Nonce → 401 - [ ] 不存在的 order_no → 404 - [ ] 未支付订单 can_ship=false - [ ] 已支付订单 can_ship=true,且 sku 正确 - [ ] success 后订单 delivered - [ ] 重复 success 幂等成功 - [ ] failed 后可再 query 且 can_ship=true --- ## 7. 商品 sku 说明 商品英文码与中文名对照由店铺维护(如:套装-糯粉咩咩 → `suit_pink_sheep`)。 查询接口返回的 `product.sku` 即当前订单应发商品。 --- ## 8. 联系方式 接口问题、密钥申请、联调订单号请联系**店铺方技术对接人**。 本文档由店铺系统生成/维护,以店铺最新版本为准。