×

🚚《京东物流轨迹API对接实战:无界电子面单与配送状态回调》(附Python源码)

万邦科技Lex 万邦科技Lex 发表于2026-07-06 09:56:19 浏览25 评论0

抢沙发发表评论

🚚《京东物流轨迹API对接实战:无界电子面单与配送状态回调》(附Python源码)

直接说结论:
京东物流对接分两块——
① 查轨迹jingdong.etms.trace.get(商家自发货填自己运单 / 京东仓推单自动回写运单号均可查)
② 电子面单:京东叫无界电子面单(原青龙面单),用 jingdong.etms.waybill.send/ jingdong.etms.waybill.get(预打印取号),需入驻京东无界或ISV代打
③ 状态回调:在应用→消息订阅 开启 etms.trace/ etms.delivery推送,接收京东 POST JSON
基础查询和轨迹免费额度内零费,电子面单按面单数计费(或绑定京东仓包涵)。

一、接口速览

接口
Method
用途
需token
物流轨迹查询
jingdong.etms.trace.get
按运单号查签收状态+节点
✅ seller AccessToken
发货回填运单号
jingdong.etms.waybill.send
ERP出库填京东/第三方运单号(自发货)
无界电子面单获取号
jingdong.etms.waybill.get
取京东物流电子面单号+打印数据
取消面单
jingdong.etms.waybill.cancel
取消未揽收面单
消息订阅(推送)
控制台配置
物流状态变更 → 你服务器URL
⚠️ etms.trace.get支持京东物流运单号;第三方快递(圆通/顺丰填京东订单)也可查但依赖京东已回写 logistics_code+bill_no,建议自己同时接快递100/快递鸟做第三方轨迹补全。

二、Python:轨迹查询 + 发货回填 + 推送校验

# jd_logistics_trace.py
"""
京东物流轨迹 + 发货回填运单号 + 推送验签示例
依赖: requests  (pip install requests)
"""
import hashlib
import json
import time
import requests
from typing import Dict, List, Optional
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex

# ─────────── JOS Client (内联) ───────────
class JdLogisticsClient:
    GW = "https://api.jd.com/routerjson"

    def __init__(self, ak, ask):
        self.ak, self.as_ = ak, ask

    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, biz, token):
        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": token
        }
        p["sign"] = self._sign(p)
        r = requests.post(self.GW, data=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 "error_response" in str(data):
            err = d.get(resp_key, {}).get("error_response") or d.get("error_response")
            if err:
                raise Exception(f"JOS [{err.get('code')}]: "
                                 f"{err.get('zh_desc') or err.get('en_desc')}")
            raise Exception(f"JOS err: {d}")
        return data

    # ─── ① 物流轨迹 ───
    def get_trace(self, token: str, waybill_code: str,
                  customer_code: str = None) -> Dict:
        """
        waybill_code: 京东物流运单号 (或三方快递已回填的bill_no)
        customer_code: 京东物流客户编码(可选, 部分账号需填)
        """
        biz = {"waybillCode": waybill_code}
        if customer_code:
            biz["customerCode"] = customer_code
        resp = self._call("jingdong.etms.trace.get", biz, token)
        return resp.get("etmsTraceGetResponse", {}
                       ).get("traceApiResult", {})

    # ─── ② 发货回填运单号(自发货填快递) ───
    def send_waybill(self, token: str,
                     order_id: str,
                     logistics_code: str,
                     bill_no: str,
                     logistics_name: str = None) -> bool:
        """
        用于: ERP出库后把 快递公司码+运单号 回写京东订单
        logistics_code: 京东物流公司简码 如 'YTO'(圆通) 'SF'(顺丰) 'JD'(京东物流)
        """
        biz = {
            "orderId": order_id,
            "logisticsCode": logistics_code,
            "waybillCode": bill_no
        }
        if logistics_name:
            biz["logisticsName"] = logistics_name
        r = self._call("jingdong.etms.waybill.send", biz, token)
        return r.get("etmsWaybillSendResponse", {}
                    ).get("result", False)

    # ─── ③ (可选) 无界电子面单取号 ───
    def get_waybill_no(self, token: str, pre_num: str,
                       receiver, sender, cargo_list) -> Dict:
        """
        pre_num: 预先申请的面单号段(或传 templateId 按配置)
        实际字段参考 JOS 无界面单文档 jingdong.etms.waybill.get
        """
        biz = {
            "preNum": pre_num,
            "receiver": receiver,   # dict
            "sender": sender,       # dict
            "cargoes": cargo_list   # [{"name":"商品","count":1,"weight":0.5}]
        }
        return self._call("jingdong.etms.waybill.get", biz, token)


# ─────────── 推送接收示例(Flask) ───────────
"""
京东推送 POST 到你配置的 CallbackUrl,Body 为 JSON:
{
  "eventType": "ETMS_TRACE_UPDATE",
  "data": { "waybillCode":"JDV...", "status":"SIGN", ... }
}
验签: 同 JOS _sign(),把推送所有 param(含 eventType/data) 参与签名校验
"""
# from flask import Flask, request
# app = Flask(__name__)
# client = JdLogisticsClient("AK","ASK")
#
# @app.route("/callback/jd_logistics", methods=["POST"])
# def jd_logistics_cb():
#     body = request.get_json(force=True)
#     # 验签(把 request.form/body 转 dict 再 sign 比对)
#     # 推荐: 让京东同时 Header 给 sign,或重算
#     evt = body.get("eventType")
#     data = body.get("data", {})
#     if evt == "ETMS_TRACE_UPDATE":
#         print(f"[JD推送] 运单{data.get('waybillCode')} 状态→{data.get('status')}")
#         # 回写ERP签收状态
#     return {"success":True}

=========================================================

使用示例

=========================================================

if name== "main":
cli = JdLogisticsClient("YOUR_JD_APP_KEY", "YOUR_JD_APP_SECRET")
SELLER_TOKEN = "SELLER_ACCESS_TOKEN"
try:
    # ① 查轨迹
    trace = cli.get_trace(SELLER_TOKEN, "JDV0001234567890",
                         customer_code="YOUR_JD_CUSTOM_CODE")
    nodes = trace.get("traces") or []
    print(f"✅ 轨迹节点数: {len(nodes)}")
    for n in nodes[:3]:
        print(f"   [{n.get('time')}] {n.get('context')} {n.get('city','')}")

    sign_status = trace.get("signStatus")   # SIGN=已签 UNSIGN=未签
    print("   签收状态:", sign_status)

    # ② 发货回填(自发货填三方快递)
    # ok = cli.send_waybill(SELLER_TOKEN,
    #       order_id="2318123456789000",
    #       logistics_code="YTO",
    #       bill_no="YT9876543210123",
    #       logistics_name="圆通速递")
    # print("回填运单:", ok)

except Exception as e:
    print("❌", e)
---

## 三、京东推送(消息订阅)配置步骤

1. 开放平台 → 应用 → **消息订阅** → 开启:
   - `etms.trace`(轨迹节点变更)
   - `etms.delivery`(签收/拒收)
2. 填 **CallbackUrl**(公网 HTTPS 推荐,HTTP可备)
3. 京东 POST JSON 到你的 URL,验签同 JOS `_sign()` 校验(或简单先打日志)
4. 收到 `signStatus == "SIGN"` → 调 ERP 标记订单已签收 → 入库单过账

---

## 四、常见快递公司码(logistics_code 回填用)

| 快递 | logistics_code | 说明 |
|:---|:---|:---|
| 京东物流 | `JD` | 需 customerCode 绑定 |
| 顺丰 | `SF` | — |
| 圆通 | `YTO` | — |
| 中通 | `ZTO` | — |
| 韵达 | `YD` | — |
| EMS | `EMS` | — |

> ⚠️ 第三方快递填回京东订单后,`etms.trace.get` 能否查到取决于京东是否拉取到三方轨迹,建议同时接快递100/快递鸟做三方补全。

---

## 五、避坑清单

| 坑 | 现象 | 解决 |
|:---|:---|:---|
| trace 返回空节点 | 运单号错 / 未回填 / 京东仓未推单 | 确认 `bill_no` 已通过 `etms.waybill.send` 回填订单 |
| 电子面单 403 | 未入驻无界 / 无预申请号段 | 商家后台→无界电子面单→申请号段+绑定应用 |
| 推送验签失败 | Header签参与body签参不一致 | 推送全字段(含eventType/data)按ASCII排序再签,或先日志放行验证 |
| customerCode 不知 | trace 返回 `customerCode required` | 京东物流→客户编码 在 无界后台查 |
| timestamp毫秒 | `Invalid Timestamp` | JOS用**秒级** `int(time.time())` |

---

## 六、面试/方案一句话

> 京东物流 = 订单发货后调 `jingdong.etms.waybill.send` 回填运单号 → 定时/`etms.trace`推送 查 `jingdong.etms.trace.get(waybillCode)` 获签收状态 → 无界电子面单用 `jingdong.etms.waybill.get` 取号;轨迹与推送均须卖家AccessToken,基础查询免费额度内。

需要我补 **APScheduler 定时抓未签收订单查轨迹并回写ERP** 或 **Redis原子预占+库存校正完整脚本** 吗?


群贤毕至

访客