旺店通WMS对接全流程:订单、库存与状态回传落地指南 简介旺店通WMS对接流程代码包是一份面向供应链与物流领域软件开发工程师的完整对接方案重点解决企业系统与旺店通WMS之间的WebAPI接口集成问题。包内涵盖接口地址说明、销售出库单查询接口、接口调用规范以及标准定制接口的使用方法并提供了基于C#与HttpClient的完整POST请求、签名构建和URL参数处理示例帮助开发者从原理到实现快速贯通。压缩包大小为380KB共32个文件以C#源码.cs、DLL依赖库、JSON配置和文本说明为主要类型另含Python辅助脚本与项目工程文件便于对照编译和二次开发。目前已有183人学习浏览。资源特别附有具体调用示例详细列出了请求地址、方法、密钥及参数设置等关键信息可直接作为模板套用有效降低对接门槛加快项目落地。1. 旺店通WMS对接流程订单、库存和状态回传一张图看懂的落地路径WMS 对接听起来是个接口活儿实际上一半以上的项目都耗在数据口径、状态回传和重复单这三件事上。旺店通WMS对接流程本质上就是你的自研系统或第三方 ERP通过对外开放接口把货品档案、库存余额、出入库指令和回传状态这四类数据和 WMS 内部作业链路打通。你手上的单据在 WMS 里经过收货、上架、拣货、复核、出库之后对应的状态还要能回到你的系统让订单、账务和库存有个唯一的答案。这篇文章适合谁正在接旺店通WMS的 Java/后端开发被“同步失败”“回调丢失”“库存对不上”折磨到想换业务的对接负责人以及准备把仓储切换成旺店通WMS、正做技术评估的架构师。它的价值很简单照着链路走你知道先调哪个接口、后调哪个接口踩进坑里的时候知道去看哪张表、哪一个字段。2. 对接前先理清 WMS 开放接口的三种边界签名、数据模型和流程方向2.1 签名与授权MD5 签名和 session 是怎么凑出来的旺店通WMS开放接口的调用方式与市面上大多数电商开放平台类似。每个请求要带上一组公共参数app_key 标识你是哪个应用timestamp 是当前毫秒时间戳session 是这个应用在某商家账号下的授权令牌sign 则是把所有业务参数和 app_secret 按规则拼接后做 MD5 得到的签名。签名不通过服务端直接拒绝连业务参数都不会解析。这里有个容易被忽略的细节拼接参数时 key 要按字母序排序value 不需要 URL 编码但 secret 在拼接时放在参数串两端。用 Python 写一个签名函数大概长这样import hashlib import time import requests def make_sign(params: dict, app_secret: str) - str: # 参数按 key 的字典序排序拼成 keyvalue 形式 sorted_keys sorted(params.keys()) raw app_secret .join(f{k}{params[k]} for k in sorted_keys) app_secret return hashlib.md5(raw.encode(utf-8)).hexdigest().upper() def build_request(method: str, biz_params: dict, app_key: str, app_secret: str, session: str): params { method: method, app_key: app_key, session: session, timestamp: str(int(time.time() * 1000)), format: json, v: 1.0, } params.update(biz_params) params[sign] make_sign(params, app_secret) return params # 示例请求货品查询 resp requests.post( https://open-api.example.com/gateway, # 以实际网关地址为准 databuild_request( wms.goods.query, # 货品查询方法名各版本略有差异 {goods_no: SKU001, page_size: 20}, your_app_key, your_app_secret, your_session_token ), timeout10, )这段代码的核心在于make_sign它把所有公共参数和业务参数放在同一个字典里排序签名覆盖的是全部参数。不要漏掉毫秒时间戳这一点曾经有人把 timestamp 写成了秒级服务端返回“时间戳失效”排查了大半天。app_secret 和 session 是两回事app_secret 是你的应用密钥session 是某个具体店铺的授权一个 app 可以同时管理多家店铺换店铺只需要换 session不需要换密钥。生产环境里这两个值建议放在配置中心或环境变量里别写死在代码仓库里泄露的后果比接口限流严重得多。2.2 数据模型边界货品、SKU、仓库、单据类型怎么映射旺店通WMS的数据模型核心是“货品 仓库 单据”跟 ERP 里的物料档案、组织架构、业务单据有许多类似之处但又不完全一致。对接前务必要画一张映射表把两边系统里的概念放在一起对齐。WMS 侧的货品档案通常包含货品编号、货品名称、条码、规格、单位而你的 ERP 里可能有主 SKU、子 SKU、辅助单位、自定义属性。常见的映射错误是拿“条码”当“货品编号”传WMS 扫不到对应的主档导致入库单创建失败。库存相关的几个概念也要提前分清可用库存、在途库存、冻结库存、残次品库存WMS 接口里返回的每个数量字段都要明确对应关系不然你做出来的库存同步报表永远跟仓里实际点数对不上。仓库维度上WMS 的仓库编码warehouse_no是核心关联键你的 ERP 里可能同时有销售仓、备用仓、虚拟仓。对接时只需要映射实体仓虚拟仓不要下发到 WMS否则会造成“虚拟仓库存被扣减”这类难排查的账实差异。单据类型上WMS 侧主要分入库单、出库单、调拨单、盘点单每种单在接口里的方法名和状态机都不一样。比如出库单可能有“待审核、已审核、拣货中、已出库、已取消”这些节点你的系统在映射时要把“WMS 已出库”翻译成自己业务里的“已发货”中间不要自作聪明地加多余的中间态会增加对账成本。这步做得好后面写状态回传逻辑时基本不会改结构。2.3 流程边界先拉后推还是先推后拉同步还是异步接口流程设计上最常见的链路是“先拉后推”每天早上或每隔一段时间先从 WMS 拉一次货品和库存到本地作为基线数据然后本地系统把创建好的单据推送给 WMSWMS 作业完成后再通过查询或回调把状态拉/推回来。这种做法最稳因为库存和货品总是以 WMS 为准单证状态以本地为准。另一个方向是“纯推模式”完全依赖回调回调丢失就补单适合单据量不大但实时性要求高的场景。我一般建议物流单量在每天几千单以下的企业从“先拉后推”起步先把同步链路理顺再逐步加大实时性比例。同步还是异步这个问题取决于你的业务容忍度。库存查询必须同步因为界面上要即时展示可售数出库单下发可以用异步本地先把单存起来后台任务推送失败自动重试状态回传则必须是异步的——WMS 不可能等你处理完才继续走下一步。对接初期把这三类请求的线程池分开避免一个慢接口拖垮整个系统。3. 搭一个最小可用的对接链路从查货品到下发一张出库单3.1 先用一个通用 Client 把签名、请求、重试封装好把签名逻辑和 HTTP 请求封装成一个统一入口是这一节最值得做的事情。不要在每个业务方法里重复写签名更不要在代码里散落一堆 requests.post。封装之后后面接任何新接口都只是加一个方法的问题。一个可用的 Client 大致如下class WmsClient: def __init__(self, app_key, app_secret, session, gateway, timeout10): self.app_key app_key self.app_secret app_secret self.session session self.gateway gateway self.timeout timeout def call(self, method, biz_params, retry3): params { method: method, app_key: self.app_key, session: self.session, timestamp: str(int(time.time() * 1000)), format: json, v: 1.0, } params.update(biz_params) params[sign] make_sign(params, self.app_secret) for attempt in range(retry): try: resp requests.post(self.gateway, dataparams, timeoutself.timeout) data resp.json() # 业务失败时错误码通常在返回体里的 code/error_code 字段 if str(data.get(code, 0)) ! 0: # 如果是签名错误或限流值得重试业务参数错误不重试 if data.get(code) in (sign_error, rate_limit): time.sleep(1 * (attempt 1)) continue raise RuntimeError(f接口业务错误: {data}) return data.get(data) or data except requests.Timeout: if attempt retry - 1: raise time.sleep(1 * (attempt 1)) raise RuntimeError(重试耗尽)这段封装重点在两个地方一是签名每次都实时算不要缓存时间戳一变签名就变二是重试策略只对网络超时和服务端明确返回的限流/签名错误进行业务参数错误比如 SKU 不存在重试一万次都没用反而会把错误数据留在中间表里。实际对接时建议在 Client 外加一层本地日志把每次调用的 method、参数摘要、返回码和耗时记录到文件或日志系统上线初期这份日志是你排查对账差异的唯一线索。3.2 查货品和查库存注意分页和字段过滤别一把梭最小链路的第一步是确认 WMS 侧货品档案和你的 SKU 对得上。常见的做法是先调用货品查询接口拉最近更新的货品再逐个核对是否存在。下面是一个带分页的写法def fetch_all_goods(client: WmsClient, page_size100): all_goods [] page_no 1 while True: data client.call(wms.goods.query, { page_no: page_no, page_size: page_size, modified_begin: 2024-01-01 00:00:00, # 增量拉取减少数据量 }) goods_list data.get(goods_list, []) if data else [] all_goods.extend(goods_list) total data.get(total, 0) if data else 0 if len(all_goods) total or not goods_list: break page_no 1 return all_goods分页参数各家命名可能有差异但“用页大小控制单次返回条数、用总数判断是否拉完”这个套路是通用的。这里有个效率陷阱每天全量拉一遍几百个 SKU 没问题但几十万个 SKU 全量拉就会很慢还容易触发服务端的限流。建议日常只增量拉最近 30 分钟或 1 小时内有变更的货品全量拉取放到凌晨低峰期。库存查询接口要注意区分“按货品查”和“按仓库查”库存返回的是多仓库结构时本地要按“货品编号 仓库编码”作为唯一键落库这样才能跟后面的出库单校验对上。3.3 下发一张出库单字段映射和编码规则必须在代码里写死下发单据是整个对接的核心动作。一张出库单通常包含单号、仓库、收货人信息、明细行货品编号、数量这几块。重点在于单号的生成规则一定要保证你的系统单号在 WMS 侧全局唯一推荐用平台编码 业务类型 自增序列来拼比如FW20240220001不要直接拿数据库自增 ID 上去WMS 那边没法解释“为什么这个 ID 这么小”。def send_outbound_order(client: WmsClient, order: dict): biz_params { method: wms.outbound.order.create, out_order_no: order[out_order_no], # 唯一单号 warehouse_no: order[warehouse_no], # 仓库编码必须映射过 shop_no: order.get(shop_no, ), # 店铺编码选填 logistics_code: order.get(logistics_code, ), # 物流公司编码 is_auto_check: 1, # 1单据下发后自动审核 receiver_name: order[receiver_name], receiver_mobile: order[receiver_mobile], receiver_phone: order.get(receiver_phone, ), receiver_province: order[receiver_province], receiver_city: order[receiver_city], receiver_district: order[receiver_district], receiver_address: order[receiver_address], detail_list: order[detail_list], # 明细, 包含 goods_no, qty } data client.call(wms.outbound.order.create, biz_params) # 返回的 wms_order_no 是 WMS 侧的单号要落库保存 return data.get(wms_order_no)detail_list 里每一项至少要有货品编号和数量。数量字段要特别注意WMS 的数量一般按最小单位来如果你的系统里订单卖的是“箱”而 WMS 主档里货品单位是“件”下发前必须做单位换算否则会出现“客户买了一箱仓里发了一件”的事故。is_auto_check 这个参数看你的业务要不要 WMS 自动审核一般来说电商订单希望自动审B2B 的订单希望对单后人工审。单据创建成功后返回的 wms_order_no 一定要记下来它就是后续查状态、对账、做逆向的主键。4. 回传与异步通知把 WMS 的状态变成自己系统的状态4.1 主动轮询和 Webhook 回调怎么选数据量决定答案单据下发到 WMS 之后最关心的问题是“拣了没、发了没、签收了没”这就要靠状态回传了。两种方式各有适用场景。主动轮询的做法是本地保存所有待确认的单号每隔几分钟调用一次状态查询接口把最新状态更新到本地。实现简单逻辑直观问题在于单据量大了之后轮询频率和数据库压力不好平衡每分钟查一遍可能有一半单子根本没动纯属浪费请求。Webhook 回调则是 WMS 在状态变化时主动推送给你的接口实时性好但要求你的回调接口必须稳定接收、校验、落库、返回成功环环都要可靠。我一般会按这个标准来选日均出库单量在 5000 以下轮询完全够用超过 5000建议轮询加回调并行回调负责实时更新轮询只兜底查那些超过 30 分钟还没变化的单子。两种方式本质上是“推”和“拉”的取舍没有绝对好坏只有你的技术团队擅长哪种。回调推送的方式对服务端稳定性要求高如果你只有一台应用服务器回调接口的线程池配置不好一个慢 SQL 就可能把整条链路拖死。4.2 回调验签与幂等处理你必须写对的两段逻辑回调接口的正确姿势是先验签再判重最后才处理业务。很多事故都出在“收到推送就直接更新数据库”上结果要么是推了两次导致同一单子被处理两遍要么是黑客伪造了一个假状态把订单改成已发货。验签逻辑跟前面请求签名一致把推送过来的参数除 sign 外按字典序拼接加上回调密钥做 MD5和签名字段比对。注意回调的密钥可能与应用签名所用的 secret 不同这是出于安全的考虑不要默认用同一个。def webhook_handler(request): # 1. 验签回调参数与 sign 字段 params request.form.to_dict() sign_received params.pop(sign, ) sign_expected make_sign(params, your_callback_secret) if sign_received ! sign_expected: return {success: False, msg: sign error}, 403 # 2. 按业务单号判断是否已处理过 out_order_no params.get(out_order_no) status params.get(status) # 比如 OUTBOUND_SHIPPED wms_order_no params.get(wms_order_no) if is_processed(out_order_no, status): # 已经处理过同样状态直接返回成功不重复执行 return {success: True, msg: duplicate ignored} # 3. 更新本地订单状态记录 wms_order_no 与本次状态 update_order_status(out_order_no, wms_order_no, status) mark_processed(out_order_no, status) # 4. 返回成功WMS 侧收到成功才会停止重推 return {success: True, msg: ok}这段代码里的关键不是状态更新而是mark_processed这一步。判断“已处理”时要细到“单号 状态”组合比如“已发货”和“已完成”是两个状态如果只按单号判重后面“已完成”的回调会被误判成重复而丢掉。库存变化和单据状态建议分别落不同的表不要混在一张表里否则以后对账的时候你会花大量时间拆数据。另外回调接口要返回结构化的 JSON不要返回 200 空页面也不要返回一串纯文本WMS 侧多数期望标准 JSON 里带 success 字段。回调处理是异步链路中最容易出幽灵问题的地方超时、重复推送、乱序推送都会遇到建议在回调落库时额外记一个“接收时间”字段乱序问题可以通过状态机校验来拦截。这里有一半的功夫是靠本地补偿任务去兜底回调只能算是一个加速器。5. 旺店通WMS对接避坑5 个踩完才能睡的坑5.1 SKU 编码不一致库存对不上现象本地系统显示某 SKU 可用库存 800 件WMS 里查出来只有 80 件且不是小数位问题仿佛凭空少了十倍。原因WMS 货品主档里这个 SKU 的编码是 ERP 里另一个不用的编码导入主档时用了条码而对接查询用的是货品编号两边根本不是同一个主档记录。解决上线前做一次“全量货品对账”把两边系统的货品编码拉出来做 left join 比对不一致的提前处理。不要相信“编码都一样”的口头承诺哪怕同属一个公司两边主数据归不同部门维护是常态。处理建议是在中间表里维护一套“本地货品编码 - WMS货品编码”映射对接字段只准用映射后的 WMS 编码杜绝出现“临时手输一个编码进去”这类操作。5.2 仓库编码映射错位单据下到了错误仓库现象创建出库单接口返回成功但仓里始终没有这张单查日志发现 warehouse_no 传的是“华东一号仓”而接口文档里该字段要求的是仓库编码如 HZ01你传的是仓库名称。原因对接时没有仔细看字段定义把界面里看得见的“仓库名称”当成了“仓库编码”直接塞进去WMS 侧因找不到编码而默认走到了某个默认仓或者直接丢进错误队列。解决在配置表里把“本地仓名称 / 本地仓编码 / WMS仓库编码”三列维护好代码里只允许用配置中心或数据库里的 WMS 仓库编码字段。测试环境验证时先故意传一个错误编码看是否报错如果传错还能建单成功说明这个接口有容错那更要小心——它可能把单子丢到某个你不知道的地方了。5.3 数量精度与单位换算导致少发/多发现象明细里数量传的是 1仓里拣货时拣了 1 件可客户下单买的是 1 箱。核对代码发现本地数量字段是包装单位而 WMS 接口要求的是最小零售单位。原因两边系统的“数量”定义不同本地走的商品上架时设定的箱规倍数WMS 主档里的单位是“件”。解决对接前在货品档案里增加“转换系数”字段例如一箱等于 12 件所有下发接口的明细数量都先乘以转换系数再传。这个系数建议跟货品主数据一起管理不要散落在订单流程里。另一个常被忽略的是小数数量和非整除场景——比如订单要买 3.5 箱换算成 42 件没问题但有些系统会直接传 3.5到了 WMS 侧解析失败报“数量格式错误”。建议下发前做一个数量校验函数非整数数量超过两位小数就抛异常人工介入。5.4 回调丢失或超时订单永远停在“已下发”现象WMS 里已经发货了单据状态也已经变成“已出库”本地系统里这张单却一直停留在“待发货”直到客户来催单。原因回调接口不稳定WMS 推送时你的服务恰好不可用WMS 重推了几次也没成功于是放弃。也有的情况是回调接口处理时间太久超过网关超时服务端认为推送失败但不再重试。解决不要依赖回调作为唯一状态来源。在本地维护一个“单据状态待确认”表每次创建完出库单就往这个表里写一条记录由定时任务每隔 10 到 30 分钟扫描一次凡是超过 N 分钟还没有“已出库”终态的单子主动调状态查询接口补拉。回调负责快轮询负责稳两边各干各的任一边挂了系统都能兜住。另外一个更隐蔽的坑是状态回传顺序——WMS 先推“已出库”后推“已完成”如果先收到“已完成”再收到“已出库”很多新手直接更新旧状态反而覆盖了新状态。应对办法是本地做状态值比较只允许状态往前流转往后流转的记录记入异常表。5.5 重复推送导致同一张单处理两遍现象对账发现同一张出库单在本地系统里有两条发货记录金额翻倍。原因回调重推机制触发时本地没有做幂等处理或者判重键用的是“单号 状态”而两次推送之间的状态恰好一致比如都是已出库。解决落库之前用唯一索引兜底在发货流水表上给“source out_order_no”建唯一约束第二次插入直接报冲突代码里捕获这个异常当作已处理即可。同时要注意判重键不要只用单号因为一张单可能有多次状态变化。建议采用“单号 目标状态”联合判重具体到你自己的流程里就是要确保每个状态变更动作本身是幂等的——同一个状态的重复执行不应该产生任何新的业务记录。最后再提醒一句所有重复和乱序问题在最外层加一个对账定时任务每天凌晨把 WMS 的已出库单据拉回来和本地流水做比对差异主动告警比任何代码技巧都有效。6. 进阶用一条命令的“对账脚本”把你的对接链路变成可信任的基础设施对接跑通容易长期稳定难。我的习惯是在整个对接模块旁边放一个独立的对账脚本它不属于业务流程只干一件事把 WMS 的“当日已出库单”和本地系统的“当日已发货单”拉出来做全量比对。这个脚本要能单独跑、能出报表、失败只告警不影响线上。脚本的建议输出维度可以做成下面这样一张对比表比对项本地系统数据WMS 数据结果单号缺失本地有、WMS无异常状态不一致已发货已出库异常数量不一致3件2件异常时间差异发货时间 10:02出库时间 10:05正常这个脚本不必天天跑但上线初期至少跑一个月它最大的价值不是发现差异而是让你建立起“本地系统和 WMS 到底有多少口径差异”的感知。另一个值得做的小技巧是在本地系统的订单状态流转里加一个“状态流转轨迹表”每次状态变更都插入一条带时间戳的流水之后无论哪个环节出了问题翻开轨迹表就能看到完整的因果链不用到处翻日志。我参与的一次对接项目中团队就是靠这个轨迹表定位到一个“换单重发”场景里状态回退的 bug——那是代码逻辑问题不是接口问题没有轨迹表的话可能要排查几天。对接这件事没有炫技空间把基础查漏补缺的工作做扎实后面新对接任何渠道、任何仓库都会非常顺手希望帮到你。本文还有配套的精品资源点击获取