订单与履约

从下单到签收的完整生命周期,以及你该如何向用户转述。

订单状态机

created ──▶ pending_payment ──▶ paid ──▶ order_pushed ──▶ shipping ──▶ shipped ──▶ completed
                │ (30min未付)       │          │  商家仓备货/发货    物流运输       签收
                ▼                  │          ▼
              closed               │      push_blocked(平台风控拦截,见下)
                                   ▼
                              refunding ──▶ refunded ──▶ closed(售后驱动)

给用户的人话对照(推荐转述口径)

status 对用户说
pending_payment 待支付(附二维码/跳转)
paid 支付成功,正在通知商家发货
order_pushed 商家已接单,备货中
shipping 商家仓打包发货中
shipped 已发货,运单号 XXX(附轨迹查询)
completed 已签收
refunded 退款已原路退回

NOTE push_blocked 是平台风控状态(如亏损拦截、商家预存余额不足),平台会在后台处理或走售后兜底——不要向用户转述这个内部状态,用户侧体验是"发货稍有延迟"。

事件时间线

get_order_status 响应中的 events 是订单的事实记录,每条含 event / payload / created_at:

{ "events": [
  { "event": "paid", "created_at": "..." },
  { "event": "merchant_accepted", "created_at": "..." },
  { "event": "shipped", "payload": { "waybill_no": "7830xxxxxx", "express_co": "ZTO" }, "created_at": "..." },
  { "event": "delivered", "created_at": "..." }
] }

可订阅的事件:created / paid / push_blocked / merchant_accepted / shipped / delivered / refund_initiated / refunded / closed。

物流轨迹

订单进入 shipped 后查询全程轨迹:

curl
curl "https://api.yuantoutao.com/v1/orders/YM20261003100003/logistics" \
  -H "Authorization: Bearer $YUANTOUTAO_API_KEY"
node
const tr = await (await fetch(`${API}/orders/${orderNo}/logistics`, {
  headers: { Authorization: `Bearer ${KEY}` }
})).json();
console.log(tr.express_co, tr.waybill_no);
tr.traces.forEach(t => console.log(t.time, t.context));
python
tr = requests.get(f"{API}/orders/{order_no}/logistics",
                  headers={'Authorization': f'Bearer {KEY}'}).json()
print(tr['express_co'], tr['waybill_no'])
for t in tr['traces']: print(t['time'], t['context'])

WARNING 未发货订单调用物流接口返回 INVALID_PARAM——先看 get_order_status 里的 shipped 事件再查轨迹。

幂等:重试的正确姿势

POST /orders 必须带 Idempotency-Key。网络超时/响应丢失时,用同一个 key 原样重试——平台保证返回同一订单(duplicate:true),绝不重复扣单。每次新的用户意图才用新 key。

node
async function createOrderWithRetry(body, idem, tries = 3) {
  for (let i = 0; i < tries; i++) {
    try { return await callCreateOrder(body, idem); }
    catch (e) { if (i === tries - 1) throw e; await new Promise(r => setTimeout(r, 1000 * 2 ** i)); }
  }
}
python
def create_order_with_retry(body, idem, tries=3):
    for i in range(tries):
        try:
            return call_create_order(body, idem)
        except Exception:
            if i == tries - 1: raise
            time.sleep(2 ** i)

库存与价格的有效性