🧾《京东订单API(jd.order.detail.get)对接ERP:企业认证+OAuth授权避坑指南》(附Python源码)
直接说结论先:
京东订单类接口(
jingdong.pop.order.search/jd.order.detail.get/jingdong.etms.waybill.send)个人开发者应用无权限,必须:
企业支付宝/企业对公认证 创建「商家自用型应用」
申请订单相关接口权限(需填场景说明)
用店铺卖家账号OAuth 2.0授权获取
access_token(session参数)个人应用调会返回
403 no permission / invalid method,属正常限制。
一、现象对照表
你做的 | 返回 | 原因 |
|---|---|---|
个人应用 + |
| 个人号无订单接口权限 |
企业应用未申请接口 | 同上 403 | 控制台→API权限→申请 |
传了买家 AccessToken | 空/403 | 必须是店铺卖家 OAuth 换的 token |
沙箱调订单 | 返回 mock/空 | 沙箱不支持真实订单,仅验签名 |
session 过期 |
| 用 |
二、企业认证 + OAuth授权流程(关键!)
企业认证
JOS控制台 → 账户管理 → 企业实名(营业执照 + 企业对公/企业支付宝)
创建应用
应用类型选「商家自用型应用」(ISV需额外软服中心入驻)
申请接口权限
应用→接口权限→申请:
jingdong.pop.order.search(订单列表)jd.order.detail.get(订单明细)jingdong.etms.waybill.send(发货回填运单)jingdong.etms.trace.get(物流轨迹)
📌 场景说明示例:"ERP系统同步本店铺已付款订单生成内部销售单,并回写发货物流,仅访问授权店铺数据"
卖家OAuth授权换取 AccessToken
① 引导卖家访问: https://auth.jd.com/oauth2/toLogin.action ?response_type=code &client_id=YOUR_APP_KEY &redirect_uri=URLENCODE(你在应用配置的回调地址) &state=erp_jd ② 回调 → redirect_uri?code=xxx ③ POST https://auth.jd.com/oauth2/accessToken grant_type=authorization_code client_id=APP_KEY client_secret=APP_SECRET code=xxx redirect_uri=同上 → {access_token, refresh_token, expires_in, user_nick}此
access_token= JOS接口中的access_token(session) 参数
三、Python:订单列表 + 明细调用封装(含权限提示)
# jd_order_sync.py """ 京东订单同步 Demo(企业应用 + 卖家AccessToken) jingdong.pop.order.search → jd.order.detail.get 依赖: requests (pip install requests) """ import hashlib import json import requests import time from datetime import datetime, timedelta from typing import Dict, List # 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex class JdOrderClient: GW = "https://api.jd.com/routerjson" def __init__(self, app_key: str, app_secret: str): self.ak = app_key self.as_ = app_secret # ─── JOS MD5签名(秒级timestamp)─── def _sign(self, p: Dict) -> str: filt = sorted((k, v) for k, v in p.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, access_token: str): api_p = { "app_key": self.ak, "method": method, "timestamp": str(int(time.time())), # ← 秒级! "format": "json", "v": "2.0", "sign_method": "md5", "360buy_param_json": json.dumps(biz, ensure_ascii=False, separators=(',', ':')), "access_token": access_token } api_p["sign"] = self._sign(api_p) r = requests.post(self.GW, data=api_p, timeout=15) r.raise_for_status() d = r.json() resp_key = method.replace(".", "_") + "_response" if resp_key not in d: for k in d: if k.endswith("_response"): resp_key = k break data = d.get(resp_key, d) # 权限/业务错误检测 if isinstance(data, dict): err = data.get("error_response") or d.get("error_response") if err: code = str(err.get("code", "")) zh = err.get("zh_desc") or err.get("en_desc") if "no permission" in zh or "invalid method" in zh: raise PermissionError( "❌ 【无权限】订单接口需:\n" " 1) 企业实名商家应用\n" " 2) 已申请 jingdong.pop.order.search / jd.order.detail.get\n" " 3) access_token 须是【卖家】OAuth授权所得(非买家token)\n" f" 原始: [{code}] {zh}" ) raise Exception(f"JOS [{code}]: {zh} sub:{err.get('sub_code')}") return data # ─── 增量拉取订单列表 ─── def list_orders(self, access_token: str, minutes_back: int = 30, order_state: str = "WAIT_SELLER_STOCK_OUT", page: int = 1, page_size: int = 50) -> Dict: now = datetime.now() start = (now - timedelta(minutes=minutes_back)).strftime("%Y-%m-%d %H:%M:%S") end = now.strftime("%Y-%m-%d %H:%M:%S") return self._call( "jingdong.pop.order.search", { "start_modified": start, "end_modified": end, "order_state": order_state, # WAIT_SELLER_STOCK_OUT=已付待发 "page": page, "page_size": min(page_size, 100) }, access_token ).get("popOrderSearch", {}).get("orderSearch", {}) # ─── 订单明细 ─── def get_detail(self, access_token: str, order_id: str) -> Dict: return self._call( "jd.order.detail.get", {"orderId": order_id}, access_token ).get("orderDetail", {}).get("orderInfo", {}) # ========================================================= # 使用示例 # ========================================================= if __name__ == "__main__": client = JdOrderClient( app_key="YOUR_JD_ENTERPRISE_APP_KEY", app_secret="YOUR_JD_APP_SECRET" ) SELLER_TOKEN = "SELLER_ACCESS_TOKEN" # ← OAuth2 换取的卖家 token try: result = client.list_orders(SELLER_TOKEN, minutes_back=30) orders = result.get("orderInfoList", []) or [] total = result.get("orderTotal", 0) print(f"✅ 近30分钟变更订单: {len(orders)} / 共计{total}") for o in orders[:3]: detail = client.get_detail(SELLER_TOKEN, str(o.get("orderId"))) print(f" 单 {detail.get('orderId')} {detail.get('orderState')} " f"¥{detail.get('orderPrice')}") except PermissionError as pe: print(pe) print("\n➡ 解决:企业实名→创建自用型应用→申请订单权限→卖家OAuth授权→填入SELLER_TOKEN") except Exception as e: print("❌", e)四、OAuth Token 交换最简片段(补全用)
def jd_exchange_token(app_key, app_secret, code, redirect_uri): r = requests.post("https://auth.jd.com/oauth2/accessToken", data={ "grant_type": "authorization_code", "client_id": app_key, "client_secret": app_secret, "code": code, "redirect_uri": redirect_uri }, timeout=15) r.raise_for_status() return r.json() # access_token / refresh_token / expires_in / user_nick五、避坑清单(京东订单对接必看)
坑 | 现象 | 解决 |
|---|---|---|
个人应用调订单 | 403 no permission | 切企业实名商家自用应用 |
接口未申请 | 同上 403 | 应用→API权限→申请订单接口 |
传买家 token | 空/403 | 必须用店铺卖家 OAuth 换的 AccessToken |
token 过期 |
|
|
沙箱返回空订单 | 正常 | 沙箱只验签,用生产网关 |
ISV应用403 | 未入驻软服/未绑定店铺 | 完成 ISV 入驻并绑定授权店铺 |
timestamp 毫秒 |
| JOS用秒级 |
六、面试/方案一句话
京东订单API(
jingdong.pop.order.search/jd.order.detail.get)须企业实名商家应用 + 申请订单权限 + 卖家OAuth AccessToken(session参数);增量按start_modified/end_modified时间窗拉取防超量,遇403先确认以上三点,沙箱仅验签名不返回真实订单。
需要我补APScheduler 定时增量订单同步(断点续跑+Token自动刷新) 或京东发货回填jingdong.etms.waybill.send完整参数 吗?