商户服务端对接文档

聚合支付 开放接口

版本 1.0 · 2026-10-21 · 5 个接口 · 请求签名 HMAC-SHA256
接口根地址https://qcsj88888.com/api/v1
请求方式POST,application/json
金额单位分,整数(100 = 1.00 元)
成功标志响应体 code = 0

1 请求约定

所有接口用 POST 提交 JSON,响应也是 JSON。每个请求带三个请求头:

请求头值
Content-Typeapplication/json
AuthorizationApiKey <你的 api_key>,注意 ApiKey 后面有一个空格
Signature对请求体算出的 HMAC-SHA256,64 位小写十六进制,算法见 §2

响应包为 {"code":0,"msg":"成功","data":{...},"status_code":200,"success":true}。只看 code 和 data:code=0 成功,业务数据在 data;code≠0 失败,原因看 msg。status_code、success 与 code 重复,可忽略,下文示例省略这两个字段。HTTP 200 不代表成功。

用途方法 + 路径(拼在根地址后面)
统一下单POST /payment/openapi/unified/order/create
查询订单POST /payment/openapi/unified/order/query
发起退款POST /payment/openapi/unified/refund
查询退款POST /payment/openapi/unified/refund/query
获取渠道用户 ID(仅 JSAPI / 小程序)POST /payment/openapi/unified/channel-user/jump-url

商户号 mch_no:租户只有一个商户号时不传。有多个商户号时,下单和获取渠道用户 ID 必须传;查单、退款、查退款按单号寻址,不必传。

2 签名

签名只算请求体的顶层字段,与 URL、请求头、时间戳无关。

  1. 取请求体的顶层字段。
  2. 去掉 sign、null、空字符串、空对象 {}、空数组 []。
  3. 按字段名 ASCII 升序排列。
  4. 每个值先 URL 编码(RFC 3986:空格 %20、星号 %2A、波浪号保留)。对象和数组先按键排序输出紧凑 JSON(无空格、中文不转义),再整体编码。
  5. 用 & 拼成 k=v&k=v。
  6. 以 api_secret 为密钥做 HMAC-SHA256,取小写十六进制,放入 Signature 头。
from urllib.parse import quote
import hashlib, hmac, json


def sign(body: dict, api_secret: str) -> str:
    parts = []
    for key in sorted(body):
        value = body[key]
        if key == "sign" or value is None or value == "" or value == {} or value == []:
            continue
        if isinstance(value, (dict, list)):
            text = json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
        elif isinstance(value, bool):
            text = "true" if value else "false"
        else:
            text = str(value)
        parts.append(key + "=" + quote(text, safe=""))
    raw = "&".join(parts)
    return hmac.new(api_secret.encode(), raw.encode(), hashlib.sha256).hexdigest()
密钥只在服务端使用。不要把 api_secret 写进浏览器、App 或前端仓库。签名算的是你实际发出的那个请求体,改了任何字段都要重算。

3 下单

3.1 必填参数

字段类型说明
out_trade_nostring(64)你的订单号,幂等键。同一租户内唯一。
amountinteger金额,单位分,必须大于 0。
way_codestring(16)支付方式,见 §4。
subjectstring(128)商品标题。
mch_nostring(64)多商户时必传;单商户可空。

3.2 可选参数

字段类型说明
bodystring(512)商品描述。不传时用 subject。
notify_urlstring(512)异步通知地址,必须是公网可访问的 http/https 地址。见 §10。
return_urlstring(512)支付完成后浏览器跳回的地址(部分支付方式使用)。
expired_timeinteger订单有效期,单位秒,默认 7200(2 小时)。
client_ipstring(64)用户终端 IP。不传时取请求来源 IP。
ext_paramstring(512)透传参数,上游原样返回。
channel_extraobject支付方式附加参数,见 §4。

3.3 完整调用示例

import requests, uuid

BASE = "https://qcsj88888.com/api/v1"
API_KEY = "你的 api_key"
API_SECRET = "你的 api_secret"


def post(path: str, body: dict) -> dict:
    headers = {
        "Content-Type": "application/json",
        "Authorization": "ApiKey " + API_KEY,
        "Signature": sign(body, API_SECRET),   # sign() 见 §2
    }
    resp = requests.post(BASE + path, json=body, headers=headers, timeout=15)
    return resp.json()


body = {
    "out_trade_no": "T" + uuid.uuid4().hex[:16],
    "amount": 100,                     # 分
    "way_code": "QR_CASHIER",
    "subject": "测试商品",
    "notify_url": "https://example.com/pay/notify",
}
print(post("/payment/openapi/unified/order/create", body))

3.4 成功响应

{
  "code": 0,
  "msg": "成功",
  "data": {
    "order_no": "UP1960000000000000001",
    "out_trade_no": "T20261020001",
    "way_code": "QR_CASHIER",
    "amount": 100,
    "refundable_amount": 100,
    "trade_status": "0",
    "pay_data_type": "codeUrl",
    "pay_data": "https://qr.example.com/xxx",
    "created_time": "2026-10-20T12:00:00+08:00"
  }
}

order_no 是平台订单号。out_trade_no 是你的单号,后续查单、退款都用它。把 pay_data 交给用户完成支付,渲染方式见 §4.2。

4 支付方式与 pay_data

4.1 支持的 way_code

way_code场景channel_extra
QR_CASHIER聚合扫码,用户扫你可选 payDataType:codeUrl / codeImgUrl
AUTO_BAR ALI_BAR WX_BAR YSF_BAR条码支付,你扫用户(聚合 / 支付宝 / 微信 / 云闪付)必传 {"auth_code":"付款码"}
ALI_JSAPI支付宝生活号必传 {"buyerUserId":"..."}
WX_JSAPI微信公众号必传 {"openid":"..."};服务商模式加 wxAppId
WX_LITE微信小程序必传 {"openid":"..."};服务商模式加 wxAppId
YSF_JSAPI云闪付 JSAPI可选 {"openid":"..."}
ALI_APP WX_APP原生 APP 支付(支付宝 / 微信)不用传
ALI_WAP支付宝手机网站可选 payDataType:form / codeImgUrl / payUrl
ALI_PC支付宝电脑网站可选 payDataType:form / payUrl
ALI_QR支付宝二维码可选 payDataType:codeUrl / codeImgUrl
WX_NATIVE微信扫码可选 payDataType:codeUrl / codeImgUrl
WX_H5微信 H5可不传;已拿到 openid 时可传

way_code 大小写敏感。条码类的付款码可写 auth_code,也可写 authCode。付款码、openid、buyerUserId 只用于本次请求:平台不落库、不写日志。

4.2 pay_data 怎么用

pay_data_typepay_data 内容用法
codeUrl支付链接生成二维码给用户扫
codeImgUrl二维码图片 URL放进 <img src>
payUrl支付页链接浏览器跳转
formHTML 表单字符串作为独立页面提交;不要插进现有页面
wxappJSON 字符串,微信支付参数JSON.parse 后传给 WeixinJSBridge.invoke('getBrandWCPayRequest', ...)
只渲染 http/https 地址。把 pay_data 当数据,不要当代码执行。form 是完整表单 HTML,插入页面会破坏现有 DOM。

5 幂等与失败重试

6 查询订单

{"out_trade_no": "T20261020001"}

也可以传 {"order_no": "UP1960000000000000001"}。两个都传时以 order_no 为准。响应的 data 与下单相同。

trade_status含义
0待支付
1支付中
2支付成功(之后可能变成 5 已退款)
3支付失败
4已撤销
5已退款
6已关闭

3 / 4 / 5 / 6 是终态。建议每 3 秒查一次,到终态停止。支付结果以查单或异步通知中的 trade_status 为准。

7 发起退款

{
  "out_trade_no": "T20261020001",
  "out_refund_no": "R20261020001",
  "refund_amount": 100,
  "refund_reason": "测试退款",
  "notify_url": "https://example.com/refund/notify"
}

定位订单用 order_no 或 out_trade_no,至少传一个,都传时以 order_no 为准。mch_no 可选,传了必须与订单一致。

字段类型说明
out_refund_nostring(64)你的退款单号,幂等键。
refund_amountinteger退款金额,分。
refund_reasonstring(64)退款原因,必填。
notify_urlstring(512)本笔退款的通知地址,可选。
{
  "code": 0,
  "msg": "成功",
  "data": {
    "refund_no": "UR1960000000000000001",
    "out_refund_no": "R20261020001",
    "order_no": "UP1960000000000000001",
    "mch_no": "M202610200001",
    "amount": 100,
    "state": "1",
    "created_time": "2026-10-20T12:05:00+08:00"
  }
}
state含义
0退款生成
1退款中
2退款成功
3退款失败
4退款关闭

2 / 3 / 4 是终态。

8 查询退款

{"out_refund_no": "R20261020001"}

也可以传 {"refund_no": "UR1960000000000000001"}。两个都传时以 refund_no 为准。响应与退款相同。不要只等通知:主动查到 state=2 再确认退款成功。

9 获取渠道用户 ID(JSAPI / 小程序)

WX_JSAPI、WX_LITE、ALI_JSAPI 需要先拿渠道用户 ID。YSF_JSAPI 可选。上游签名要用 api_secret,所以这一步必须由服务端换取 URL,浏览器不接触密钥。

{
  "mch_no": "M202610200001",
  "redirect_url": "https://example.com/channel/callback",
  "ext_param": "order=1"
}
{"code": 0, "msg": "成功", "data": {"url": "https://...签名后的上游地址"}}

把用户浏览器跳到返回的 url。授权完成后,redirect_url 会带上 channelUserId(微信还会带 channelAppId):

if_code 不用传,平台固定使用 fuioupay。

10 异步通知

平台用 form POST(application/x-www-form-urlencoded)推送,固定三个字段:

字段说明
notify_id事件 ID。同一事件重试时不变,用它去重。
timestamp毫秒时间戳。
contentJSON 字符串,结构见下。

支付事件(event=ORDER):

{
  "event": "ORDER",
  "order": {
    "order_no": "UP1960000000000000001",
    "out_trade_no": "T20261020001",
    "mch_no": "M202610200001",
    "way_code": "QR_CASHIER",
    "amount": 100,
    "refunded_amount": 0,
    "refundable_amount": 100,
    "trade_status": "2",
    "upstream_pay_order_id": "19522112587029827620865",
    "upstream_channel_order_no": "4200001234202610200000000000",
    "pay_time": "2026-10-20T12:00:00+08:00",
    "err_code": null,
    "err_msg": null
  },
  "upstream": {}
}

退款事件(event=REFUND):

{
  "event": "REFUND",
  "refund": {
    "refund_no": "UR1960000000000000001",
    "out_refund_no": "R20261020001",
    "order_no": "UP1960000000000000001",
    "amount": 100,
    "state": "2",
    "upstream_refund_order_id": "...",
    "success_time": "2026-10-20T12:06:00+08:00",
    "err_code": null,
    "err_msg": null
  },
  "order": { "...": "订单当前快照,字段同 ORDER 事件" },
  "upstream": {}
}

upstream 是上游回调字段的白名单投影(payOrderId、state、channelOrderNo 等),没有快照时是空对象。

10.1 推送时机

事件什么时候推
ORDER订单状态变成 2 / 3 / 4 / 6
ORDER + REFUND全额退款导致订单变成 5,两条都推
REFUND退款状态变成 2 / 3 / 4
不推订单 0 / 1、退款 0 / 1
只推 REFUND部分退款成功、订单仍是 2;新的 refunded_amount 从订单快照读

10.2 接收规则

11 错误码与排查

HTTPcode含义处理
2000成功处理 data
200400业务参数不合法看 msg
200404订单或退款单不存在核对单号;确认是同一租户
200409幂等键已存在且参数不一致换单号,或先查询确认原单
200500上游失败或超时不要换单号,先查单
400400JSON 格式错误或字段校验失败看 msg 里的字段名
40110401认证失败检查 ApiKey 前缀、key、签名
404404路径不存在检查 URL 拼写
500500服务端异常稍后重试;持续出现联系平台

11.1 常见问题

12 联调清单

聚合支付开放接口 v1 · 2026-10-21 · 接口如有变更以平台通知为准