Files
affiliate_dash/docs/开放接口-皮肤源头对接.md
T
2026-07-31 12:57:17 +08:00

318 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 店铺订单对接 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 bodyGET 用**空字符串** |
规则:
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&timestamp=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&timestamp=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 |
|--------|------|----------|
| `pending` | 待支付 | **false** |
| `paid` | 已支付 | **true** ← 正常可发 |
| `delivering` | 发货中 | false |
| `delivered` | 已交付 | false |
| `ship_failed` | 发货失败 | **true** ← 可重试 |
| `cancelled` | 已取消 | false |
### 3.5 错误
| HTTP / code | 含义 |
|-------------|------|
| 401 | 鉴权失败(Key/签名/时间/Nonce |
| 404 | 订单号不存在 |
| 400 | 参数错误 |
---
## 4. 接口二:发货结果推送
> 当前接口只接受 `success` / `failed` 两种最终结果。`processing` 是旧口径里曾出现过的中间状态,当前 `ship_notify` 不再接受;当 `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. 联系方式
接口问题、密钥申请、联调订单号请联系**店铺方技术对接人**。
本文档由店铺系统生成/维护,以店铺最新版本为准。