# 皮肤源头开放接口对接文档 版本:`v1` Base URL:`https://{你们的域名}`(开发环境示例:`http://localhost:8080`) 统一响应: ```json { "code": 0, "message": "ok", "data": {} } ``` - `code = 0` 表示成功 - `code != 0` 表示失败,以 `message` 为准 --- ## 1. 鉴权(ApiKey + 签名) 开放接口采用 **双因子**: | 凭证 | 说明 | |------|------| | `api_key` | 标识对接方,放在请求头 `X-Api-Key` | | `api_secret` | **仅用于本地算签名,禁止放在 Header / 前端 / 日志明文传输** | 每次请求必须带齐: | Header | 必填 | 说明 | |--------|------|------| | `X-Api-Key` | 是 | 对接方 Key | | `X-Timestamp` | 是 | Unix **秒**级时间戳 | | `X-Nonce` | 是 | 随机串,长度 8~64,同一 Key 下有效期内不可重复 | | `X-Sign` | 是 | HMAC-SHA256 签名,小写十六进制 | ### 1.1 签名算法 待签名字符串(UTF-8,行之间用 `\n`,共 6 行): ```text {api_key} {timestamp} {nonce} {METHOD} {path} {body} ``` | 项 | 规则 | |----|------| | METHOD | 大写,如 `GET` / `POST` | | path | `URL.Path`,**不含** query,如 `/api/open/v1/orders/O123` | | body | 原始请求体字符串;GET 无 body 时用 **空字符串**(仍保留最后一行空内容,即末尾仍有换行结构中的 body 段为空) | 计算: ```text X-Sign = hex( HMAC-SHA256( api_secret, string_to_sign ) ) ``` - 使用 **小写** hex - 时间戳与服务器偏差超过 **300 秒**(可配置)则拒绝 - 同一 `api_key + nonce` 在有效期内重复使用 → 拒绝(防重放) ### 1.2 Python 示例 ```python import hmac, hashlib, time, uuid, requests API_KEY = "sk_source_dev_key_change_me" API_SECRET = "sk_source_dev_secret_change_me" BASE = "http://localhost:8080" def sign(method: str, path: str, body: str = "") -> dict: ts = str(int(time.time())) nonce = uuid.uuid4().hex string_to_sign = "\n".join([API_KEY, ts, nonce, method.upper(), path, body]) sign_hex = hmac.new( API_SECRET.encode(), string_to_sign.encode(), hashlib.sha256 ).hexdigest() return { "X-Api-Key": API_KEY, "X-Timestamp": ts, "X-Nonce": nonce, "X-Sign": sign_hex, } # 查询订单 path = "/api/open/v1/orders/O202607201550038000" r = requests.get(BASE + path, headers=sign("GET", path)) print(r.json()) # 发货推送 path = "/api/open/v1/orders/ship-notify" body = '{"order_no":"O202607201550038000","ship_status":"success","provider_order_no":"SRC001"}' headers = {"Content-Type": "application/json", **sign("POST", path, body)} r = requests.post(BASE + path, headers=headers, data=body.encode()) print(r.json()) ``` > **注意**:POST 签名用的 `body` 必须与实际发送的 body **字节级一致**(不要先 `json.dumps` 又改空格)。 ### 1.3 鉴权失败示例 ```json { "code": 401, "message": "签名校验失败" } ``` 常见原因:secret 错误、body 不一致、timestamp 过期、nonce 重复、path 写错(多了 query / 域名)。 ### 1.4 环境变量(我方) | 变量 | 说明 | 开发默认 | |------|------|----------| | `OPEN_API_KEY` | Api Key | `sk_source_dev_key_change_me` | | `OPEN_API_SECRET` | 签名密钥 | `sk_source_dev_secret_change_me` | | `OPEN_SIGN_SKEW` | 时间偏差秒数 | `300` | 生产务必更换 Key / Secret。 --- ## 2. 对接流程 ```text 1. 买家在店铺下单并完成支付 2. 上游拿到「店铺订单号 order_no」 3. 调用【订单查询】确认商品 sku、can_ship 4. can_ship=true 时执行发货 5. 发货结果调用【发货推送】回传 6. 我方同步订单状态;重复推送 success 幂等成功 ``` --- ## 3. 订单查询(发货前置) ### 请求 ```http GET /api/open/v1/orders/{order_no} X-Api-Key: {api_key} X-Timestamp: {unix_seconds} X-Nonce: {random_8_to_64} X-Sign: {hmac_sha256_hex} ``` ### 成功响应 ```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": "" } } ``` ### 字段说明 | 字段 | 类型 | 说明 | |------|------|------| | order_no | string | 店铺订单号 | | status | string | 订单状态,见下表 | | can_ship | bool | **是否允许发货**,请以此为准 | | cannot_ship_reason | string | 不可发货原因(can_ship=false 时) | | product.name | string | 商品中文名 | | product.sku | string | **商品英文固定标识,发货请用此字段** | | product.game | string | 游戏,如「和平精英」 | | buyer_name | string | 买家备注名 | | amount | number | 订单金额 | | provider_order_no | string | 上游单号(若已回传) | | created_at | string | 下单时间 | | shipped_at | string\|null | 发货成功时间 | | ship_fail_reason | string | 最近一次发货失败原因 | ### 订单状态 status | 值 | 含义 | can_ship | |----|------|----------| | pending | 待支付 | false | | paid | 已支付 | **true** | | delivering | 发货中 | false | | delivered | 已交付 | false | | ship_failed | 发货失败(可重试) | **true** | | cancelled | 已取消 | false | ### 错误 | HTTP | message | |------|---------| | 401 | 鉴权失败(Key / 签名 / 时间 / Nonce) | | 404 | 订单不存在 | | 400 | 订单号不能为空 | ### 调用说明 请使用上一节 Python 示例生成签名后请求;仅带 `X-Api-Key` 而不带签名会被拒绝。 --- ## 4. 发货结果推送 ### 请求 ```http POST /api/open/v1/orders/ship-notify Content-Type: application/json X-Api-Key: {api_key} X-Timestamp: {unix_seconds} X-Nonce: {random_8_to_64} X-Sign: {hmac_sha256_hex} ``` ```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 时建议填写) | ### ship_status 与订单状态映射 | ship_status | 订单变为 | 说明 | |-------------|---------|------| | processing | delivering | 已接单/发货中 | | success | delivered | 发货成功 | | failed | ship_failed | 发货失败,允许再次查询后重试 | ### 幂等 - 订单已是 `delivered`,再次推送 `success` → **返回成功**,不重复处理 - 订单已是 `delivered`,推送 `failed` → 拒绝 - 订单 `cancelled` → 拒绝更新 ### 成功响应 ```json { "code": 0, "message": "ok", "data": { "order_no": "O202607201550038000", "status": "delivered", "message": "发货成功,订单已交付" } } ``` ### 调用说明 Body 与签名字符串中的 body **必须完全一致**。完整示例见 **§1.2 Python**。 | ship_status | body 示例 | |-------------|-----------| | processing | `{"order_no":"O...","ship_status":"processing","provider_order_no":"SRC..."}` | | success | `{"order_no":"O...","ship_status":"success","provider_order_no":"SRC...","shipped_at":"2026-07-20T16:00:00+08:00"}` | | failed | `{"order_no":"O...","ship_status":"failed","fail_reason":"账号不存在"}` | --- ## 5. 推荐调用顺序 ```text query → can_ship? ├─ false → 停止,展示 cannot_ship_reason └─ true →(可选)push processing → 执行发货 → push success / failed ``` 发货务必使用查询结果中的 **`product.sku`**,不要仅依赖中文名。 --- ## 6. 联调检查清单 - [ ] 缺少 Sign / 错误 Secret 返回 401 - [ ] 错误 ApiKey 返回 401 - [ ] 过期 Timestamp / 重复 Nonce 返回 401 - [ ] 不存在的订单号返回 404 - [ ] `pending` 订单 `can_ship=false` - [ ] `paid` 订单 `can_ship=true` 且 sku 正确 - [ ] success 推送后状态为 delivered - [ ] 重复 success 幂等成功 - [ ] failed 后可再次 query 且 can_ship=true --- ## 7. 联系方式 接口问题、密钥申请、联调环境请联系店铺方技术对接人。 商品英文 sku 对照见:`docs/商品英文名对照.md`