8.8 KiB
8.8 KiB
店铺订单对接 API 文档
文档对象:发货系统技术人员
版本:v1
更新说明:鉴权为 ApiKey + HMAC 签名,请按本文实现,勿只传 Key。
0. 你们要做什么(一句话)
- 用我们店铺的订单号(
order_no)调【订单查询】,确认商品sku、是否可发货can_ship - 可发则在你们系统发货
- 发货结果调【发货推送】告诉我们,我们同步订单状态
发货记录、积分消耗在你们后台给店铺看;我们这边以订单状态为准。
1. 环境信息
| 项 | 值(示例 / 请替换) |
|---|---|
| API 根地址 Base URL | https://http://221329.cc.cd 开发临时地址 |
| X-Api-Key | 测试使用,如 sk_source_dev_key_3ad3d1bbacda0e54 |
| api_secret | 测试使用,只用于本地算签名,不要写在 URL/Header, sk_source_dev_secret_5a43ec55cd020d83 |
| 时间偏差 | 默认允许 ±300 秒 |
统一响应格式:
{
"code": 0,
"message": "ok",
"data": {}
}
code = 0:成功code != 0:失败,看message
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 用空字符串 |
规则:
- value 原样拼接,不做 URL encode
- 按参数名 ASCII 字典序 排序
- 拼成:
k1=v1&k2=v2&k3=v3... X-Sign = hex( HMAC-SHA256( api_secret, 签名字符串 ) ),小写十六进制
排序后参数名顺序固定为:
api_key, body, method, nonce, path, timestamp
GET 示例(body 为空):
api_key=sk_xxx&body=&method=GET&nonce=a1b2c3d4e5f67890&path=/api/open/v1/orders/O202607201550038000×tamp=1721450000
POST 示例:
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 参考实现
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 请求
GET /api/open/v1/orders/{order_no}
order_no:店铺订单号(由买家/店铺提供,格式类似 O202607201550038000)。
3.2 成功示例
{
"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 |
|---|---|---|
pending |
待支付 | false |
paid |
已支付 | true ← 正常可发 |
delivering |
发货中 | false |
delivered |
已交付 | false |
ship_failed |
发货失败 | true ← 可重试 |
cancelled |
已取消 | false |
3.5 错误
| HTTP / code | 含义 |
|---|---|
| 401 | 鉴权失败(Key/签名/时间/Nonce) |
| 404 | 订单号不存在 |
| 400 | 参数错误 |
4. 接口二:发货结果推送
4.1 请求
POST /api/open/v1/orders/ship-notify
Content-Type: application/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 / processing |
provider_order_no |
否 | 你们系统的发货单号 |
shipped_at |
否 | RFC3339;success 未传则用服务端时间 |
fail_reason |
否 | 失败原因(failed 时建议填) |
4.2 ship_status → 我们订单状态
| ship_status | 订单变为 | 说明 |
|---|---|---|
processing |
delivering |
已接单 / 发货中 |
success |
delivered |
发货成功 |
failed |
ship_failed |
失败,允许之后再次查询并重试 |
4.3 幂等与限制
- 订单已是
delivered,再推success→ 仍返回成功,不重复处理 - 订单已是
delivered,推failed→ 拒绝 - 订单
cancelled→ 拒绝更新
4.4 成功响应示例
{
"code": 0,
"message": "ok",
"data": {
"order_no": "O202607201550038000",
"status": "delivered",
"message": "发货成功,订单已交付"
}
}
5. 推荐调用顺序
拿到店铺订单号 order_no
↓
GET 订单查询
↓
can_ship == false ? → 停止,展示 cannot_ship_reason
↓ true
(可选)POST ship_status=processing
↓
按 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. 联系方式
接口问题、密钥申请、联调订单号请联系店铺方技术对接人。
本文档由店铺系统生成/维护,以店铺最新版本为准。