主流电商平台订单怎么进自建仓?我们踩过推送不 ACK、漏历史单这两坑
仓库要打单、财务要对账,平台店加私域却各出各的单。本文按真实对接顺序写:WebSocket 怎么接、为什么必须 ACK、增量拉单怎么补历史,示例对齐蜂巢开放平台文档(https://doc.fw199.com/docs/h7b/)。适合正在写订单中台的研发。
上周五下午,已经高高兴兴背上托特回家,仓库同事在群里甩了张截图被同事艾特:主流电商平台后台有一单待发,我们 ERP 里没有。财务同时在催:私域小程序单和平台店订单对不齐,不得已周末加班。
解决问题要彻底,本质上这并不是「有没有订单」的问题,是线上单怎么一比一地灌进自家系统。
现在不只是我们,很多人第一反应都是每个平台都接一遍开放平台。结果:这家一套、那家一套、授权一套、字段再映射一套——能做,但工期和合规成本直接劝退。最佳实践是:店铺授权到统一能力层,自己系统只对接一套消息 + 一套拉单 API。我们用的是蜂巢开放平台。
下面按「怎么接」写,执行层面全干货,不讲模糊的概念。
先想清楚:推送和拉取都要,别图省米二选一
对接时我们就吃过两次暗亏:
- 只接了 WebSocket:新单是实时了,商家重新授权那天,历史空窗没人补,仓库又喊漏单。
- 只靠定时任务拉单:延迟到分钟级,大促时财务说「客户都催发货了系统里才冒出来」,所以还是得实时数据同步。
文档里写得很直白:推送实时,但不推历史;拉单能补历史和漏单,但要自己调度。两边一起上。
大概长这样:
私域单本来就在你们库里,platform=private 塞进同一张表即可。平台单用 platform + seller_nick + tid 做唯一键,别只拿 tid——多店会撞。
开工前准备
- 控制台拿
AppId/AppSecret,余额别空(空了调用会莫名失败,我们排障排过一次才发现) - 商家完成店铺授权(授权说明见开放平台文档目录)
- WSS 的 token 文档写法:
MD5(AppSecret + AppId + AppSecret)
WebSocket:实时进单(别忘了 ACK)
连接:
wss://kf.fw199.com/acc?appid=YOUR_APPID&token=YOUR_TOKEN&version=v2.0&clientid=erp-order-01
String token = Utils.MD5(Config.AppSecret + Config.AppId + Config.AppSecret);
String url = "wss://kf.fw199.com/acc"
+ "?appid=" + Config.AppId
+ "&token=" + token
+ "&version=v2.0"
+ "&clientid=erp-order-01";
webSocketClient = new EasyWSClient(new URI(url));
webSocketClient.connect();
两件琐事必须做:大约 30 秒发一次 {"cmd":"beat"},以及断线重连。不做心跳,连着连着就被踢。
买家付款后常见 topic:tb_push_wait_seller_send_trade(具体 topic 以文档为准)。坑点:data 看起来像 JSON,文档说是字符串,要再 parse 一次。我们第一次直接当对象取字段,全是 null,还以为平台没推。
{
"uuid": "20201010221640251138",
"code": 0,
"msg": "success",
"topic": "tb_push_wait_seller_send_trade",
"data": "{...tid / seller_nick / status / orders...}"
}
收到带 uuid 的消息一定要回执,否则会重推、积压,严重时还影响费用:
{"cmd":"ack_sync_data","seq":"20201010221640251138"}
处理建议:先打日志(uuid、tid、seller_nick)→ 丢队列 → 尽快 ACK → 业务侧按 tid 幂等 upsert。onMessage 里别同步调打印、别同步写一长串 SQL。
@Override
public void onMessageEvent(String message) {
logger.info("ws_raw={}", message);
SyncTradeResponse resp = JSON.parseObject(message, SyncTradeResponse.class);
if (resp.getCode() != 0) {
logger.warn("biz_error={}", resp.getMsg());
return;
}
if ("tb_push_wait_seller_send_trade".equals(resp.getTopic())) {
SyncTrade trade = JSON.parseObject(resp.getData(), SyncTrade.class);
orderService.upsertFromPlatform("mall", trade);
}
if (resp.getUuid() != null && !resp.getUuid().isEmpty()) {
send("{\"cmd\":\"ack_sync_data\",\"seq\":\"" + resp.getUuid() + "\"}");
}
}
多店一般一个开发者开一条 WSS 就够,靠 seller_nick 区分。两个网站共用一个 AppId 各挂一条 WSS,消息可能只落到其中一台——生产别这么玩。
其它 topic(发货、退款、交易成功)见:https://doc.fw199.com/docs/h7b/ws-message
增量拉单:补历史、补授权空窗
推送救不了「授权前的单」。授权回调、对账、仓库喊漏单时,用增量列表按修改时间拉。注意:单次跨度 ≤ 1 天,文档还建议窗口尽量控制在 30 分钟内,成功率更高。
接口说明:https://doc.fw199.com/docs/h7b/tb-order-increment-list
常用参数:appid / timestamp / sign、tb_seller_nick(卖家登录账号,不是店铺名)、start_modified / end_modified、status(如 WAIT_SELLER_SEND_GOODS)、page_no / page_size(最大 30)、use_has_next=true。
@Test
public void getIncrementTradeList() throws Exception {
Map<String, String> data = new HashMap<>();
data.put("appid", Config.AppId);
data.put("tb_seller_nick", Config.SellerNick);
data.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
data.put("start_modified", "2026-08-20 08:00:00");
data.put("end_modified", "2026-08-20 08:30:00");
data.put("status", "WAIT_SELLER_SEND_GOODS");
data.put("page_no", "1");
data.put("page_size", "20");
data.put("use_has_next", "true");
data.put("sign", Utils.Sign(data, Config.AppSecret));
// 增量列表 URL 以开放平台文档为准
doHttpRequest(Config.TradeIncrementListUrl, data);
}
code == 0 后从 data.trades.trade[] 取。列表字段不够就按 tid 拉详情:https://doc.fw199.com/docs/h7b/trade-detail
data.put("tid", "1189348257255565830");
data.put("sign", Utils.Sign(data, Config.AppSecret));
doHttpRequest(Config.OrderDetailUrl, data);
水位怎么记:本地存 last_modified。上次到 T,下次从 T 拉到 T+Δ。授权刚回来,先补近 24~48 小时;落后太离谱(比如超 72 小时),先追最近一天再往后挪——具体频率你们按单量定。
增量的好处是:早上 8 点那单中午被改备注/改状态,你不用从凌晨重扫,从上次水位继续拉也能捞到。
表怎么建才够仓和财务用
我们落库最小集大致是:
platform, seller_nick, tid -- 唯一键三件套
status, payment, post_fee
pay_time, modified -- modified 给增量水位
buyer_message, seller_memo
raw_json -- 对账扯皮时救命
updated_at
UNIQUE(platform, seller_nick, tid)
状态映射对照推送文档里的 WAIT_SELLER_SEND_GOODS、WAIT_BUYER_CONFIRM_GOODS、TRADE_FINISHED、TRADE_CLOSED 即可。推送和拉取走同一套 upsert,不然双通道必出重复单。
漏单时我们实际怎么查
别一上来就怪平台。顺序一般是:
- 授权是否过期(Session 过了必须让商家重新登一次)
- 对应消息 topic 有没有开通
- 本地日志有没有收到这个 tid——收到没入库,是自己的 bug
- 有 uuid 有没有 ACK
- 用增量接口按修改时间窗补一把,确认平台侧有没有这单
收束
要让主流电商平台订单和私域单进同一仓库、同一财务口径:实时靠 WSS(心跳、ACK、幂等),完整靠增量/详情拉单,归集靠一张统一订单表。
字段和各平台差异以文档为准:
- https://doc.fw199.com/docs/h7b/
- https://doc.fw199.com/docs/h7b/ws-message
- https://doc.fw199.com/docs/h7b/tb-order-increment-list
- https://doc.fw199.com/docs/h7b/trade-detail
- https://github.com/CheeliAI/zeroone-opensdk
- https://console.fw199.com
你那边若卡在「推送有、库没有」或「授权后空窗」,评论区丢一下现象,我按排查顺序帮你对一下。