Files
affiliate_dash/docs/开放接口-皮肤源头对接.md
T
yml2213 e4d0a71963 支持 .env 配置,并完善源头对接文档与测试订单
- 增加 .env.example 与 godotenv 加载,start.sh 自动读环境变量
- 重写发给源头的开放接口对接文档
- 订单页支持创建测试订单(可直接已支付并复制店铺订单号)
2026-07-20 16:19:57 +08:00

285 lines
7.7 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://api.example.com`(开发:`http://主机:8080` |
| **X-Api-Key** | 由店铺方分配,如 `sk_xxxx` |
| **api_secret** | 由店铺方单独发送,**只用于本地算签名,不要写在 URL/Header** |
| **时间偏差** | 默认允许 ±300 秒 |
统一响应格式:
```json
{
"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 签名字符串(6 行,用 `\n` 连接)
```text
{api_key}
{timestamp}
{nonce}
{METHOD}
{path}
{body}
```
| 字段 | 规则 |
|------|------|
| METHOD | 大写:`GET` / `POST` |
| path | 仅路径,**不要**域名、**不要** query。例:`/api/open/v1/orders/O123` |
| body | 原始 HTTP body 字符串;GET 用**空字符串** |
| X-Sign | `hex( HMAC-SHA256( api_secret, 签名字符串 ) )`**小写**十六进制 |
### 2.2 Python 参考实现
```python
import hmac, hashlib, time, uuid, requests
API_KEY = "请替换为店铺下发的 key"
API_SECRET = "请替换为店铺下发的 secret"
BASE = "https://api.example.com" # 请替换
def sign_headers(method: str, path: str, body: str = "") -> dict:
ts = str(int(time.time()))
nonce = uuid.uuid4().hex
raw = "\n".join([API_KEY, ts, nonce, method.upper(), 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 时 `json.dumps` 后的字符串要原样发送,不要一边签名一边再改空格/字段顺序。
- 签名失败常见原因: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. 接口二:发货结果推送
### 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` / `processing` |
| `provider_order_no` | 否 | 你们系统的发货单号 |
| `shipped_at` | 否 | RFC3339success 未传则用服务端时间 |
| `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 成功响应示例
```json
{
"code": 0,
"message": "ok",
"data": {
"order_no": "O202607201550038000",
"status": "delivered",
"message": "发货成功,订单已交付"
}
}
```
---
## 5. 推荐调用顺序
```text
拿到店铺订单号 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. 联系方式
接口问题、密钥申请、联调订单号请联系**店铺方技术对接人**。
本文档由店铺系统生成/维护,以店铺最新版本为准。