四方支付商户接口对接文档
本文档提供给商户服务端系统对接,用于接入平台下单、查单和订单状态通知。所有示例中的商户号、密钥、产品编码和域名均为占位内容,实际值以平台后台提供为准。
提交方式:POST Form
Content-Type:application/x-www-form-urlencoded
字符集:UTF-8
金额单位:分
签名方式:MD5 大写
时间格式:yyyyMMddHHmmss
1. 对接资料
平台运营会给商户技术或对接系统提供以下资料:
| 名称 | 说明 |
|---|---|
| mchId | 商户号。每个商户唯一,下单、查单和验签都需要使用。 |
| secretKey | 商户签名密钥。只在开户或重置密钥时展示一次,请只保存在服务端。 |
| productId | 产品编码。商户只能使用平台已绑定并启用的产品编码下单。 |
| 下单地址 | {平台域名}/api/v1/pay/create_order |
| 查单地址 | {平台域名}/api/v1/pay/query_order |
| 回调IP | 平台通知商户时的出口 IP。商户如果做通知来源白名单,应放行该 IP。 |
| 商户API白名单 | 商户调用下单/查单接口的服务器公网 IP,需要提前提供给平台配置。 |
商户通知地址不在平台后台固定配置。商户每次下单时通过
notifyUrl 传入本笔订单的通知地址。
商户下单、查单、接收支付通知均统一使用 POST Form。商户服务端只需要按本文档这一套格式对接,不需要适配其它提交方式。
2. 编码、URL 解码与字段约束
请求和通知均使用 application/x-www-form-urlencoded; charset=UTF-8。签名与业务处理都以服务端完成一次 Form URL 解码后的字段名和值为准,不以 HTTP 原始编码串为准。
- 客户端先使用 UTF-8 表示逻辑字段值,再按 Form 规则编码。
%XX在服务端只解码一次;+解为一个空格,字面加号必须编码为%2B。 - 计算签名时使用解码后的原始值。例如标题为
商品 A+B时,参与签名的是包含空格和+的 UTF-8 值,不是%E5...或+的传输表达。 - 同一个字段只能提交一次。服务端只取同名字段的第一个值;重复字段会使不同语言的客户端产生不同签名结果。
- 字段名区分大小写,只有小写
sign被排除在签名外。不要发送未在本文档声明的非空字段,它们会参与签名但不构成业务字段。 - 所有下表字段必须是有效 UTF-8,且不得含控制字符(包括 TAB、CR、LF、NUL)。标识字段
mchId、mchOrderNo、productId不得有前导或尾随空白。 - 除签名字段外,空白不会在签名阶段自动删除;空字符串才会被排除。请不要依赖服务端对业务字段的后续 trim 行为。
| 字段 | 最大值 | 补充约束 |
|---|---|---|
| mchId | 64 UTF-8 字节 | 必填标识,不得有首尾空白。 |
| mchOrderNo | 128 UTF-8 字节 | 必填标识,不得有首尾空白。 |
| productId | 64 UTF-8 字节 | 必填标识,不得有首尾空白。 |
| amount | 1 至 9,999,999,999,999,999 分 | 仅 ASCII 十进制正整数;该上限保证可写入平台 NUMERIC(18,4) 金额列,实际通道金额规则仍可能更小。 |
| notifyUrl | 2048 UTF-8 字节 | 可选;生产环境必须是公网 https 地址,平台会在每次通知前重新校验解析结果。 |
| subject | 128 UTF-8 字节 | 可选订单标题。 |
| body | 512 UTF-8 字节 | 可选订单描述。 |
| param1 / param2 | 各 256 UTF-8 字节 | 可选透传字段。 |
| reqTime | 14 UTF-8 字节 | 固定为 14 位数字 yyyyMMddHHmmss。 |
| sign | 64 UTF-8 字节 | 必填;推荐传 32 位大写 MD5 十六进制值。 |
整个 HTTP 请求体最大为 1 MiB,超过会返回 HTTP 413。字段长度按 UTF-8 编码后的字节数计算,不是中文字符数;商户应在发送前自行校验。
3. 签名规则
所有商户请求和平台支付通知均使用同一套 MD5 签名规则。
- 取一次 Form URL 解码后的全部参数,排除小写
sign字段。 - 排除值恰好为空字符串的参数,例如
param1=不参与签名;仅含空格的值不是空字符串,仍会参与签名。 - 按参数名 ASCII 从小到大排序。
- 拼接为
key=value&key2=value2。 - 末尾追加
&key=商户密钥。 - 按 UTF-8 字节对完整字符串做 MD5,结果转为大写 32 位十六进制。
reqTime 默认必传并参与签名,格式为 yyyyMMddHHmmss。服务器允许时间误差为 5 分钟,超时会拒绝,防止重放攻击。
签名示例
import crypto from 'node:crypto';
function makeSign(params, secretKey) {
const pairs = Object.keys(params)
.filter((key) => key !== 'sign' && params[key] !== undefined && params[key] !== null && String(params[key]) !== '')
.sort()
.map((key) => `${key}=${String(params[key])}`);
return crypto.createHash('md5').update(`${pairs.join('&')}&key=${secretKey}`).digest('hex').toUpperCase();
}
4. 下单接口
地址:POST {平台域名}/api/v1/pay/create_order
| 字段 | 必填 | 最大值 | 说明 |
|---|---|---|---|
| mchId | 是 | 64 字节 | 商户号;不得有前导或尾随空白。 |
| mchOrderNo | 是 | 128 字节 | 商户订单号。同一商户下必须唯一,且不得有前导或尾随空白。重复提交相同金额和产品会返回原订单;金额或产品不一致会被拒绝。 |
| productId | 是 | 64 字节 | 平台提供的产品编码;不得有前导或尾随空白。 |
| amount | 是 | 1 至 9,999,999,999,999,999 分 | 订单金额,单位为分,必须为十进制正整数。例如 100.00 元传 10000。 |
| notifyUrl | 建议必填 | 2048 字节 | 本笔订单状态通知地址。生产环境必须使用公网 https,且不允许内网、localhost、链路本地地址;本地联调只有在服务器显式开启测试开关时才允许 HTTP。 |
| subject | 否 | 128 字节 | 订单标题。为空时平台会使用默认标题。 |
| body | 否 | 512 字节 | 订单描述。 |
| param1 | 否 | 256 字节 | 商户自定义透传字段,订单状态通知时原样返回。 |
| param2 | 否 | 256 字节 | 商户自定义透传字段,订单状态通知时原样返回。 |
| reqTime | 是 | 14 字节 | 请求时间,固定为 14 位数字格式 yyyyMMddHHmmss,参与签名。 |
| sign | 是 | 64 字节 | 按第 3 节规则生成;推荐传 32 位大写 MD5 值。 |
请求示例
curl -X POST 'https://pay.example.com/api/v1/pay/create_order' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'mchId=M10001' \
--data-urlencode 'mchOrderNo=TEST202607090001' \
--data-urlencode 'productId=8007' \
--data-urlencode 'amount=10000' \
--data-urlencode 'notifyUrl=https://merchant.example.com/pay/notify' \
--data-urlencode 'subject=商品订单' \
--data-urlencode 'param1=merchant-extra-1' \
--data-urlencode 'reqTime=20260709213000' \
--data-urlencode 'sign=按签名规则计算后的MD5大写值'
成功响应
{
"code": 0,
"message": "ok",
"data": {
"order_no": "SF20260709213000A1B2C3D4",
"merchant_order_no": "TEST202607090001",
"pay_url": "https://pay-provider.example/pay?token=xxx",
"amount": 10000,
"status": 0
},
"request_id": "..."
}
下单成功返回
code=0 和 status=0 只表示平台已受理,不代表付款成功。商户入账必须以后续通知或查单 status=1 为准。
5. 查单接口
地址:POST {平台域名}/api/v1/pay/query_order
| 字段 | 必填 | 最大值 | 说明 |
|---|---|---|---|
| mchId | 是 | 64 字节 | 商户号;不得有前导或尾随空白。 |
| mchOrderNo | 是 | 128 字节 | 商户订单号;不得有前导或尾随空白。 |
| reqTime | 是 | 14 字节 | 请求时间,固定为 14 位数字格式 yyyyMMddHHmmss,参与签名。 |
| sign | 是 | 64 字节 | 按第 3 节规则生成;推荐传 32 位大写 MD5 值。 |
成功响应
{
"code": 0,
"message": "ok",
"data": {
"order_no": "SF20260709213000A1B2C3D4",
"merchant_order_no": "TEST202607090001",
"amount": 10000,
"status": 1,
"pay_url": "https://pay-provider.example/pay?token=xxx"
},
"request_id": "..."
}
6. 订单状态通知
订单变为交易成功(status=1)后,平台会向商户下单时传入的 notifyUrl 发送 UTF-8 POST Form 通知;如果成功订单之后被交易冲正,平台会再发送 status=3 更正通知。Content-Type 为 application/x-www-form-urlencoded。商户收到每次通知后都必须按第 3 节对解码后的字段验签、校验金额和订单归属,并按订单号幂等更新最终状态;处理成功后响应纯文本 success。
| 字段 | 说明 |
|---|---|
| mchId | 商户号。 |
| payOrderId | 平台订单号。 |
| mchOrderNo | 商户订单号。 |
| amount | 订单金额,单位为分。 |
| status | 1 表示交易成功;3 表示该成功状态已被冲正,是必须处理的更正通知。状态含义见第 7 节。 |
| param1 | 下单时传入的自定义字段,可能为空。 |
| param2 | 下单时传入的自定义字段,可能为空。 |
| reqTime | 通知时间,格式 yyyyMMddHHmmss。 |
| sign | 平台按商户密钥生成的签名。 |
通知示例
mchId=M10001
payOrderId=SF20260709213000A1B2C3D4
mchOrderNo=TEST202607090001
amount=10000
status=1
param1=merchant-extra-1
param2=
reqTime=20260709213120
sign=平台签名
同一订单发生冲正时,平台会再次发送上述字段,其中 status=3,并使用新的 reqTime 和对应签名。商户不得因为该订单曾处理过 status=1 而忽略这次更正。
商户处理要求
- 先验签,签名失败直接拒绝处理。
- 按
mchId、payOrderId和mchOrderNo查询本地订单,确认订单归属一致。 - 校验
amount与本地订单金额完全一致。 - 状态更新必须幂等:
status=1对同一订单只能入账一次;收到status=3时,如已按status=1入账,必须只冲账或扣回一次,并把本地订单更新为冲正。 - 处理成功后响应纯文本
success,不要返回 JSON、HTML 或额外空格内容。
平台会分别记录当前目标状态的通知结果。无论
status=1 还是 status=3,商户未返回 success 时平台都会自动重试;重启或队列短暂故障后也会从数据库恢复,后台同时支持人工补发通知。
超时与自动重试
每次通知 HTTP 请求最长等待 10 秒。首次通知立即发起;只有收到 HTTP 2xx 且响应体去除首尾空白后不区分大小写等于 success 才视为成功。请固定返回小写纯文本 success。网络错误、超时、非 2xx 或其他响应体均会重试。
| 发送次数 | 计划时间 | 说明 |
|---|---|---|
| 1 | 订单成功或冲正后立即 | 当前状态的首次通知。 |
| 2 | 第 1 次失败后约 5 秒 | 自动重试。 |
| 3 | 第 2 次失败后约 10 秒 | 自动重试。 |
| 4 | 第 3 次失败后约 20 秒 | 自动重试。 |
| 5 | 第 4 次失败后约 30 秒 | 自动重试。 |
| 6 | 第 5 次失败后约 60 秒 | 自动重试。 |
| 7 | 第 6 次失败后约 120 秒 | 自动重试。 |
| 8 | 第 7 次失败后约 180 秒 | 自动重试。 |
| 9 | 第 8 次失败后约 300 秒 | 最后一次自动通知;仍失败后标记为失败,可由运营人工补发。 |
7. 状态与错误
订单状态
| status | 含义 | 商户处理建议 |
|---|---|---|
| 0 | 待支付 | 不要入账,等待通知或继续查单。 |
| 1 | 交易成功 | 验签、校验金额后入账。 |
| 2 | 交易失败 | 不要入账,可提示用户重新发起支付。 |
| 3 | 交易冲正 | 平台会发送可靠的更正通知。商户侧如已按状态 1 入账,验签并幂等校验后必须冲账或扣回,并将订单最终状态更新为冲正。 |
通用响应结构
{
"code": 0,
"message": "ok",
"data": {},
"request_id": "..."
}
| HTTP | code | 常见含义 |
|---|---|---|
| 400 | 40000 | 参数错误、金额不合法、reqTime 超出允许范围。 |
| 401 | 40100 | 签名验证失败。 |
| 403 | 40300 | 请求 IP 不在白名单、商户停用、产品不可用或未绑定产品费率。 |
| 404 | 40400 | 订单不存在。 |
| 409 | 40900 | 商户订单号重复,但本次金额或产品与原订单不一致。 |
| 502 | 50200 | 请求尚未发送给上游时即发现转发配置不可用;该请求未形成可支付链接。 |
| 503 | 50200 | 当前金额没有可用通道。 |
| 500 | 50000 | 系统繁忙,请稍后重试。 |
8. 上线检查
商户侧必须完成
- 商户号和密钥只保存在商户服务端,不放到任何公开页面、客户端代码或日志中。
- 下单、查单都带
reqTime和sign,并按解码后的 UTF-8 字段签名。 - 金额按分传整数,不传元、不传小数。
- 提交前校验字段 UTF-8 字节长度、控制字符和标识字段首尾空白。
- 通知接口先验签,再校验金额,最后幂等入账。
- 通知处理成功只返回
success。
联调前确认
- 商户调用服务器公网 IP 已提供给平台加入 API 白名单。
- 平台提供的产品编码已绑定给该商户且状态启用。
- 商户
notifyUrl是公网可访问地址。 - 商户服务器已放行平台提供的回调 IP。
- 服务器时间已同步,和北京时间误差不超过 5 分钟。