×

🏢【企业级开发】1688采购系统API对接实战(含OAuth 2.0授权完整流程|附Python源码)

万邦科技Lex 万邦科技Lex 发表于2026-06-17 09:19:38 浏览23 评论0

抢沙发发表评论

🏢【企业级开发】1688采购系统API对接实战(含OAuth 2.0授权完整流程|附Python源码)

1688采购系统对接与普通商品搜索最大的区别是:订单、物流、购物车、确认收货等接口必须携带买家身份(Access Token),而这个 Token 需要通过 OAuth 2.0 授权码模式 换取。下面给你生产可用的完整链路。

一、1688 OAuth 2.0 授权整体流程

┌─────────┐   ① 拼接授权URL(带redirect_uri)   ┌────────────┐
│ 你的ERP │ ────────────────────────────────▶ │ 1688登录页  │
└─────────┘                                   └────┬─────────┘
                                              用户登录并同意授权
                                                   │ ② 跳回 redirect_uri?code=xxx
                                                   ▼
                                          ┌────────────────┐
                                          │ 你的回调Server  │
                                          └────┬───────────┘
                                              ③ 用 code 换 access_token / refresh_token
                                              ④ 存DB,请求API时带 access_token
⚠️ 应用类型必须为「自用型应用」,回调地址须在控制台 → 应用详情 → OAuth配置 中提前登记。

二、Python完整实现:OAuth授权 + Token管理 + 采购API调用

# ali1688_oauth_client.py
import hashlib
import time
import requests
import urllib.parse
from datetime import datetime, timedelta
from typing import Dict, Optional
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
# ────────────────────────────────────────────────────────
# 1. OAuth 2.0 授权助手
# ────────────────────────────────────────────────────────
class Ali1688OAuth:
    """
    1688 OAuth2.0 Authorization Code Flow
    文档参考: https://open.1688.com/doc/auth/oauth2.html
    """
    AUTH_URL = "https://auth.1688.com/oauth/authorize"
    TOKEN_URL = "https://gw.open.1688.com/openapi/http/1/auth/token"

    def __init__(self, app_key: str, app_secret: str, redirect_uri: str):
        self.app_key = app_key
        self.app_secret = app_secret
        self.redirect_uri = redirect_uri

    def get_authorize_url(self, state: str = "erp_1688") -> str:
        """① 生成用户点击的授权URL(需在1688控制台登记 redirect_uri)"""
        params = {
            "client_id": self.app_key,
            "redirect_uri": self.redirect_uri,
            "response_type": "code",
            "state": state
        }
        return self.AUTH_URL + "?" + urllib.parse.urlencode(params)

    def exchange_token(self, code: str) -> Dict:
        """
        ③ 用授权码换 access_token / refresh_token
        返回示例:
        {
          "access_token": "xxx",
          "refresh_token": "yyy",
          "expires_in": 3600 * 24 * 365,   # 通常1年
          "refresh_token_expires_in": 3600 * 24 * 365 * 3
        }
        """
        data = {
            "grant_type": "authorization_code",
            "client_id": self.app_key,
            "client_secret": self.app_secret,
            "code": code,
            "redirect_uri": self.redirect_uri
        }
        resp = requests.post(self.TOKEN_URL, data=data, timeout=15)
        resp.raise_for_status()
        result = resp.json()
        if result.get("error_code"):
            raise Exception(f"OAuth失败: {result}")
        return result

    def refresh_access_token(self, refresh_token: str) -> Dict:
        """用 refresh_token 续期(建议提前7天检测并刷新)"""
        data = {
            "grant_type": "refresh_token",
            "client_id": self.app_key,
            "client_secret": self.app_secret,
            "refresh_token": refresh_token
        }
        resp = requests.post(self.TOKEN_URL, data=data, timeout=15)
        resp.raise_for_status()
        return resp.json()


# ────────────────────────────────────────────────────────
# 2. 带 OAuth Token 的 1688 采购 API Client
# ────────────────────────────────────────────────────────
class Ali1688PurchaseClient:
    """采购相关接口(需 AccessToken):订单列表/详情/确认收货)"""
    GW = "https://gw.open.1688.com/openapi/http/2/1"

    def __init__(self, app_key: str, app_secret: str, access_token: str):
        self.ak = app_key
        self.as_ = app_secret
        self.tk = access_token

    # ─── 签名 ───
    def _sign(self, params: Dict) -> str:
        filt = sorted((k, v) for k, v in params.items()
                      if v is not None and str(v).strip() != '' and k != 'sign')
        qs = ''.join(f"{k}{v}" for k, v in filt)
        return hashlib.md5(f"{self.as_}{qs}{self.as_}".encode()).hexdigest().upper()

    def _call(self, method: str, biz: Dict) -> Dict:
        api_p = {
            "method": method, "app_key": self.ak, "session": self.tk,
            "timestamp": str(int(time.time() * 1000)),
            "format": "json", "v": "2.0", "sign_method": "md5"
        }
        api_p["param2"] = urllib.parse.quote_plus(str(biz).replace("'", '"'))
        api_p["sign"] = self._sign(api_p)

        r = requests.get(self.GW, params=api_p, timeout=15)
        r.raise_for_status()
        d = r.json()
        if "error_response" in d:
            err = d["error_response"]
            raise Exception(f"1688 [{err.get('code')}]: {err.get('msg')}")
        return d.get(list(d.keys() - {"error_response"})[0], {})

    # ─── 采购订单列表 ───
    def list_purchase_orders(self, status="waitsellersend",
                              hours_back=72, page=1, sz=50) -> list:
        from datetime import timedelta
        biz = {
            "orderStatus": status,
            "gmtCreateStart": (datetime.now() - timedelta(hours=hours_back))
                               .strftime("%Y-%m-%d %H:%M:%S"),
            "gmtCreateEnd": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "pageNo": page, "pageSize": sz
        }
        res = self._call("alibaba.trade.buyer.list", biz)
        return res.get("alibaba_trade_buyer_list_response", {}
                      ).get("tradeModelList", []) or []

    # ─── 订单明细 ───
    def get_order_detail(self, order_id: str) -> Dict:
        biz = {"orderId": str(order_id)}
        res = self._call("alibaba.trade.get", biz)
        return res.get("result", {})

    # ─── 确认收货(入库后调) ───
    def confirm_receive(self, order_id: str, sub_ids: list = None) -> bool:
        biz = {"orderId": str(order_id)}
        if sub_ids:
            biz["subOrderIds"] = ",".join(map(str, sub_ids))
        res = self._call("alibaba.trade.confirmReceive", biz)
        return res.get("alibaba_trade_confirmreceive_response", {}
                       ).get("result", False)


# =============================================================
# 使用示例
# =============================================================
if __name__ == "__main__":
    APP_KEY = "YOUR_APP_KEY"
    APP_SECRET = "YOUR_APP_SECRET"
    REDIRECT_URI = "https://your-erp.com/callback/ali1688"

    oauth = Ali1688OAuth(APP_KEY, APP_SECRET, REDIRECT_URI)

    # ====== 第一步:让用户浏览器访问此URL并登录授权 ======
    auth_url = oauth.get_authorize_url()
    print("🔗 请浏览器访问此URL完成授权:")
    print(auth_url)
    print("\n授权后 1688 会跳转到:")
    print(f"  {REDIRECT_URI}?code=XXXXXX&state=erp_1688")
    print("-" * 50)

    # ====== 第二步:在你的 Web 回调接口中取出 code 换 token ======
    # 下面模拟你拿到 code(实际从 request.args['code'] 取)
    demo_code = input("粘贴回调返回的 code: ").strip()
    if demo_code:
        token_info = oauth.exchange_token(demo_code)
        access_token = token_info["access_token"]
        refresh_token = token_info.get("refresh_token")
        expires_at = datetime.now() + timedelta(seconds=int(token_info.get("expires_in", 31536000)))
        print(f"\n✅ AccessToken 获取成功!")
        print(f"   access_token = {access_token[:20]}...")
        print(f"   有效期至(本地估算): {expires_at.strftime('%Y-%m-%d')}")
        print(f"   refresh_token 请存DB,用于自动续期")

        # ====== 第三步:用 token 调采购API ======
        client = Ali1688PurchaseClient(APP_KEY, APP_SECRET, access_token)
        orders = client.list_purchase_orders(status="waitsellersend", hours_back=48)
        print(f"\n📋 待发货采购单: {len(orders)} 笔")
        for o in orders[:3]:
            print(f"   1688单号:{o.get('id')}  金额:{o.get('totalAmount')}")

        # 示例:确认收货(谨慎!会真正确认)
        # client.confirm_receive("2338123456789000")

三、Web回调(Flask最小示例)

# callback_server.py  (需 pip install flask)
from flask import Flask, request
from ali1688_oauth_client import Ali1688OAuth

app = Flask(__name__)

OAUTH = Ali1688OAuth("YOUR_APP_KEY", "YOUR_APP_SECRET", "https://your-erp.com/callback/ali1688")

@app.route("/callback/ali1688")
def ali1688_callback():
    code = request.args.get("code")
    state = request.args.get("state")
    if not code:
        return "授权失败:未获取到code", 400

    token_info = OAUTH.exchange_token(code)
    # ★ 存入数据库(user_id ↔ access_token / refresh_token / expire_time)
    print("✅ Token已获取,请存入DB:", token_info.get("access_token")[:20])

    return f"<h3>1688授权成功!state={state}</h3><p>Token已保存,可关闭此页。</p>"

if __name__ == "__main__":
    app.run(port=8000, debug=True)

四、Token 管理生产建议

要点
做法
存储
DB存 access_token / refresh_token / expires_at,关联内部员工/店铺
提前刷新
定时任务每天扫,距过期 < 7天 调 refresh_access_token()更新
多账号
一个1688账号 = 一个 AccessToken,ERP支持绑定多供方账号
安全
AppSecret/ refresh_token加密存储(AES),不在日志打印
重试
API 调失败且返回 token 过期码 → 自动刷新并重试一次

五、完整采购对接链路(面试叙述版)

员工登录ERP → 点击「绑定1688账号」
  → 跳转1688 OAuth授权页(get_authorize_url)
  → 回调拿到 code → exchange_token → 存DB

每日定时任务:
  用 access_token 调 alibaba.trade.buyer.list(waitsellersend)
  → 创建/更新内部采购单
  → 展示物流单号(alibaba.logistics.trade.ship)
  → 仓库入库后调 alibaba.trade.confirmReceive
需要我补 采购下单接口 alibaba.trade.create完整参数封装(含SKU ID/收货地址)APScheduler定时同步 + Token自动刷新脚本 吗?


群贤毕至

访客