支付接口开发文档

标准 HTTP 表单 + JSON 响应,与开发语言无关。下单、查单、回调验签共用同一套签名规则,PHP、Java、Python、Go、Node.js 均可直接对接。

01接入准备

开始对接前,先在商户后台拿到以下三项凭证。

1

接口地址(apiurl):https://pay.yunpay.site,后续所有接口均基于该地址。

2

商户 ID(pid):注册后自动生成,登录商户后台首页即可查看。

3

商户密钥(key):用于计算签名,在商户后台「接口信息」中获取。密钥只保存在你的服务端,切勿暴露在前端页面或 APP 中。

下单接口中的 notify_url 与 return_url 所在域名,需先在商户后台完成接口域名绑定,否则下单会被拒绝。

02签名算法(V1·MD5)

所有接口的 sign 字段都按以下规则生成,请求与回调验签使用同一算法。需要更高安全等级的 RSA 签名,请直接看 第 3 章 V2 接口。

1

将请求参数按 参数名 ASCII 码从小到大排序。

2

剔除 sign、sign_type 以及值为空的参数。

3

按 k1=v1&k2=v2&... 拼接(末尾不留 &),不做 URL 编码。

4

在字符串末尾直接拼接商户密钥,对整串做 MD5(32 位小写)即得 sign。

SIGN EXAMPLE
// 待签名参数(已剔除 sign / sign_type / 空值,并按 ASCII 升序)
money=9.90&name=VIP会员&notify_url=https://demo.com/notify.php&out_trade_no=202610270001&pid=1001&return_url=https://demo.com/return.php&type=alipay

// 拼接商户密钥(假设 key 为 abcd1234)后取 MD5
sign = md5("money=9.90&name=VIP会员&...&type=alipayabcd1234")
验签时使用同样的算法重新计算并与收到的 sign 比对,完全一致才可信。V1 适合快速接入;若需更高安全等级,请切换到 V2(RSA) 并上传商户公钥。

V2V2 接口(RSA 密钥对接)

V2 是平台提供的高安全签名模式,使用 RSA 非对称密钥。与 V1(MD5 共享密钥)不同,V2 下双方各持密钥:你用商户私钥给请求签名、平台用商户公钥验签;平台用平台私钥给响应/回调签名、你用平台公钥验签。即使一方私钥泄露也不会危及另一方。

一、两套密钥分别是什么 核心

密钥保管方用途在哪里获取
平台私钥平台给平台返回的响应、异步通知签名平台保管,你拿不到
平台公钥你(仅用于验签)验证平台发来的响应/通知是否真实商户后台「接口信息 → V2接口」中展示
商户私钥你(务必离线保管)给你的请求签名后台「生成商户RSA密钥对」时展示给你 / 你自己用 openssl 生成
商户公钥平台平台验证你的请求签名你上传到商户后台「接口信息 → V2接口 → 商户公钥」
商户私钥绝不能出现在前端、APP 或任何可被下载的文件中,只能放在你的服务端。泄露后立即在后台「重置商户RSA密钥对」。

二、在后台开启 V2

商户后台「接口信息 → 签名方式开关」有三个档位:

0

开启 MD5+RSA 签名(兼容模式):V1、V2 都能调用,平台按你请求里的 sign_type 自动识别。适合从 V1 平滑升级到 V2。

1

仅开启 RSA 签名(安全模式):只接受 sign_type=RSA 的请求,提交 MD5 会直接拒绝并返回「该商户只能使用RSA签名类型」。

推荐先保持兼容模式,把 V2 对接跑通后再切到仅 RSA。

三、生成并上传商户密钥对

方式一(推荐,后台一键生成):商户后台「接口信息 → V2接口」点击「生成商户RSA密钥对」,平台生成 2048 位密钥对,自动保存你的商户公钥,并把商户私钥展示给你下载保存(请离线保管)。

方式二(自备密钥):你自己用 openssl 生成,把公钥粘贴到后台「商户公钥」框:

openssl
openssl genrsa -out merchant_private.pem 2048
openssl rsa -in merchant_private.pem -pubout -out merchant_public.pem
# 把 merchant_public.pem 的内容粘贴到后台「商户公钥」,merchant_private.pem 自己保存

四、请求签名(V2)

拼接待签名串的规则与 V1 完全一致:参数按 ASCII 升序 → 剔除 sign / sign_type / 空值 → 拼成 k=v&(末尾去 &,不做 URL 编码)。区别只在最后一步——用商户私钥对该串做 SHA256withRSA 签名,结果 base64 编码后作为 sign,同时传 sign_type=RSA。

PHP · V2 请求签名
function rsaSign(array $params, $privateKeyPem) {
    // 1. 排序 + 剔除 sign / sign_type / 空值
    ksort($params);
    $str = '';
    foreach ($params as $k => $v) {
        if ($k=='sign' || $k=='sign_type' || $v==='' || is_array($v)) continue;
        $str .= $k.'='.$v.'&';
    }
    $str = rtrim($str, '&');
    // 2. SHA256withRSA 签名 + base64
    $pkey = openssl_pkey_get_private($privateKeyPem);
    openssl_sign($str, $sign, $pkey, OPENSSL_ALGO_SHA256);
    return base64_encode($sign);
}
$params['sign_type'] = 'RSA';
$params['sign']      = rsaSign($params, $merchantPrivateKey);
Python · V2 请求签名
import hashlib, base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_private_key

def rsa_sign(params: dict, private_key_pem: str) -> str:
    p = {k: v for k, v in params.items()
         if k not in ('sign', 'sign_type') and v != '' and not isinstance(v, (list, dict))}
    s = '&'.join(f'{k}={p[k]}' for k in sorted(p))
    key = load_pem_private_key(private_key_pem.encode(), password=None)
    sig = key.sign(s.encode(), padding.PKCS1v15(), hashes.SHA256())
    return base64.b64encode(sig).decode()

params['sign_type'] = 'RSA'
params['sign']      = rsa_sign(params, merchant_private_key)

五、验证平台响应 / 异步通知(V2)

平台响应 / 异步通知的签名类型,取决于订单的创建方式(订单 version 字段),而不是你后台的 keytype 设置:

下单方式通知 / 响应签名你用什么验签
通过 ?s= 接口下单(如 api.php?s=pay/submit、api.php?s=pay/create)sign_type=RSA(平台私钥签名)用商户后台展示的平台公钥做 SHA256withRSA 验签
通过传统 submit.php / mapi.php 下单sign_type=MD5(你的商户密钥签名)按第 2 章 V1 规则用商户密钥重算 MD5 比对
也就是说:请求签名永远由你决定(传 sign_type=RSA 即用商户私钥);但平台回推给你的通知用哪种签名,由这笔订单当初怎么建的来决定。下方 RSA 验签代码仅适用于 ?s= 下单产生的 RSA 通知与 ?s= 接口响应。
建议「仅 RSA」模式的商户统一用 ?s= 接口下单,这样请求与通知全程 RSA,验签只认平台公钥。若用 mapi.php 下单,平台仍会以 MD5 回推通知,须用商户密钥做 MD5 验签,与 V2 的 RSA 验签是两套逻辑。
PHP · 验签(平台公钥)
function rsaVerify(array $data, $platformPublicKey) {
    if (!isset($data['sign'])) return false;
    ksort($data);
    $str = '';
    foreach ($data as $k => $v) {
        if ($k=='sign' || $k=='sign_type' || $v==='' || is_array($v)) continue;
        $str .= $k.'='.$v.'&';
    }
    $str = rtrim($str, '&');
    $pkey = openssl_pkey_get_public($platformPublicKey);
    return openssl_verify($str, base64_decode($data['sign']), $pkey, OPENSSL_ALGO_SHA256) === 1;
}
Python · 验签(平台公钥)
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives.serialization import load_pem_public_key

def rsa_verify(data: dict, platform_public_key: str) -> bool:
    d = {k: v for k, v in data.items()
         if k not in ('sign', 'sign_type') and v != '' and not isinstance(v, (list, dict))}
    s = '&'.join(f'{k}={d[k]}' for k in sorted(d))
    key = load_pem_public_key(platform_public_key.encode())
    try:
        key.verify(base64.b64decode(data['sign']), s.encode(),
                   padding.PKCS1v15(), hashes.SHA256())
        return True
    except Exception:
        return False

六、关于 timestamp(时间戳)

通过平台 ?s= 形式的 RESTful 接口(如 pay/submit、pay/create)调用时,需额外传 timestamp(Unix 秒),与平台服务器时间误差须 ≤ 300 秒,否则返回「时间戳字段不正确,请检查服务器时间」。

页面跳转支付(submit.php)与 API 下单(mapi.php)不强制 timestamp,按上面的 RSA 签名规则正常传参即可。注意你自己的服务器时间要准确,避免签名/时间戳校验失败。

04页面跳转支付

用户浏览器跳转到收银台完成付款,适合网页/移动端直接接入。

GET / POST https://pay.yunpay.site/submit.php
参数必填说明
pid必填商户 ID
type选填支付方式:alipay、wxpay 等。留空则跳转收银台由用户自选
out_trade_no必填商户订单号,仅允许字母、数字与 ._-|,需保证唯一
notify_url必填异步通知地址(服务端,POST 不可达时为 GET)
return_url必填支付成功后浏览器跳转地址
name必填商品名称
money必填金额,单位元,最多两位小数,如 9.90
sitename选填站点名称,收银台展示用
param选填自定义回传参数,回调时原样返回
sign / sign_type必填签名 与 签名类型(MD5)
REQUEST EXAMPLE
<!-- 组装参数并计算 sign 后,以 GET 跳转或 POST 表单提交 -->
<form method="post" action="https://pay.yunpay.site/submit.php">
  <input type="hidden" name="pid"          value="1001">
  <input type="hidden" name="type"         value="alipay">
  <input type="hidden" name="out_trade_no" value="202610270001">
  <input type="hidden" name="notify_url"   value="https://demo.com/notify.php">
  <input type="hidden" name="return_url"   value="https://demo.com/return.php">
  <input type="hidden" name="name"         value="VIP会员">
  <input type="hidden" name="money"        value="9.90">
  <input type="hidden" name="sign_type"    value="MD5">
  <input type="hidden" name="sign"         value="{按第 2 节规则计算}">
  <button type="submit">去支付</button>
</form>
return_url 仅作展示跳转,不能作为发货依据;发货必须以异步通知或主动查单结果为准。

05API 下单

服务端直接下单,返回 JSON(含支付跳转链接 / 二维码),适合后端服务与 APP 内拉起支付。

POST https://pay.yunpay.site/mapi.php

请求参数与「页面跳转支付」基本一致,但有两点区别:① clientip(用户真实 IP)为必填,且必须是合法 IPv4/IPv6,否则报「用户IP地址(clientip)不能为空 / 不合法」;② type(支付方式)在 API 下单时为必填(页面跳转留空则由用户自选)。即需 POST:pid、type、out_trade_no、notify_url、return_url、name、money、clientip、sign、sign_type。

返回字段

成功时返回 code=1 与 trade_no,并按支付方式只返回下述其中一个支付字段(不是全部同时返回):

字段出现条件说明
code总是1 成功;-2 下单失败(原因见 msg)
trade_no成功时平台订单号,可用于后续查单
payurl跳转 / H5 支付收银台跳转链接,引导用户打开即可付款
qrcode扫码支付扫码支付二维码内容(可直接生成二维码图片)
html自动提交表单需服务端/前端原样输出的 HTML 表单(自动跳转支付)
urlscheme小程序 / H5 scheme小程序或 H5 深链 scheme 地址
msg失败时失败原因(成功时不返回该字段)
更丰富的结构化返回(pay_type / pay_info)仅由 ?s= 形式的 RESTful 接口提供;mapi.php 统一以 payurl / qrcode / html / urlscheme 之一返回,接到后按字段引导用户即可。
RESPONSE(成功示例,仅含命中字段)
{
  "code": 1,
  "trade_no": "2026102714302512345",
  "payurl": "https://bei2.yunpay.site/pay/submit/2026102714302512345/"
}
RESPONSE(失败示例)
{
  "code": -2,
  "msg": "用户IP地址(clientip)不能为空"
}

06异步通知(notify_url)

订单支付成功后,平台向 notify_url 发起通知。请以通知结果作为发货唯一依据。

通知方式 GET

平台以 GET 方式请求你的 notify_url,附加以下参数:

参数说明
pid商户 ID
trade_no平台订单号
out_trade_no商户订单号
type实际支付方式
name商品名称
money订单金额(数值,如 9.9)
trade_statusTRADE_SUCCESS 表示支付成功
api_trade_no上游渠道订单号(部分通道有值,可能不存在)
param下单时传入的自定义参数(原样回传)
sign / sign_type签名与签名类型(MD5 或 RSA,取决于该订单创建方式,见第 3 章第 5 节)
1

按第 2 节规则对收到的参数本地重新验签,不一致直接忽略。

2

校验 out_trade_no 是否存在于你的订单系统、money 是否与订单金额一致。

3

确认 trade_status 为 TRADE_SUCCESS 后执行发货/入账逻辑。

4

处理成功后输出纯文本 success(不要有任何多余字符);未收到 success 平台会按退避策略自动重试。

通知可能重复到达,发货逻辑必须幂等(同一订单只发货一次)。通知丢失时可通过订单查询接口主动补单。

07订单查询

按平台订单号或商户订单号查询支付结果,用于主动查单补单。

GET https://pay.yunpay.site/api.php?act=order&pid={pid}&key={key}&out_trade_no={商户订单号}

也可用 &trade_no={平台订单号} 替换 out_trade_no,二者传其一即可。

返回字段

字段说明
code1 查询成功,-1 订单不存在,-3 商户ID不存在 / 商户密钥错误 / 该商户只能使用RSA签名类型
msg结果描述,成功时为 succ
pid商户 ID
trade_no / out_trade_no平台订单号 / 商户订单号
api_trade_no / bill_trade_no上游渠道订单号 / 上游流水号(部分通道有值,可能为空 / null)
type支付方式名称
name / money商品名称 / 金额(字符串,如 "123.00")
addtime / endtime下单时间 / 支付完成时间
status0 未支付,1 已支付
buyer付款方账号(部分通道返回)
param下单时的自定义参数(无则为 null)
payurl本笔订单的收银台链接(可用于重新发起支付,无则为 null)
订单查询与商户查询无需签名:直接在 URL 中带上明文 pid + key 即可,平台按密钥做身份校验(请勿在公网前端页面暴露 key)。其中 money / status / pid 等字段以字符串形式返回。

08商户信息查询

查询商户余额、结算方式与订单概况。

GET https://pay.yunpay.site/api.php?act=query&pid={pid}&key={key}
字段说明
code1 查询成功,-3 商户ID不存在 / 商户密钥错误 / 该商户只能使用RSA签名类型
pid商户 ID
key商户密钥(与后台一致,可用于本地校验)
money商户当前余额
active商户状态(1 正常)
type结算方式 ID
account / username结算账号 / 收款人
orders / orders_today / orders_lastday总订单数 / 今日成功 / 昨日成功

09接入示例

下单 + 回调验签的最小可运行示例,可直接作为参考实现。以下示例使用 V1(MD5);若要用 V2(RSA),只需把签名函数替换为第 3 节的 rsaSign() 并将 sign_type 改为 RSA 即可,其余参数完全相同。

PHP · 下单
<?php
function getSign($params, $key) {
    ksort($params);
    $str = '';
    foreach ($params as $k => $v) {
        if ($k == 'sign' || $k == 'sign_type' || $v === '') continue;
        $str .= $k . '=' . $v . '&';
    }
    return md5(substr($str, 0, -1) . $key);
}

$params = [
    'pid'          => '1001',
    'type'         => 'alipay',
    'out_trade_no' => '202610270001',
    'notify_url'   => 'https://demo.com/notify.php',
    'return_url'   => 'https://demo.com/return.php',
    'name'         => 'VIP会员',
    'money'        => '9.90',
];
$params['sign_type'] = 'MD5';
$params['sign'] = getSign($params, '你的商户密钥');

// 页面跳转支付:跳转到 submit.php?http_build_query($params)
// API 下单:POST 到 mapi.php,解析返回 JSON 中的 payurl / qrcode
PHP · 回调验签(notify.php)
<?php
$sign   = $_GET['sign'];
unset($_GET['sign'], $_GET['sign_type']);

if (getSign($_GET, '你的商户密钥') !== $sign) exit('sign error');
if ($_GET['trade_status'] != 'TRADE_SUCCESS') exit('fail');

// 1) 按订单号查出你的订单  2) 比对金额  3) 幂等发货
// $order = db_query($_GET['out_trade_no']);
// if ($order && $order['status'] == 0 && $order['money'] == $_GET['money']) { mark_paid_and_deliver(); }

echo 'success'; // 只输出 success,其他内容视为失败会触发重试
Python · API 下单
import hashlib, requests

def get_sign(params, key):
    items = sorted((k, v) for k, v in params.items()
                   if k not in ('sign', 'sign_type') and v != '')
    s = '&'.join(f'{k}={v}' for k, v in items)
    return hashlib.md5((s + key).encode()).hexdigest()

params = {'pid': '1001', 'type': 'alipay', 'out_trade_no': '202610270001',
          'notify_url': 'https://demo.com/notify.php', 'return_url': 'https://demo.com/return.php',
          'name': 'VIP会员', 'money': '9.90',
          'clientip': '1.2.3.4'}  # clientip 为 API 下单必填,填用户真实 IP
params['sign_type'] = 'MD5'
params['sign'] = get_sign(params, '你的商户密钥')

r = requests.post('https://pay.yunpay.site/mapi.php', data=params).json()
print(r.get('payurl') or r.get('qrcode') or r.get('html') or r.get('urlscheme'))

10常见问题

对接高频问题与排错入口。

报错原因与处理
签名校验失败参数排序/空值剔除有误、密钥不对、或对参数做了 URL 编码。严格按第 2 节(V1)或第 3 节(V2)规则重算
该商户只能使用RSA签名类型后台已将签名方式设为「仅开启RSA签名(安全模式)」,但请求仍用 MD5。改为 sign_type=RSA 并按第 3 节用商户私钥签名;兼容模式下两种都可
签名校验失败,商户公钥错误V2 模式下商户公钥与私钥不匹配或格式不对。重新「生成商户RSA密钥对」或确认上传的公钥与本地私钥是同一对
时间戳字段不正确,请检查服务器时间仅在 ?s= 接口要求 timestamp,且与平台时间差 > 300 秒。校准你的服务器时间
订单号格式不正确out_trade_no 只允许字母、数字与 . _ - |
金额不合法money 必须为纯数字且大于 0,最多两位小数
域名未绑定notify_url / return_url 的域名需先在商户后台完成绑定
回调收不到检查 notify_url 公网可达、无防火墙拦截;同时用订单查询接口做定时补单兜底
重复回调正常现象,发货逻辑按订单号做幂等即可
对接遇到问题?联系技术支持:QQ 3954346809