Developer Docs

支付 API 接入文档

统一的 JSON 支付网关:签名下单、订单查询、退款与异步通知。RESTful 语义、MD5 签名、金额单位为分,十分钟完成接入。

1获取密钥

商户后台 → 接口信息,获取 AppKeyAppSecret

2签名下单

MD5 大写签名后 POST /api/gateway/orders,跳转收银台。

3接收通知

验签异步通知,应答 SUCCESS 后发货,完成闭环。

Guide

接入须知

项目说明
网关地址https://当前站点域名(下文 {BASE} 代指)
请求方式HTTP POST,请求体 JSON(UTF-8)
请求头Content-Type: application/json · X-App-Key: {商户AppKey}
金额单位(10.00 元传 1000
时间格式RFC3339(UTC,带 Z 后缀)
统一应答{"code":0,"message":"ok","data":{…}},code=0 成功,非 0 时 message 为失败原因
已在用彩虹易支付 Pro?下载官方插件上传到易支付站点 plugins 目录,即可把本平台作为上游支付通道,见下方「易支付插件」章节。
Guide

签名规则

  1. 取除 sign 外所有值非空的参数;
  2. 按参数名 ASCII 升序排序,拼接为 k1=v1&k2=v2…
  3. 末尾拼接 &key={AppSecret}
  4. MD5 取 32 位大写十六进制即为 sign
sign-demo
raw  = "amount=1000&merchantOrderNo=NO123&subject=测试" + "&key=" + secret
sign = MD5(raw).大写
API

提交支付

POST{BASE}/api/gateway/orders

请求参数

参数必填类型说明
merchantOrderNo必填string商户系统内唯一订单号
amount必填int支付金额,单位分(10.00 元传 1000)
subject必填string商品标题
notifyUrl必填string异步通知地址,必须 https 公网地址
payMethod可选stringalipay / wxpay / qqpay;留空由收银台选择
channelCode可选string指定支付通道编码,通常留空由平台路由
returnUrl可选string支付完成回跳地址(仅展示用)
expireMinutes可选int订单有效期(分钟),默认 5
extra可选string附加参数 JSON,可传 openid / buyerId / payer
sign必填string按签名规则计算

请求示例

request.json
POST {BASE}/api/gateway/orders
Content-Type: application/json
X-App-Key: 1001

{
  "merchantOrderNo": "NO1756512345",
  "amount": 1000,
  "subject": "测试商品",
  "notifyUrl": "https://商户域名/notify",
  "payMethod": "alipay",
  "sign": "A1B2C3..."
}

返回示例

response.json
{
  "code": 0,
  "message": "ok",
  "data": {
    "orderNo": "P1756512345678",
    "merchantOrderNo": "NO1756512345",
    "payUrl": "https://...",
    "payType": "page",
    "cashierUrl": "https://平台域名/pay/P1756512345678"
  }
}
幂等:同一 merchantOrderNo 原单待支付时返回原单;已终态则报「merchantOrderNo 已存在」。推荐跳转 cashierUrl 聚合收银台。
API

查询订单

POST{BASE}/api/gateway/orders/query
参数必填类型说明
orderNo二选一string平台订单号
merchantOrderNo二选一string商户订单号
sign必填string按签名规则计算

返回示例

response.json
{
  "code": 0,
  "data": {
    "orderNo": "P1756512345678", "merchantOrderNo": "NO1756512345",
    "amount": 1000, "fee": 0, "actualAmount": 1000, "refundedAmount": 0,
    "status": "SUCCESS",
    "channelCode": "...", "payMethod": "alipay",
    "createdAt": "2026-08-30T02:00:00Z",
    "paidAt": "2026-08-30T02:01:00Z"
  }
}

查询待支付订单时会同步向上游查证一次,上游已支付会自动补单并返回最新状态。

API

退款

POST{BASE}/api/gateway/refunds
参数必填类型说明
orderNo / merchantOrderNo二选一string目标订单
refundAmount必填int退款金额(分);支持部分退款,累计不可超原单金额
reason可选string退款原因
sign必填string按签名规则计算

SUCCESS / PARTIAL_REFUND 状态可退,返回 data.refundNo(R 开头)。

Callback

异步通知

支付完成后平台向 notifyUrl 发起 POST(Content-Type: application/json):

notify.json
{
  "orderNo": "P1756512345678",
  "merchantOrderNo": "NO1756512345",
  "amount": "1000",
  "status": "SUCCESS",
  "channelCode": "...",
  "payTime": "2026-08-30T02:01:00Z",
  "sign": "..."
}
步骤说明
验签剔除 sign 后按签名规则重算比对;一致且 status=SUCCESS 后再发货
应答HTTP 200,完整纯文本 SUCCESS(兼容 OK),忽略大小写及首尾空白。不要返回 JSON、HTML 或包含其他文字的应答;响应读取失败或超过 4096 字节均不算成功。
重试商户未正确应答时,每轮最多尝试 3 次(含首次);无持久化自动补发计划。商户必须按订单号幂等处理。
回跳returnUrl 为浏览器跳转,仅展示用,不可作为发货依据
Reference

订单状态

状态含义
PENDING待支付
SUCCESS已支付
FAILED支付失败
CLOSED已关闭
REFUNDING退款中
REFUNDED已全额退款
PARTIAL_REFUND部分退款
Plugin

易支付插件

正在使用彩虹易支付 Pro?无需改造现有系统:下载本平台官方支付插件,上传到你易支付网站的 plugins 目录,即可把本平台添加为上游支付通道。

安装步骤
1. 下载插件压缩包(下方「文档下载」卡片),解压得到 fourpay 文件夹
2. 将 fourpay 文件夹上传到易支付网站根目录的 plugins/ 下
3. 易支付后台 → 支付接口 → 支付插件 → 刷新插件列表 → 启用「聚合支付云」
4. 编辑通道,填写:网关地址 / 商户AppKey / 商户AppSecret
5. 保存后在支付方式中勾选 支付宝 / 微信 / QQ 钱包
要求:易支付站点需 HTTPS(平台异步通知要求 https 地址);支持支付方式 alipay / wxpay / qqpay;支持后台发起退款。
SDK

示例代码

PayDemo.php
<?php
$base = 'https://平台域名'; $appKey = '商户AppKey'; $secret = '商户AppSecret';
function sign_json(array $p, string $secret): string {
  unset($p['sign']); ksort($p); $pairs = [];
  foreach ($p as $k => $v) { if ($v !== '' && $v !== null) $pairs[] = "$k=$v"; }
  return strtoupper(md5(implode('&', $pairs) . '&key=' . $secret));
}
$order = ['merchantOrderNo' => 'NO'.time(), 'amount' => 1000, 'subject' => '测试商品',
          'notifyUrl' => 'https://商户域名/notify', 'payMethod' => 'alipay'];
$order['sign'] = sign_json($order, $secret);
$ch = curl_init($base.'/api/gateway/orders');
curl_setopt_array($ch, [CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-App-Key: '.$appKey],
  CURLOPT_POSTFIELDS => json_encode($order), CURLOPT_RETURNTRANSFER => true]);
$resp = json_decode(curl_exec($ch), true);
if ($resp['code'] === 0) header('Location: '.$resp['data']['cashierUrl']);
// 回调验签(notify.php):
// $input = json_decode(file_get_contents('php://input'), true);
// $sign = $input['sign']; unset($input['sign']);
// if (sign_json($input, $secret) === $sign && $input['status'] === 'SUCCESS') { /* 发货 */ echo 'SUCCESS'; }
pay_demo.py
import hashlib, json, time, urllib.request
BASE, APPKEY, SECRET = 'https://平台域名', '商户AppKey', '商户AppSecret'
def sign(p):
    items = sorted((k, str(v)) for k, v in p.items() if k != 'sign' and str(v) != '')
    return hashlib.md5(('&'.join(f'{k}={v}' for k, v in items) + '&key=' + SECRET).encode()).hexdigest().upper()
p = {'merchantOrderNo': 'NO%d' % time.time(), 'amount': 1000, 'subject': '测试商品',
     'notifyUrl': 'https://商户域名/notify', 'payMethod': 'alipay'}
p['sign'] = sign(p)
req = urllib.request.Request(BASE + '/api/gateway/orders', data=json.dumps(p).encode(),
    headers={'Content-Type': 'application/json', 'X-App-Key': APPKEY})
print(json.load(urllib.request.urlopen(req))['data']['cashierUrl'])
# 回调验签:同一 sign() 重算比对,status==SUCCESS 后发货并响应 SUCCESS
pay-demo.js
const crypto = require('crypto');
const BASE = 'https://平台域名', APPKEY = '商户AppKey', SECRET = '商户AppSecret';
const sign = p => {
  const raw = Object.keys(p).filter(k => p[k] !== '' && p[k] != null && k !== 'sign')
    .sort().map(k => k + '=' + p[k]).join('&') + '&key=' + SECRET;
  return crypto.createHash('md5').update(raw).digest('hex').toUpperCase();
};
const p = { merchantOrderNo: 'NO' + Date.now(), amount: 1000, subject: '测试商品',
  notifyUrl: 'https://商户域名/notify', payMethod: 'alipay' };
p.sign = sign(p);
const r = await fetch(BASE + '/api/gateway/orders', {
  method: 'POST', headers: { 'Content-Type': 'application/json', 'X-App-Key': APPKEY },
  body: JSON.stringify(p) });
const j = await r.json();
console.log(j.data.cashierUrl);
// 回调验签(Express):app.post('/notify', express.json(), (req, res) => {
//   const { sign: s, ...p } = req.body;
//   if (sign(p) === s && p.status === 'SUCCESS') { /* 发货 */ res.send('SUCCESS'); }
// });
PayDemo.java
import java.net.URI; import java.net.http.*; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.util.*;

public class PayDemo {
    static final String BASE = "https://平台域名", APPKEY = "商户AppKey", SECRET = "商户AppSecret";
    static String sign(Map<String, String> p) throws Exception {
        TreeMap<String, String> t = new TreeMap<>(p); t.remove("sign");
        StringBuilder sb = new StringBuilder();
        t.forEach((k, v) -> { if (v != null && !v.isEmpty()) sb.append(k).append('=').append(v).append('&'); });
        sb.append("key=").append(SECRET);
        byte[] d = MessageDigest.getInstance("MD5").digest(sb.toString().getBytes(StandardCharsets.UTF_8));
        StringBuilder hex = new StringBuilder();
        for (byte b : d) hex.append(String.format("%02X", b));
        return hex.toString();
    }
    public static void main(String[] args) throws Exception {
        Map<String, String> p = new LinkedHashMap<>();
        p.put("merchantOrderNo", "NO" + System.currentTimeMillis());
        p.put("amount", "1000"); p.put("subject", "测试商品");
        p.put("notifyUrl", "https://商户域名/notify"); p.put("payMethod", "alipay");
        p.put("sign", sign(p));
        HttpRequest req = HttpRequest.newBuilder()
            .uri(URI.create(BASE + "/api/gateway/orders"))
            .header("Content-Type", "application/json").header("X-App-Key", APPKEY)
            .POST(HttpRequest.BodyPublishers.ofString(new JSONObject(p).toString())).build();
        System.out.println(HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString()).body());
    }
}
main.go
func sign(p map[string]string, secret string) string {
    keys := []string{}
    for k, v := range p { if v != "" && k != "sign" { keys = append(keys, k) } }
    sort.Strings(keys)
    parts := []string{}
    for _, k := range keys { parts = append(parts, k+"="+p[k]) }
    sum := md5.Sum([]byte(strings.Join(parts, "&") + "&key=" + secret))
    return strings.ToUpper(hex.EncodeToString(sum[:]))
}
// 下单:POST JSON 到 /api/gateway/orders,头带 X-App-Key
// 回调验签:同一 sign() 重算比对,status==SUCCESS 后发货并响应 SUCCESS
FourPay.cs
public static string Sign(Dictionary<string, string> p, string secret)
{
    var raw = string.Join("&", p
        .Where(kv => kv.Key != "sign" && !string.IsNullOrEmpty(kv.Value))
        .OrderBy(kv => kv.Key, StringComparer.Ordinal)
        .Select(kv => kv.Key + "=" + kv.Value)) + "&key=" + secret;
    using var md5 = MD5.Create();
    return Convert.ToHexString(md5.ComputeHash(Encoding.UTF8.GetBytes(raw)));
}
// 下单:POST JSON 到 /api/gateway/orders,头带 X-App-Key(.NET 6+,无第三方依赖)
// 回调验签:同一 Sign() 重算比对,status==SUCCESS 后发货并响应 SUCCESS
pay_demo.rb
require 'digest'
def sign(p, secret)
  raw = p.reject { |k, v| k.to_s == 'sign' || v.nil? || v.to_s.empty? }
         .sort_by { |k, _| k.to_s }
         .map { |k, v| "#{k}=#{v}" }.join('&') + '&key=' + secret
  Digest::MD5.hexdigest(raw).upcase
end
# 下单:POST JSON 到 /api/gateway/orders,头带 X-App-Key(仅标准库)
# 回调验签:同一 sign() 重算比对,status==SUCCESS 后发货并响应 'SUCCESS'
Download

文档下载

📄平台 API 完整文档(Markdown)单文件交付给开发或 AI 阅读:参数表、签名、示例、FAQ 🧩易支付 Pro 支付插件(xypay)上传到易支付站点 plugins 目录,把本平台添加为上游支付通道
FAQ

常见问题

现象原因与处理
提示签名错误检查空值是否剔除、是否按 ASCII 升序、是否以 &key= 结尾、结果是否大写
notifyUrl 无效 / 回调 301回调地址必须为最终 https 地址;http 会被强制升级导致 301
应答 200 但仍显示回调失败应答体须完整匹配纯文本 SUCCESS 或兼容的 OK,不能返回 JSON、HTML、NOT SUCCESS 等内容。易支付兼容回调仅接受纯文本 success。
merchantOrderNo 已存在更换商户订单号,或改用查询接口
无可用支付通道检查通道状态为启用、插件配置完整
风控拦截检查单笔/单日限额、IP 黑名单与下单频率(系统设置 → 风控管理)