商户交易API接口文档

本文档描述商户对接支付系统的完整API接口,供商户开发人员接入使用。

🔗 接口网关:https://zf.qqtkm.com
💡 数据格式:请求采用 POST + JSON,返回 JSON,字符编码 UTF-8
🔐 签名方式:MD5,详见下方签名规则

更新日志

日期版本说明
2026-06-01发布品牌化版本,迁移至自有平台
2026-06-06重点补充签名说明及代码示例

🔐 签名规则(重点)

⚠️ 签名错误是最常见的接入问题!90%的接入失败都是签名计算错误导致的。请务必仔细阅读以下规则,参照代码示例进行对接。

一、请求签名(商户 → 平台)

商户调用所有接口时,需在请求中携带 app_idtimestampsign 三个鉴权参数。签名用于验证请求来源的合法性。

📝 签名公式

sign = MD5( app_id + app_secret + timestamp )

即:将 app_idapp_secrettimestamp 三个值直接拼接(中间无分隔符,无等号,无&符号),对拼接后的字符串做 MD5 运算,得到32位小写十六进制字符串。

鉴权参数说明

参数必填限制类型说明
app_id32string应用ID,在商户后台「密钥管理」中获取
timestamp10int请求时间戳,10位精确到秒(如 1717728000
sign32string请求参数签名,32位小写MD5

签名计算示例

已知参数:
  app_id     = "app_37b2ee23d733b7cb"
  app_secret = "sec_a1b2c3d4e5f6g7h8"
  timestamp  = 1717728000

拼接字符串(无分隔符):
  "app_37b2ee23d733b7cbsec_a1b2c3d4e5f6g7h81717728000"

MD5 计算:
  sign = md5("app_37b2ee23d733b7cbsec_a1b2c3d4e5f6g7h81717728000")
       = "e10adc3949ba59abbe56e057f20f883e"   ← 32位小写
⚠️ 签名常见错误(务必避免):

二、回调验签(平台 → 商户)

支付成功/退款成功后,平台会向商户的 notify_url 发送 POST 通知(application/json)。商户需验证签名以确保通知来自平台。

📝 验签公式

sign = MD5( key1=value1&key2=value2&...&keyN=valueN &key= app_secret )

规则:将回调 JSON 中所有字段(除 sign 本身和值为空的字段外)按 key 的 ASCII 升序排列,拼接成 key=value 格式,最后追加上 &key=***(***为 app_secret),对整个字符串做 MD5。

验签步骤

1. 接收回调 JSON 数据
2. 取出 sign 字段值备用
3. 移除 sign 字段,过滤掉值为空(""或null)的字段
4. 剩余字段按 key 的 ASCII 码升序排列(字母顺序)
5. 拼接:key1=value1&key2=value2&...&keyN=valueN
6. 末尾追加 &key=你的app_secret
7. 对整个字符串做 MD5,得到32位小写十六进制
8. 比对计算结果与回调中的 sign 字段是否一致

验签计算示例

回调数据:
{
  "amount": "1.00",
  "order_no": "SF20260606123456XXXXXX",
  "out_order_no": "M202606060001",
  "paid_at": "2026-06-06T12:00:00.000Z",
  "status": "paid",
  "trade_no": "4200001234202606061234567890",
  "sign": "xxxxxxxx"
}

Step 1: 移除sign,按key升序排列
  amount, order_no, out_order_no, paid_at, status, trade_no

Step 2: 拼接
  amount=1.00&order_no=SF...&out_order_no=M...&paid_at=2026-06-06T12:00:00.000Z&status=paid&trade_no=4200...

Step 3: 追加 &key=***
  ...&trade_no=4200...&key=sec_a1b2c3d4e5f6g7h8

Step 4: MD5计算,与回调中sign对比
  一致 → ✅ 验签通过
  不一致 → ❌ 非平台通知,请忽略
💡 验签要点:

签名代码示例

PHP 请求签名 + 回调验签

<?php
// ===== 请求签名 =====
$app_id     = 'app_37b2ee23d733b7cb';   // 你的应用ID
$app_secret = 'sec_a1b2c3d4e5f6g7h8';   // 你的应用密钥
$timestamp  = time();                     // 10位秒级时间戳

// 拼接:app_id + app_secret + timestamp(无分隔符!)
$sign = md5($app_id . $app_secret . $timestamp);

// 构造请求参数
$params = [
    'app_id'       => $app_id,
    'timestamp'    => $timestamp,
    'sign'         => $sign,
    'out_order_no' => 'M202606060001',
    'amount'       => '1.00',
    'notify_url'   => 'https://your-site.com/notify',
    'return_url'   => 'https://your-site.com/return',
    'user_ip'      => '123.456.789.012',
];

$ch = curl_init('https://zf.qqtkm.com/api/pay/order');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);

// ===== 回调验签 =====
$notifyData = json_decode(file_get_contents('php://input'), true);
$receivedSign = $notifyData['sign'];
unset($notifyData['sign']);

$notifyData = array_filter($notifyData, function($v) { return $v !== '' && $v !== null; });
ksort($notifyData);

$signStr = '';
foreach ($notifyData as $k => $v) { $signStr .= ($signStr ? '&' : '') . $k . '=' . $v; }
$signStr .= '&key=' . $app_secret;

$calcSign = md5($signStr);
if ($calcSign === $receivedSign) {
    echo 'SUCCESS';  // ✅ 验签通过
} else {
    echo 'SIGN_ERROR';  // ❌ 验签失败
}
?>

Python 请求签名 + 回调验签

import hashlib, time, requests

app_id     = 'app_37b2ee23d733b7cb'
app_secret = 'sec_a1b2c3d4e5f6g7h8'
timestamp  = int(time.time())

# ===== 请求签名 =====
sign_str = app_id + app_secret + str(timestamp)  # 无分隔符!
sign = hashlib.md5(sign_str.encode('utf-8')).hexdigest()

params = {
    'app_id': app_id, 'timestamp': timestamp, 'sign': sign,
    'out_order_no': 'M202606060001', 'amount': '1.00',
    'notify_url': 'https://your-site.com/notify',
    'return_url': 'https://your-site.com/return',
    'user_ip': '123.456.789.012',
}

resp = requests.post('https://zf.qqtkm.com/api/pay/order', json=params)
print(resp.json())

# ===== 回调验签(Flask示例)=====
from flask import Flask, request
app = Flask(__name__)

@app.route('/notify', methods=['POST'])
def notify():
    data = request.json
    received_sign = data.pop('sign', '')
    filtered = {k: v for k, v in data.items() if v != '' and v is not None}
    sign_str = '&'.join(f'{k}={v}' for k, v in sorted(filtered.items()))
    sign_str += f'&key={app_secret}'
    calc_sign = hashlib.md5(sign_str.encode('utf-8')).hexdigest()
    return 'SUCCESS' if calc_sign == received_sign else 'SIGN_ERROR'

Java 请求签名

import java.security.MessageDigest;

String appId     = "app_37b2ee23d733b7cb";
String appSecret = "sec_a1b2c3d4e5f6g7h8";
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);

String signStr = appId + appSecret + timestamp;  // 无分隔符!
String sign = md5(signStr);

static String md5(String input) {
    MessageDigest md = MessageDigest.getInstance("MD5");
    byte[] digest = md.digest(input.getBytes("UTF-8"));
    StringBuilder sb = new StringBuilder();
    for (byte b : digest) sb.append(String.format("%02x", b & 0xff));
    return sb.toString();  // 32位小写
}

常见签名问题排查

错误现象原因解决方法
接口返回 code: 1003, msg: "签名错误"签名计算有误对照上方签名公式检查拼接方式、时间戳位数、大小写
拼接用了 app_id=xxx&app_secret=xxx错误使用键值对拼接直接拼接值,不要加key=,正确:app_id + app_secret + timestamp
timestamp 传了13位毫秒级时间戳改为10位秒级:time()(PHP)/int(time.time())(Python)
sign 结果是大写MD5输出格式错误转为32位小写
回调验签不通过字段顺序错误或漏字段必须按key升序,动态遍历所有非空字段,不要写死字段名
提示"提交时间与系统时间相差超过60秒"服务器时间不准检查服务器时间为北京时间

API目录

分类接口中文名描述
下单接口统一下单主扫,C扫B。消费者主动扫码支付。
查询接口订单查询查询订单支付状态
退款接口订单退款对已支付订单发起退款
退款查询退款查询查询退款状态

公共请求参数

所有接口均需携带以下公共参数:

变量名必填限制类型说明
app_id32string应用APPID,在商户后台「密钥管理」中获取
timestamp10int请求时间戳,10位精确到秒
sign32string请求参数签名,签名规则
是128string支付成功后同步跳转地址,不允许携带参数 user_ip是32string买家客户端IP,必须传入真实买家IP description否64string商品标题,默认"测试订单" sub_mch_id否32string指定二级商户号(特殊场景使用)

请求数据示例

{
  "app_id": "app_37b2ee23d733b7cb",
  "timestamp": 1717728000,
  "sign": "e10adc3949ba59abbe56e057f20f883e",
  "out_order_no": "M202606060001",
  "amount": "1.00",
  "notify_url": "https://your-site.com/notify",
  "return_url": "https://your-site.com/return",
  "user_ip": "123.456.789.012",
  "description": "商品标题"
}

返回参数

变量名必填类型说明
codeint返回状态,0=成功,其它均为失败
msgstring返回信息
data.order_nostring平台订单号
data.pay_urlstring支付链接,跳转给用户完成支付

返回数据示例

{
  "code": 0,
  "msg": "success",
  "data": {
    "order_no": "SF20260606123456XXXXXX",
    "pay_url": "https://zf.qqtkm.com/pay/go/SF20260606123456XXXXXX"
  }
}

2、订单查询

支付完成后可调用该接口查询订单状态。

GET /api/pay/query/:order_no

请求参数(Query String)

变量名必填类型说明
app_idstring应用APPID
timestampint10位秒级时间戳
signstring签名

请求示例

GET /api/pay/query/SF20260606123456XXXXXX?app_id=app_xxx&timestamp=1717728000&sign=abc123

返回参数

变量名必填类型说明
codeint返回状态,0=成功
msgstring返回信息
data.order_nostring平台订单号
data.out_order_nostring商户订单号
data.trade_nostring微信交易号
data.amountdecimal订单金额
data.feedecimal手续费
data.statusstring订单状态:pending/paid/refunded/closed
data.paid_atstring支付成功时间
data.created_atstring订单创建时间

3、订单退款

支付完成后可调用该接口进行退款。当前仅支持全额退款。

POST /api/pay/refund

请求参数

变量名必填限制类型说明
out_order_no64string商户订单号

请求数据示例

{
  "app_id": "app_37b2ee23d733b7cb",
  "timestamp": 1717728000,
  "sign": "e10adc3949ba59abbe56e057f20f883e",
  "out_order_no": "M202606060001"
}

返回参数

变量名必填类型说明
codeint0=退款成功,其它均为失败
msgstring返回信息

4、订单退款查询

退款发起后,可通过订单查询接口查看退款状态。当订单状态为 refunded 时表示退款成功。

💡 退款状态通过订单查询接口获取,data.status 字段值:refund_failed=退款失败,refunded=退款完成。

5、异步交易通知

由于诸多因素都会影响到支付结果的通知,推送可能存在延迟,并且不能保证100%推送成功。故强烈建议主接交易查询接口。

注意:为保障异步队列正常运行,系统限制异步请求时间5秒内处理完毕并返回正确报文。超过5秒系统自动中断,将会导致您的服务器无法正常收到异步交易通知。建议接收异步通知后2秒内处理完毕并返回正确报文。

如果第一次通知不成功或者返回异常,系统将会重发通知,通知频率:15秒→30秒→60秒→120秒→300秒,最多5次。

通知参数(后期可能根据不同情况新增参数,请做好处理)

变量名必填限制类型说明
out_order_no64string商户订单号
order_no64string平台订单号
trade_no64string微信交易号
amount11decimal订单金额
status10string订单状态,固定值 paid=支付成功
paid_at32string支付成功时间(ISO 8601格式)
sign32string签名,验签方式

通知数据示例

{
  "out_order_no": "M202606060001",
  "order_no": "SF20260606123456XXXXXX",
  "trade_no": "4200001234202606061234567890",
  "amount": "1.00",
  "status": "paid",
  "paid_at": "2026-06-06T12:00:00.000Z",
  "sign": "a1b2c3d4e5f6..."
}
注意:
  1. 所有通知参数除空值与签名串外均需加入验签字段,通知参数后期可能会增加,验签时请动态遍历,禁止固定写死验签字段。
  2. 接收到通知并处理成功后需返回 "success""SUCCESS"
  3. 回调验签请参照验签规则,使用 app_secret 进行验签。

6、退款异步通知

退款成功后,平台同样向商户的 notify_url 发送POST通知。通知规则与交易通知一致,5秒超时限制,处理成功后返回 "success"

通知参数

变量名必填限制类型说明
out_order_no64string商户订单号
order_no64string平台订单号
trade_no64string微信交易号
amount12string原订单金额
refund_amount12string退款金额
status10stringrefunded=退款完成, refunding=退款中
refunded_at32string退款完成时间(退款中为null)
sign32string签名

7、错误码说明

错误码说明
0成功
1001缺少鉴权参数(app_id/timestamp/sign)
1003签名错误(最常见的接入问题,请检查签名算法)
2001参数错误(金额/订单号等)
2002应用ID不存在或已禁用
2003无可用商户号
2004订单不存在 / 订单已退款
2005接口已关闭
2006商户订单号重复
2010订单已标记线下退款,无法重复退款
3001上游支付未配置
3002上游下单失败
500系统内部错误
💡 接入帮助:如遇接入问题,请重点检查签名计算方式。可使用商户后台的「密钥管理」功能查看 app_id 和 app_secret。