本文约定开放接口的接入凭证、签名算法、防重放机制与响应码口径。所有接口(含连通性自测)都必须签名,未通过签名的请求不进入业务逻辑。
| 项 | 说明 |
|---|---|
| 接口地址 | https://{域名}/openapi/bwc/...(测试/生产地址由我方在开通时提供) |
| 传输协议 | 生产环境必须使用 HTTPS |
| 凭证 | appId(渠道应用 ID)+ secret(签名密钥),同时用于接口签名与 Webhook 验签 |
| 获取方式 | 由我方在渠道管理后台开通并线下下发,禁止通过接口回传 |
code=3,message 说明具体原因。| 参数 | 必填 | 类型 | 说明 |
|---|---|---|---|
appId | 是 | string | 渠道应用 ID |
timestamp | 是 | string | 请求时间戳,秒或毫秒均可(大于 10 位的按毫秒处理),与服务端时间偏差不得超过 ±300 秒 |
nonce | 是 | string | 随机串,300 秒内同一 appId 下不可重复(建议 UUID 去横线) |
sign | 是 | string | 签名,见第三节 |
| 校验项 | 失败返回 |
|---|---|
appId 缺失或查不到渠道 | code=3,参数(appId)错误:未查询到渠道,请稍后再试 |
timestamp 缺失 / 非数字 | code=3,参数(timestamp)错误:时间戳不能为空 / 时间戳格式错误 |
timestamp 超出 ±300 秒 | code=3,参数(timestamp)错误:请求已过期 |
nonce 缺失 | code=3,参数(nonce)错误:随机串不能为空 |
nonce 重复 | code=3,参数(nonce)错误:重复请求 |
| 签名不匹配 | code=101,签名不匹配 |
sign 字段(appId / timestamp / nonce 参与签名)。k1=v1&k2=v2&...;值为 null 或空字符串的键整条跳过。&、不加分隔符)。sign = lowerHex( MD5( "k1=v1&k2=v2&...&kn=vn" + secret ) )| 结构 | 规则 | 示例 |
|---|---|---|
| 普通字段 | 原样 | orderNo=BW17612345670001234 |
| 嵌套对象 | 父键 + . + 子键 | {"coordinate":{"longitude":117.28}} → coordinate.longitude=117.28 |
| 数组 | 父键 + [下标] | {"imgs":["a","b"]} → imgs[0]=a、imgs[1]=b |
| null / 空串 | 跳过不拼 | {"reason":""} → 不出现 |
| 数字 / 布尔 | 按原字面量转字符串 | true、500、117.285735 |
本开放接口的请求字段均为扁平结构(经度 longitude、纬度latitude直接放在顶层,不要求包成coordinate对象)。若渠道方自行嵌套,签名必须按上表的点号路径计算,否则校验失败。
encodeURIComponent;中文按 UTF-8 原样参与。code=3「参数(body)错误:请求体缺失或 JSON 格式错误」。MY_APP_SECRET):GET /openapi/bwc/ping?appId=200009×tamp=1761234567&nonce=6f1a2b3c4d5e6f70&sign=1ddb3ec7a8af3322c76945576d8d5f48appId < nonce < timestamp,末尾拼 secret):appId=200009&nonce=6f1a2b3c4d5e6f70×tamp=1761234567MY_APP_SECRET1ddb3ec7a8af3322c76945576d8d5f48appId=200009×tamp=1761234567&nonce=a1b2c3d4e5f60718{
"channelOrderNo": "CH20261023001",
"channelUserId": "user_10001",
"mobile": "13800138000",
"activityId": "12345",
"storeId": "store_123",
"longitude": 117.285735,
"latitude": 31.863916
}sign 不参与):activityId=12345&appId=200009&channelOrderNo=CH20261023001&channelUserId=user_10001&latitude=31.863916&longitude=117.285735&mobile=13800138000&nonce=a1b2c3d4e5f60718&storeId=store_123×tamp=1761234567MY_APP_SECRET068735da2dbf5c9c9f9d938bd33e8bf2两份示例均按“空值跳过、URL 覆盖 body”实现;请勿在拼接前后对值做 URL 编码或 trim。
code 判断:{
"code": 0,
"message": "success",
"data": {}
}| 字段 | 类型 | 说明 |
|---|---|---|
code | int | 业务码,0 表示成功 |
message | string | 结果描述 |
data | object | 业务数据,失败时可能为空或携带 field 定位信息 |
data 结构:{
"total": 128,
"page": 0,
"size": 20,
"data": []
}| code | 含义 | 典型场景 | 处理建议 |
|---|---|---|---|
0 | 成功 | — | 正常解析 data |
3 | 参数错误(含防重放失败) | 缺公共参数、参数格式越界、时间戳过期、nonce 重复、请求体缺失、请求方法不支持、渠道禁用/接口过期 | 修正参数后重试;时间窗与 nonce 类错误需重新生成 timestamp 与 nonce |
101 | 签名不匹配 | 密钥错误、参与签名的参数集合不一致、空值/编码处理不一致 | 用 ping 复现;核对签名原文(常见原因:query 未参与签名、对值做了 URL 编码) |
300 | 业务参数错误 | 当前状态不允许该操作、外卖单号非纯数字、参数组合非法 | 先用 order/detail 刷新状态再决定是否重试 |
301 | 业务校验失败 | 未开通渠道服务费、订单不存在、订单不属于当前渠道、活动不存在/已结束/不在活动时间窗、报名被拒 | 按 message 提示修正;归属类错误确认 orderNo 是否属于当前 appId |
500 | 服务端异常 | 内部错误或依赖超时 | 可间隔重试(报名类建议先按 channelOrderNo 查单避免重复报名);持续失败请联系我方 |
data.field 会给出具体字段名:{"code": 3, "message": "参数(rebateMode)错误:返利模式不能为空", "data": {"field": "rebateMode"}}301 的 message 为面向渠道的简短原因,可直接展示给渠道运营。code。| 约定 | 说明 |
|---|---|
| 金额 | 一律为分(mealPrice / commissionAmount / channelAmount 等),整数字段 |
| 比例 | 万分比(commissionRate=500 表示 5%) |
| 接口时间字段 | yyyy-MM-dd HH:mm:ss 字符串(GMT+8);ping 返回的 timestamp 为毫秒数字 |
| Webhook 时间字段 | 毫秒时间戳(epoch millis),与接口响应不同,注意区分 |
status | 接口响应为枚举名字符串(Registered / Submitted / PendingReview / Completed / Rebated / Cancelled);Webhook 为整数 |
| 分页 | page 从 0 起,size 1~100(默认 20,超过 100 按 100 处理) |
GET /openapi/bwc/ping,code=0 且 data.appId 与自己的一致,说明签名链路可用。{"code": 0, "message": "success", "data": {"appId": "200009", "timestamp": 1761234567123}}code=101;用 1000 秒前的时间戳应返回 code=3 请求已过期;同 nonce 连发两次,第二次应返回 code=3 重复请求。三条都符合,即可判定签名与防重放链路正常。activity/categories → activity/search → activity/detail,最后再测 order/apply(写接口),避免产生无效报名数据。请求已过期。secret 仅保存在服务端,禁止下发到客户端(App/H5)或写入前端配置。nonce 使用真随机(UUID v4 或 SecureRandom),不要用固定值或自增数。code,请勿以 HTTP 状态码做告警,应以 code != 0 做业务告警。