问达通

开发者文档

ERP 标准接口规范

鉴权、资源模型、订单状态、幂等、增量同步与异常恢复。

版本 1.0 设计稿,2026 年 9 月 24 日。适用于猫粮厂家、ERP 实施商及集成开发人员。本规范是待实现和联调确认的接口合同,不代表现有线上 API。示例域名使用保留的 example.com,不能用于生产。未提供后台实现代码。

1 架构与系统责任

经销商应用 → 问达通业务服务 ↔ 厂家适配层 ↔ 自研或第三方 ERP。经销商 App 不持有 ERP 密钥,不直接连接 ERP 数据库。适配层负责厂商鉴权、账套选择、字段映射、单位转换和重试。

ERP 是客户、商品、价格、库存、审核结果与履约状态的主系统。问达通是沟通身份、订货需求及业务关联的主系统。聊天文本不自动转成订单,也不默认回传聊天正文。适配层将 ERP 对象转换为本规范,并调用问达通接口;经销商提交需求的前端接口不在本文范围。

2 基础协议与鉴权

拟定基础地址:https://api.example.com/integration/v1。沙箱与生产必须使用不同账号、密钥、数据和地址;正式地址由实施交付提供。

HTTPS + UTF-8 JSON。鉴权方案为 OAuth 2.0 client_credentials;POST /oauth/token 的表单字段为 grant_type=client_credentials、client_id、client_secret、scope。Token 响应包含 access_token、token_type=Bearer、expires_in=3600、scope;不颁发 refresh_token。密钥只存服务端受控密钥存储,不进入 App、网页或日志。

每个客户端绑定一个厂家 tenant_id 和一个 ERP connection_id(含账套/组织映射),不能凭请求头切换租户。接口服务端必须逐对象验证此绑定;跨租户 ID 返回 404 或 403,不能泄露对象存在性。换账套建立独立连接及凭据。

拟定 scopes:master:write(客户与商品)、commercial:write(价格库存)、orders:read、orders:write(结果回写)、fulfillment:write(物流)、returns:read、returns:write、events:read。按需授权。每次请求使用 Authorization: Bearer <token>;服务端生成 request_id 并返回 X-Request-ID,调用方可记录自身关联号,但不能控制授权。

响应统一为 {"data":对象或列表,"request_id":"req_demo"};列表另含 next_cursor。错误统一为 {"error":{"code":"...","message":"...","retryable":false,"details":[]},"request_id":"..."}。HTTP 状态必须反映成功或失败,不用 200 包装失败。单资源 PUT 返回 200,仅表示已持久接收该投影,不表示真实支付、审核或出库。

3 标识 时间 金额与单位

外部主键使用 ERP 稳定编码或 UUID,禁止用客户名称、手机号、商品名称作为关联键。ID 限 ASCII 字母、数字、点、下划线与短横线,长度 1–64;历史 ERP 编码不符合时适配层维护映射,不做有碰撞的简单替换。

所有版本号 version 为正整数,在 connection_id + 对象类型 + 主键内单调递增;ERP 没有原生版本时由适配层的持久变更序列产生,禁止只依赖秒级时间戳。updated_at 为 RFC3339 UTC 时间,批次日期为 YYYY-MM-DD。时间只用于审计,版本号决定先后。

金额使用人民币分整数 amount_minor,currency=CNY;单价 unit_price_minor 为每个订货单位的含税价格。税率 tax_rate 使用 0 到 1 的十进制字符串。数量 quantity、available_quantity 等使用十进制字符串,最多 3 位小数;袋/箱等不可拆零单位必须为整数。禁止二进制浮点参与金额计算。

商品单位 unit_code 例如 BAG、CASE;一个 unit_code 对应的 base_quantity 必须固定。例如 1 CASE = 6 BAG,以 6.000 表示。箱规改变应产生新 SKU 或版本并阻止旧需求静默换算。默认总额按行含税单价 × 数量四舍五入到分,再汇总;折扣、运费、返利等规则须扩展签署后接入,不在首期隐式计算。

4 幂等与并发

所有写接口要求 Idempotency-Key(UUID 格式建议)。服务端以 client_id + 方法 + 规范路径 + key 记录首次请求指纹及结果,拟定保留 7 天。同 key 同内容返回相同结果;同 key 不同内容返回 409 IDEMPOTENCY_CONFLICT。超时不能直接换 key 重建业务。

PUT 为完整资源快照,必填字段不得省略;可选 nullable 字段省略或 null 均清空,适配层须先构造完整快照。新 version 大于当前才应用;相同 version 同内容返回原结果,相同 version 内容不同返回 409 VERSION_CONFLICT,较旧 version 返回 409 STALE_VERSION。返回 current_version 供对账。不得把逻辑停用改成物理删除;active=false 不删除历史单据。

每个单据只有一个适配层写入 owner。ERP 创建订单必须以问达通 order_id 建立唯一外部单号约束或持久映射;即使超过幂等缓存期,也不能再次创建。网络超时而结果未知时,先按该业务 ID 查询 ERP,查到后补记映射与回执;无法确定时进入人工异常队列,不盲重试新单。

5 接口目录

方法与路径调用方用途
POST /oauth/token厂家适配层获得集成令牌,位于站点根路径,不带 /integration/v1
PUT /dealers/{dealer_id}适配层同步 ERP 经销客户与授权门店
PUT /products/{sku_id}适配层同步 SKU、箱规、单位、上下架
PUT /price-lists/{price_list_id}/items/{sku_id}适配层同步价格表中某 SKU 的有效含税价
PUT /inventories/{warehouse_id}/{sku_id}适配层同步指定仓库 SKU 可售库存快照
GET /orders适配层增量拉取订货需求及取消请求
GET /orders/{order_id}适配层查询单笔需求的当前完整状态
PUT /orders/{order_id}/erp-status适配层回传 ERP 接收、审核或拒绝结果
PUT /shipments/{shipment_id}适配层同步分批发货及物流状态
GET /returns适配层增量拉取售后需求,不自动退钱
PUT /returns/{return_id}/erp-status适配层回传售后审核与处理状态
GET /events适配层拉取可靠业务事件,用于补偿回调

6 数据对象与字段

所有可写资源必填 version、updated_at;URL 中的主键为资源身份,不在 body 重复。完整类型、required、枚举和范围见 openapi.json。

经销商 Dealer

erp_customer_code 为 ERP 客户编码;name、active 必填。price_list_id 关联已存在价格表,未设置则不展示交易价格;stores 为门店数组,每项含 store_id、name、active。phone 可空;地区和联系人仅传递必要内容。经销商 App 账户到 dealer_id 的绑定必须由厂家审核,本接口不凭 phone 自动建账号或授权。停用客户后停止新需求,保留旧单查询权限由厂家政策决定。

商品 Product

name、active、base_unit、order_units 必填;order_units 每项 unit_code、base_quantity、min_quantity、step_quantity。净重 net_weight_grams 为整数克,包装说明 package_text 可空。batch_tracking、shelf_life_days 为可选属性;批次不是 SKU,生产日期和失效日期属于实际发运批次。图片使用允许域名的 HTTPS URL,禁止内网地址;尺寸、内容和 URL 生命周期由实施约定。

价格 Price

unit_code、unit_price_minor、currency、tax_rate、valid_from、valid_to、active 必填,其中 valid_to 允许 null。有效区间为 [valid_from,valid_to),同价格表同 SKU 当前合同只支持一个订货单位。需要袋价和箱价并存时须新增明确扩展,不能覆盖同一资源的语义。价格表 ID 由厂家分配;价格条目可先写入再关联经销商。不存在或失效价格时禁止猜价或使用其它客户价。

库存 Inventory

unit_code(必须等于产品 base_unit)、available_quantity、observed_at 必填。可售值是 ERP 已扣除冻结与占用后的参考快照;不代表问达通预留库存,不累加快照,不用重复消息重复扣减。负库存或不确定值由 ERP 明确政策处理,首期拒绝负值;过期阈值由项目约定,前端提示更新时间。

订货需求 Order

order_id、dealer_id、store_id、status、lines、shipping_address、submitted_at、version、updated_at 必有。每行含 line_id、sku_id、unit_code、quantity、unit_price_minor、currency;total_amount_minor 为需求提交时的含税总额,非付款凭证。shipping_address 为提交时不可变快照,含 recipient、phone、province、city、district、detail;样例一律虚构。

status 取 SUBMITTED、ERP_ACCEPTED、ERP_REJECTED、CANCEL_REQUESTED、CANCELLED。ERP 销售单号 erp_order_id 仅在成功接受后存在。提交需求后不原地改变行项目和价格;变更应走取消后新需求,保留关联和审计。ERP 校验价与原价不一致时返回 PRICE_RECONFIRM_REQUIRED 拒绝或人工确认结果,不自动用新价成交。

ERP 状态 OrderERPStatus

status 仅取 ACCEPTED、REJECTED、CANCELLED;ACCEPTED 必须带 erp_order_id;REJECTED 必须带 reason_code 和可读 message;CANCELLED 必须表明 ERP 已实际批准取消。ERP_ACCEPTED 不等于已审核完成付款或已出库。首次 SUBMITTED 可接收或拒绝;CANCEL_REQUESTED 只有确认取消后才改 CANCELLED。若无法取消,回写 ACCEPTED + CANCEL_DENIED 原因,维持原销售订单;已终结的拒绝或取消不可被旧回调恢复。

发运 Shipment

order_id、erp_shipment_id、status、lines、shipped_at 必填,其中未发货时 shipped_at 允许 null。status 为 READY、SHIPPED、DELIVERED、EXCEPTION、VOID;每行含 order_line_id、sku_id、unit_code、quantity,可带 batch_no、production_date、expiry_date。多个 shipment_id 支持分批发货。累计有效发货量不能大于订单行量;撤销按高版本 VOID 处理,不能物理删除。已实际发运不得用 VOID 掩盖退货,须走售后。

carrier_code、tracking_number、tracking_url 可空;只能在可靠来源确认后标 DELIVERED,不按发货天数推测。物流地址和手机号仅给授权用户看;tracking_url 须 HTTPS 且限制可接受域名。库存与物流到达顺序独立,不据其中一个猜测另一个。

售后 Return

return_id、order_id、dealer_id、items、reason、status、version、updated_at;items 含 order_line_id、quantity、unit_code。status 为 REQUESTED、APPROVED、REJECTED、RECEIVED、CLOSED;适配层回写仅允许后四项,拒绝须给原因,收货不能大于批准数量。财务退款是独立业务,本状态不能当退款成功凭证。

7 增量拉取与 Webhook

GET /orders、/returns、/events 接受 cursor 和 limit(默认 100,最大 200)。响应 data.items 与 next_cursor。首次不带 cursor 从当前可保留变更序列最早位置开始;next_cursor 是不透明字符串,即使空页也要持久保存并用于下次查询。按租户连接和资源维护独立游标,不支持自己拼接游标。按变更序列稳定排序,重复版本去重。拟定变更记录保留 30 天,过期返回 410 CURSOR_EXPIRED,不能回到起点假装增量成功。

首期全量初始化通过厂家审核的脱敏导出及受控同步完成;过期恢复时暂停写入、导出当前业务主键与版本对账、确定新起点并补齐差异。/orders 不提供无限期历史档案导出。生产保留期须在合同中确认。

可选 Webhook 为问达通向客户登记的 HTTPS 接收地址 POST 事件通知,非默认已实现能力。事件类型:order.submitted、order.cancel_requested、return.requested。载荷含 event_id、event_type、tenant_id、connection_id、occurred_at、resource_id、resource_version;不携带聊天正文或完整地址。接收方再用 GET /orders/{id} 或增量返回对象核验当前状态。

拟定签名:X-WDT-Key-Id、X-WDT-Timestamp(Unix 秒)、X-WDT-Signature(小写 hex)。待签字节为 timestamp + 换行 + 原始 HTTP body 字节;以每连接 Webhook secret 做 HMAC-SHA256。接收方常量时间比对、验时间偏差不超过 300 秒、以 event_id 去重,并验证载荷租户连接与密钥绑定。HTTP 客户端不得先重排 JSON 再验签。密钥轮换通过 key ID 识别新旧版本,过渡窗口按合同约定。

接收方持久记录入队后才返回 2xx,业务处理异步进行;超时按至少一次投递,建议 1、5、15、60 分钟退避,持续不超过 24 小时,然后人工告警,并用 /events 游标补偿。此建议不是当前 SLA。不保证不同对象或同对象事件按序到达;按版本应用并查询当前对象。POST 到客户 Webhook 的 2xx 不能当作 ERP 已成功建单。

8 错误与恢复

HTTPcode处理
400INVALID_ARGUMENT修正字段格式,不原样重试
401TOKEN_EXPIRED获取新令牌,保留原幂等键重试一次
403SCOPE_DENIED停止并核对权限,不换身份绕过
404RESOURCE_NOT_FOUND核对映射与对象前置,不能自动跨租户查找
409IDEMPOTENCY_CONFLICT / VERSION_CONFLICT / STALE_VERSION查询当前版本和原业务结果,不换键覆盖
409INVALID_STATE_TRANSITION核对审核、取消与出库先后,转人工处理
410CURSOR_EXPIRED执行受控全量对账恢复
422REFERENCE_MISSING / UNIT_MISMATCH / BUSINESS_RULE_FAILED补主数据或修业务数据后以明确版本恢复
429RATE_LIMITED按 Retry-After 秒退避,保留 key
500 / 503INTERNAL_ERROR / TEMPORARILY_UNAVAILABLE指数退避并加随机抖动;写入先查结果

拟定限流每客户端 10 请求/秒、突发 20,正式值由接口实施及容量确认。失败日志记录 request_id、资源 ID、版本、错误码和脱敏连接标识,不记录令牌、完整地址或聊天正文。业务失败与网络未知结果分别标识,不把异常全部归为“同步成功”。

9 示例

以下是完整商品写入示例,例示授权值仅为占位符。

PUT https://api.example.com/integration/v1/products/CAT-ADULT-2KG
Authorization: Bearer <access_token>
Idempotency-Key: 07f85951-1990-4325-8b0f-c9a32665e122
Content-Type: application/json

{"name":"全价成猫粮 2kg","active":true,"base_unit":"BAG","net_weight_grams":2000,"package_text":"6袋每箱","order_units":[{"unit_code":"CASE","base_quantity":"6.000","min_quantity":"1.000","step_quantity":"1.000"}],"version":1,"updated_at":"2026-09-24T00:00:00Z"}

成功响应:

{"data":{"resource_id":"CAT-ADULT-2KG","version":1,"applied":true},"request_id":"req_demo_001"}

订货需求接单回写:

PUT https://api.example.com/integration/v1/orders/WDT-DEMO-1001/erp-status
Authorization: Bearer <access_token>
Idempotency-Key: d488ea9d-f8b5-4d95-80c0-c7a32fb5d337
Content-Type: application/json

{"status":"ACCEPTED","erp_order_id":"SO-DEMO-1001","reason_code":null,"message":null,"version":1,"updated_at":"2026-09-24T00:10:00Z"}

此处 version 属于该订单的 ERP 状态投影,和问达通 Order 变更 version 是独立序列,禁止把两者混用。取消获批应使用同一投影更高版本,已签收订单不允许取消。

10 版本与扩展

路径主版本 /v1。增加可选响应字段属于向后兼容变更,消费者忽略未知字段;新增枚举值、改变字段含义或 required 等属于需协商的破坏性变更,升级主版本并约定迁移期。未知业务状态不得当成功。促销、返利、信用额度、账期、电子发票、WMS 独立连接等必须另签字段与权限合同,不能在自由文本中隐式实现。

文档包中的 openapi.json 覆盖首期集成接口的结构与枚举;本文的条件必填、状态转移、累计数量及租户隔离规则同样是合同组成部分。全部接口须实际实现、联调、独立安全验证和客户验收后才能标为可用。