原生 API · Jeepay · 易支付 API 网关 api.60185.com 开始对接

保护伞II 支付网关 · 商户对接文档

本平台首推保护伞原生支付 API(/api/pay,功能最完整、安全最强:appKey 签名 + timestamp/nonce 防重放)。同时兼容 Jeepay 与 易支付(EPay) 两套商户协议——已在用这两套系统的商户只需改域名、加路径前缀,零代码即可迁移。本页免登录查看,含完整签名算法、请求/响应字段、异步通知与错误码。

对外域名 api.60185.com 文档域名 doc.60185.com 字符集 UTF-8 默认签名 MD5 免登录 · 匿名放行 · 不走 Sa-Token 📮 bt202199@gmail.com ✈️ @umwapr

快速开始 5 分钟

四步跑通第一笔支付:拿凭证 → 生成签名 → 调统一下单 → 查单对账。

① 获取商户凭证
向运营方申请 商户号 mchNo/pid、应用 appId、密钥 appKey/KEY。
② 组装并签名
按协议把非空参数排序拼接,用密钥算出 sign(见 在线计算器)。
③ 发起下单
POST 到统一下单接口,拿回 payUrl/payData 引导用户付款。
④ 查单入账
异步通知不可信:收到回调后必须验签 + 再调查单接口核实(状态/金额一致)才幂等入账。
⚠ 强制要求:订单支付状态的唯一可信源是查单接口。异步通知只能当作“去查单”的触发信号,严禁仅凭回调入账(详见 异步通知)。建议同时部署定时对账任务:每 1–5 分钟把本地未终态订单批量查单一次,避免通知丢失导致掉单。
选协议:新接入首选 保护伞原生 API(功能最全、含防重放);存量商户按原系统选 Jeepay 或 易支付 兼容层。三套签名规则、金额单位、大小写、成功码互不相同,切勿混用。
对比项保护伞原生 API(主)Jeepay 兼容层易支付(EPay) 兼容层
下单入口POST /api/pay/unifiedOrderPOST /api/jeepay/pay/unifiedOrderPOST /api/epay/mapi.php · submit.php
报文格式JSONJSON表单 form(下划线字段)
金额单位元(decimal)分(整数)元(两位小数字符串)
签名MD5 大写 / HMAC_SHA256 / RSA2MD5 大写,尾部 &key=密钥MD5 小写,尾部裸拼密钥
防重放需 timestamp + nonce无无
成功码code=200code=0code=1
商户标识merchantNo + appIdmchNo + appIdpid

接入准备与凭证

对接前需要拿到的三项凭证,以及它们在三套协议(保护伞原生 / Jeepay / 易支付)里的对应关系。

概念说明保护伞原生字段Jeepay 字段易支付字段
商户号平台分配的唯一商户标识merchantNomchNopid
应用 ID一个商户可有多应用;易支付场景固定用 pid 占位appIdappId—(内部 = pid)
应用密钥签名/验签用,务必保存在服务端,切勿下发前端appKeyappKeyKEY
签名方式原生:MD5 / HMAC_SHA256 / RSA2(默认 MD5,且额外需 timestamp + nonce 防重放);Jeepay:MD5 / HMAC_SHA256signTypesignType固定 MD5
凭证获取:商户号 / 应用 / 密钥由运营方在中控后台开通后下发;若收不到通知,请确认已在应用配置里登记有效的 notifyUrl 且该地址公网可达、返回 HTTP 200。

通用约定

  • 协议 / 域名:全部接口经网关 https://api.60185.com,HTTPS 访问;下单类接口匿名放行,鉴权靠签名。
  • 路径规则:网关 /api/xxx 剥离一层 /api 转发到支付服务。原生访问 /api/pay/**,兼容层访问 /api/jeepay/** 与 /api/epay/**。
  • 字符集:统一 UTF-8,中文商品名需正确编码,否则验签会因字节差异失败。
  • 报文格式(JSON 支持):保护伞原生与 Jeepay 兼容层的请求体必须是 JSON 对象(一层 {...},不得包 {"param":...} 外层),并带 Content-Type: application/json;易支付兼容层只接受表单 application/x-www-form-urlencoded(或 GET 查询串),传 JSON 会直接 400。
  • 响应格式:三套协议响应与异步通知统一为 JSON(application/json;charset=UTF-8),验签失败等业务错误也返回 HTTP 200 + JSON 错误码(仅格式错、参数缺失才会抛非 2xx)。因此判定成败要看报文里的 code,不能只看 HTTP 状态码。
  • 时间格式:响应/通知内 yyyy-MM-dd HH:mm:ss。
  • 金额:原生与易支付用元(原生为 decimal、易支付为字符串两位小数),Jeepay 用分(int);平台内部统一转元。
  • 幂等:同一 mchOrderNo / out_trade_no 重复下单返回同一订单;商户订单号请全局唯一。
  • 有效期:订单默认 2 小时失效(原生 expireMinutes / Jeepay expiredTime 秒覆盖;易支付内部固定 120 分钟,均受平台上限约束)。
  • 防重放(仅原生 API):每个请求须带 timestamp(毫秒,±5 分钟内)与 nonce(窗口内唯一随机串),二者均参与签名;兼容层(Jeepay/易支付)不需要。

签名算法

三套协议都以 MD5 为主,但大小写、拼接方式、是否含防重放字段各不相同,这是对接中最容易出错的地方。

保护伞原生签名(MD5 大写 / HMAC_SHA256 / RSA2)

  1. 收集所有非空请求参数(含 timestamp、nonce),排除 sign、signType。
  2. 按参数名 ASCII 升序拼 k1=v1&k2=v2&…。
  3. 按 signType 计算:
    · MD5:尾部拼 &key={appKey} 后取 MD5,转大写;
    · HMAC_SHA256:以 appKey 为密钥对内容串做 HMAC-SHA256,大写十六进制;
    · RSA2:用商户 RSA 私钥对内容串做 SHA256withRSA,结果 Base64(平台用商户公钥验签)。
// 待签串示例(注意 timestamp / nonce 也参与)
amount=0.01&appId=app_xxx&bankCode=9311&currency=CNY&mchOrderNo=ORD20260928001&merchantNo=88001&nonce=8f2c1a&notifyUrl=https://...&payMethod=alipay&subject=test&timestamp=1758960000000&key={appKey}
// MD5 → sign = MD5(上面的串).toUpperCase()

Jeepay 签名(MD5 大写)

  1. 收集所有非空请求参数,排除 sign、signType。
  2. 按参数名 ASCII 升序排列,拼成 k1=v1&k2=v2&…。
  3. 尾部拼接 &key={appKey}。
  4. 对整串做 MD5,结果转大写 = sign。
// 待签串示例
amount=1&appId=app_xxx&currency=cny&mchNo=88001&mchOrderNo=ORD20260928001&notifyUrl=https://...&subject=test&wayCode=ALI_QR&key={appKey}
// sign = MD5(上面的串).toUpperCase()

易支付签名(MD5 小写)

  1. 收集所有非空参数,排除 sign、sign_type 与空值。
  2. 按参数名 ASCII 升序拼 k1=v1&k2=v2&…。
  3. 尾部直接拼接密钥(没有 &key= 前缀)。
  4. MD5 后转小写 = sign。
// 待签串示例(注意结尾直接跟 KEY,无 &key=)
money=0.01&name=test&notify_url=https://...&out_trade_no=ORD001&pid=1001&type=alipay{KEY}
// sign = MD5(上面的串).toLowerCase()
三大陷阱:① 易支付空值参数不参与签名但 Jeepay 会把 amount 等以 null/0 文本参与——保持与示例一致,只传有值字段最稳。② Jeepay 大写 + &key=,易支付小写 + 裸拼。③ 参与签名的字段名大小写敏感(如 signType vs sign_type)。

在线签名计算器 调试神器

纯前端计算,参数与密钥不会离开你的浏览器;用它先手算出一个正确签名,和你代码里的结果比对,秒定位差异。 注:原生 API 的 HMAC_SHA256 / RSA2 需在你服务端用对应库实现,本计算器仅演示 MD5;选“保护伞原生”时记得把 timestamp 与 nonce 一并填入参数。

—
—
verify: md5("abc")=900150983cd24fb0d6963f7d28e17f72

多语言签名 / 请求示例 复制即用

以下为保护伞原生 API(POST /api/pay/unifiedOrder)的签名 + 下单代码:非空参数 ASCII 升序 → 尾随 &key=appKey → MD5 转大写;金额单位元;timestamp 为毫秒;成功码 code=200。把 APP_KEY 换成你的应用密钥即可(密钥只放服务端)。已提供 Java / PHP / Python / Node.js / Go / C++ / .NET(C#) / Lua 八种主流语言的等价实现(Lua 面向 OpenResty / ngx_lua 网关场景),签名规则完全一致。

Java
PHP
Python
Node.js
Go
C++
.NET
Lua
// 仅依赖 JDK;md5() 用标准 MD5 实现(略)
Map<String, String> p = new TreeMap<>();   // TreeMap 保证 key ASCII 升序
p.put("merchantNo", "88001");
p.put("appId", "app_demo");
p.put("mchOrderNo", "ORD20260928001");
p.put("payMethod", "alipay");
p.put("bankCode", "9311");                   // 二级通道编码,参与签名
p.put("amount", "0.01");                     // 单位:元
p.put("currency", "CNY");
p.put("subject", "测试商品");
p.put("notifyUrl", "https://m.example.com/pay/notify");
p.put("timestamp", String.valueOf(System.currentTimeMillis()));  // 毫秒
p.put("nonce", UUID.randomUUID().toString().replace("-", ""));

// 待签串:升序 k=v&k2=v2 之后尾随 &key={appKey}
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> e : p.entrySet()) {
    if (sb.length() > 0) sb.append("&");
    sb.append(e.getKey()).append("=").append(e.getValue());
}
String signSrc = sb + "&key=" + APP_KEY;
p.put("signType", "MD5");
p.put("sign", md5(signSrc).toUpperCase());    // MD5 转大写

// POST https://api.60185.com/api/pay/unifiedOrder  Content-Type: application/json
// 响应 {"code":200,"data":{"payEntryUrl":"https://..."}} → 引导用户打开 payEntryUrl
<?php
$appKey = '你的 appKey';
$p = [
    'merchantNo' => '88001', 'appId' => 'app_demo',
    'mchOrderNo' => 'ORD20260928001', 'payMethod' => 'alipay',
    'bankCode' => '9311',
    'amount' => '0.01', 'currency' => 'CNY', 'subject' => '测试商品',   // 元
    'notifyUrl' => 'https://m.example.com/pay/notify',
    'timestamp' => (string) round(microtime(true) * 1000),   // 毫秒
    'nonce' => bin2hex(random_bytes(16)),
];
ksort($p);                                   // ASCII 升序
$parts = [];
foreach ($p as $k => $v) { $parts[] = "$k=$v"; }
$signSrc = implode('&', $parts) . '&key=' . $appKey;
$p['sign']     = strtoupper(md5($signSrc));  // MD5 大写
$p['signType'] = 'MD5';

$ch = curl_init('https://api.60185.com/api/pay/unifiedOrder');
curl_setopt_array($ch, [
    CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode($p, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
]);
$res = json_decode(curl_exec($ch), true);
// $res['code'] === 200 即成功;用 $res['data']['payEntryUrl'] 引导付款
import time, uuid, hashlib, json, requests

APP_KEY = '你的 appKey'
p = {
    'merchantNo': '88001', 'appId': 'app_demo',
    'mchOrderNo': 'ORD20260928001', 'payMethod': 'alipay',
    'bankCode': '9311',
    'amount': '0.01', 'currency': 'CNY', 'subject': '测试商品',   # 元
    'notifyUrl': 'https://m.example.com/pay/notify',
    'timestamp': str(int(time.time() * 1000)),   # 毫秒
    'nonce': uuid.uuid4().hex,
}
base = '&'.join('%s=%s' % (k, p[k]) for k in sorted(p) if p[k] != '')
sign = hashlib.md5((base + '&key=' + APP_KEY).encode('utf-8')).hexdigest().upper()
p['signType'] = 'MD5'
p['sign'] = sign

r = requests.post('https://api.60185.com/api/pay/unifiedOrder',
                  data=json.dumps(p, ensure_ascii=False).encode('utf-8'),
                  headers={'Content-Type': 'application/json'}, timeout=10)
res = r.json()
# res['code'] == 200 即成功;用 res['data']['payEntryUrl'] 引导付款
const crypto = require('crypto');
const APP_KEY = '你的 appKey';

const p = {
  merchantNo: '88001', appId: 'app_demo',
  mchOrderNo: 'ORD20260928001', payMethod: 'alipay',
  bankCode: '9311',
  amount: '0.01', currency: 'CNY', subject: '测试商品',   // 元
  notifyUrl: 'https://m.example.com/pay/notify',
  timestamp: String(Date.now()),                 // 毫秒
  nonce: crypto.randomBytes(16).toString('hex'),
};
const base = Object.keys(p).sort()
  .filter(k => p[k] !== '')
  .map(k => `${k}=${p[k]}`).join('&');
const sign = crypto.createHash('md5')
  .update(base + '&key=' + APP_KEY).digest('hex').toUpperCase();
p.signType = 'MD5';
p.sign = sign;

fetch('https://api.60185.com/api/pay/unifiedOrder', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(p),
}).then(r => r.json()).then(res => {
  // res.code === 200 即成功;用 res.data.payEntryUrl 引导付款
});
// 依赖:github.com/google/uuid;md5 用标准库
package main

import (
	"bytes"	"crypto/md5"	"encoding/hex"	"encoding/json"	"fmt"	"net/http"	"sort"	"strings"	"time"
	"github.com/google/uuid"
)

const appKey = "你的 appKey"

func md5HexUpper(s string) string {
	sum := md5.Sum([]byte(s))
	return strings.ToUpper(hex.EncodeToString(sum[:]))
}

func main() {
	p := map[string]string{
		"merchantNo": "88001", "appId": "app_demo",
		"mchOrderNo": "ORD20260928001", "payMethod": "alipay",
		"bankCode": "9311",
		"amount": "0.01", "currency": "CNY", "subject": "测试商品", // 单位:元
		"notifyUrl": "https://m.example.com/pay/notify",
		"timestamp": fmt.Sprintf("%d", time.Now().UnixMilli()), // 毫秒
		"nonce": strings.ReplaceAll(uuid.NewString(), "-", ""),
	}
	keys := make([]string, 0, len(p))
	for k, v := range p {
		if v != "" { keys = append(keys, k) } // 只对非空参数签名
	}
	sort.Strings(keys) // ASCII 升序
	parts := make([]string, 0, len(keys))
	for _, k := range keys { parts = append(parts, k+"="+p[k]) }
	signSrc := strings.Join(parts, "&") + "&key=" + appKey

	p["signType"] = "MD5"
	p["sign"] = md5HexUpper(signSrc) // MD5 转大写

	body, _ := json.Marshal(p)
	resp, err := http.Post("https://api.60185.com/api/pay/unifiedOrder",
		"application/json", bytes.NewReader(body))
	if err != nil { panic(err) }
	defer resp.Body.Close()
	var out struct { Code int `json:"code"`; Msg string `json:"msg"`; Data struct{ PayEntryUrl string `json:"payEntryUrl"` } `json:"data"` }
	json.NewDecoder(resp.Body).Decode(&out)
	// out.Code == 200 即成功;用 out.Data.PayEntryUrl 引导用户打开付款
}
// 依赖:cpp-httplib(单头文件)+ OpenSSL(MD5)
// g++ main.cpp -o pay -lssl -lcrypto
#include <httplib.h>
#include <openssl/md5.h>
#include <map> #include <string> #include <vector> #include <sstream>
#include <iomanip> #include <chrono> #include <random> #include <algorithm>

static std::string md5Upper(const std::string& s) {
    unsigned char d[MD5_DIGEST_LENGTH];
    MD5(reinterpret_cast<const unsigned char*>(s.data()), s.size(), d); // OpenSSL 1.x;3.x 用 EVP_Digest
    std::ostringstream o;
    for (unsigned char c : d) o << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << (int)c;
    return o.str();
}

int main() {
    const std::string appKey = "你的 appKey";
    std::map<std::string, std::string> p;   // std::map 天然按 key ASCII 升序
    p["merchantNo"] = "88001";              p["appId"] = "app_demo";
    p["mchOrderNo"] = "ORD20260928001";     p["payMethod"] = "alipay";
    p["bankCode"] = "9311";
    p["amount"] = "0.01";                   p["currency"] = "CNY";   // 单位:元
    p["subject"] = "测试商品";
    p["notifyUrl"] = "https://m.example.com/pay/notify";
    p["timestamp"] = std::to_string( std::chrono::duration_cast<std::chrono::milliseconds>(
                           std::chrono::system_clock::now().time_since_epoch()).count() );   // 毫秒
    p["nonce"] = []{ std::random_device rd; std::ostringstream s;
                     for (int i = 0; i < 8; ++i) s << std::hex << rd(); return s.str(); }();

    std::string base;                                                    // k=v&k2=v2 升序拼接
    for (auto& kv : p) { if (!base.empty()) base += "&"; base += kv.first + "=" + kv.second; }
    p["sign"] = md5Upper(base + "&key=" + appKey);                        // MD5 转大写
    p["signType"] = "MD5";

    std::string jsonBody = "{";                                          // 手工拼 JSON(UTF-8 原文)
    for (auto& kv : p) { jsonBody += "\"" + kv.first + "\":\"" + kv.second + "\","; }
    jsonBody.back() = '}';

    httplib::Client cli("https://api.60185.com");
    auto res = cli.Post("/api/pay/unifiedOrder", jsonBody, "application/json");
    // res->status == 200 且响应体 code==200 即成功;取 data.payEntryUrl 引导付款
}
// .NET 6+ / C#(System.Text.Json + HttpClient,无需第三方依赖)
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

string appKey = "你的 appKey";
var p = new Dictionary<string, string>
{
    ["merchantNo"] = "88001", ["appId"] = "app_demo",
    ["mchOrderNo"] = "ORD20260928001", ["payMethod"] = "alipay",
    ["bankCode"] = "9311",
    ["amount"] = "0.01", ["currency"] = "CNY", ["subject"] = "测试商品",  // 单位:元
    ["notifyUrl"] = "https://m.example.com/pay/notify",
    ["timestamp"] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(),  // 毫秒
    ["nonce"] = Guid.NewGuid().ToString("N"),
};

// 非空参数按 key ASCII 升序 → k=v&... → 尾随 &key=appKey → MD5 大写
var signSrc = string.Join("&", p.Where(kv => !string.IsNullOrEmpty(kv.Value))
                          .OrderBy(kv => kv.Key, StringComparer.Ordinal)
                          .Select(kv => $"{kv.Key}={kv.Value}")) + "&key=" + appKey;
var hash = MD5.HashData(Encoding.UTF8.GetBytes(signSrc));
p["sign"] = Convert.ToHexString(hash);   // .NET 5+ 默认输出大写 hex
p["signType"] = "MD5";

var payload = JsonSerializer.SerializeToUtf8Bytes(p,
    new JsonSerializerOptions { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
using var http = new HttpClient();
var resp = await http.PostAsync("https://api.60185.com/api/pay/unifiedOrder",
    new StringContent(Encoding.UTF8.GetString(payload), Encoding.UTF8, "application/json"));
using var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
// doc.RootElement.GetProperty("code").GetInt32() == 200 即成功;取 data.payEntryUrl 引导付款
-- 依赖:luaossl(md5)+ lua-cjson + luasec/http;OpenResty 下用 resty.md5 + resty.http
local md5    = require('resty.openssl.digest')      -- luaossl;OpenResty 也可用 resty.md5(见下方注释)
local cjson  = require('cjson')
local http   = require('http')

local appKey = '你的 appKey'
local p = {
  merchantNo = '88001', appId = 'app_demo',
  mchOrderNo = 'ORD20260928001', payMethod = 'alipay',
  bankCode = '9311',
  amount = '0.01', currency = 'CNY', subject = '测试商品',   -- 单位:元
  notifyUrl = 'https://m.example.com/pay/notify',
  timestamp = tostring(math.floor(ngx and ngx.now() * 1000 or os.time() * 1000)),  -- 毫秒
  nonce = string.format('%08x%08x', math.random(0, 0xffffffff), math.random(0, 0xffffffff)),
}

-- 非空参数按 key ASCII(字节序)升序 → k=v&... → 尾随 &key=appKey → MD5 大写
local keys = {}
for k, v in pairs(p) do if v ~= nil and v ~= '' then keys[#keys + 1] = k end end
table.sort(keys)                                                  -- Lua 字符串比较即字节序,等价 ASCII 升序
local parts = {}
for _, k in ipairs(keys) do parts[#parts + 1] = k .. '=' .. tostring(p[k]) end
local signSrc = table.concat(parts, '&') .. '&key=' .. appKey

local function md5Upper(s)
  local d = md5.new('md5'); d:update(s)
  return (d:final():gsub('.', function(c) return string.format('%02X', c) end))   -- 大写 hex
end
p.sign     = md5Upper(signSrc)
-- OpenResty 写法: local m = require('resty.md5'); m:set(signSrc); p.sign = string.upper(m:tohex())
p.signType = 'MD5'

local res, err = http.post('https://api.60185.com/api/pay/unifiedOrder', cjson.encode(p),
                           { ['Content-Type'] = 'application/json' })
if not res then error(err) end
local out = cjson.decode(res)     -- luaossl http 直接返回响应体字符串
-- out.code == 200 即成功;用 out.data.payEntryUrl 引导用户打开付款
-- 注:luaossl/cjson 为阻塞调用,适合普通 Lua 脚本;OpenResty 网关内请改用 resty.http + cjson
兼容层差异:Jeepay / 易支付 的签名与金额规则不同(大写 vs 小写、&key= vs 裸拼、分 vs 元),详见 签名算法,不可直接复用上面的字段。注:各示例中的 md5() 均为该语言标准/常用库里的 MD5(Java 需自行包一层 MessageDigest,C++ 依赖 OpenSSL),输出十六进制后统一转大写。

主 API 保护伞原生支付接口

保护伞自己的标准支付 API,也是本平台功能最完整、推荐优先对接的接口。报文体为 JSON,金额单位元,成功码 code=200,采用 appKey 签名 + timestamp/nonce 防重放(比兼容层多一层安全校验)。下方的 Jeepay / 易支付 仅为存量商户零代码迁移的兼容适配层。

接口前缀:所有原生接口经网关 https://api.60185.com/api/pay/**,免登录(不走 Sa-Token),鉴权完全依赖请求签名(在支付服务的 PaySignFilter 完成)。

接口清单

接口方法路径说明
统一下单POST/api/pay/unifiedOrder创建支付订单,返回支付入口
查询订单GET/api/pay/query/{orderId}按平台订单号查询
关闭订单POST/api/pay/close/{orderId}关闭待支付订单
申请退款POST/api/pay/refund对已支付订单发起退款(见 退款)
查询退款GET/api/pay/refund/{refundId}按退款单号查询
GET 接口签名:query/{orderId}、refund/{refundId} 需把 merchantNo / appId / timestamp / nonce / signType / sign 作为查询串 ?a=b 附加({orderId} 为路径变量,不参与签名)。
JSON 报文与验签是“字段级”比对:平台把 JSON 请求体解析后的一层字段与 URL 查询串参数合并后排序重算签名(数字按 JSON 字面量取文本),所以:
① Content-Type: application/json 必带;下单 / 退款不支持用 x-www-form-urlencoded 调用(表单字段能过验签,但业务层拿不到报文体,会直接请求格式报错);
② 金额等数字字段按原样字符串签名(1 就签 1,不要写成 1.00;0.01 就签 0.01);
③ 不要给签名串里传数组字段;嵌套对象(如 extra)请传已字符串化的 JSON(字符串值),否则平台拼接的待签串与你本地不一致;
④ 空字符串字段不参与签名(平台验签时自动剔除 null/空值),要么不传、要么传真实值;sign / signType 永不被签。

统一下单 POST /api/pay/unifiedOrder

参数类型必填说明
merchantNostring是商户号
appIdstring是应用 ID
mchOrderNostring是商户订单号,全局唯一
payMethodstring是支付方式:alipay / wxpay / bank / quick
amountdecimal是金额,单位元,最小 0.01
currencystring否币种 CNY/USD/THB/VND,默认 CNY
bankCodestring必带支付通道编码(二级分类),取值见下方 通道编码全表(如 9311 支付宝扫码、9211 微信扫码)。一旦携带即按 ASCII 升序纳入待签串、参与签名;强烈建议每笔都携带——平台据此做入口鉴定精确锁定运营商分配给该商户的入金账户,不携带则回退共享池权重路由(通道可能漂移)
subjectstring否商品标题
bodystring否商品描述
clientIpstring否客户端 IP
deviceIdstring否设备指纹(前端 SDK 采集)
notifyUrlstring否异步通知地址(强烈建议传)
returnUrlstring否前端跳转地址
expireMinutesint否订单过期分钟数(受平台上限约束)
extrastring否扩展参数 JSON,通知原样返回
timestamplong是毫秒时间戳,±5 分钟内有效,参与签名
noncestring是随机串(≤ 64 字符),窗口内不可重复,参与签名
signTypestring是MD5 / HMAC_SHA256 / RSA2
signstring是签名,见 签名算法

二级分类 · 支付通道编码(bankCode)全表

保护伞采用三级分类模型:一级大类(payMethod)→ 二级通道(bankCode,商户下单携带,做入口鉴定)→ 三级入金账户(运营商分配,经二级关联目录挂接)。下单时 payMethod 与 bankCode 需同族匹配(如 alipay 配 93xx);该字段携带后必须纳入待签串参与签名。标“待接入”的二级分类下单会被拒绝。

一级 payMethod二级分类bankCodecategoryCode接入状态
wxpay微信扫码9211WX_QR已接入
wxpay微信H59212WX_H5已接入
wxpay微信公众号9213WX_JSAPI已接入
wxpay微信APP9214WX_APP已接入
wxpay微信付款码9215WX_BARCODE已接入
wxpay微信刷脸9216WX_FACE待接入
wxpay微信转账9217WX_TRANSFER待接入
alipay支付宝扫码9311ALI_QR已接入
alipay支付宝H59312ALI_H5已接入
alipay支付宝PC9313ALI_PC已接入
alipay支付宝APP9314ALI_APP已接入
alipay支付宝条码9315ALI_BARCODE已接入
alipay支付宝刷脸9316ALI_FACE待接入
alipay支付宝转账9317ALI_TRANSFER待接入
qqpayQQ钱包扫码9411QQ_QR已接入
qqpayQQ钱包H59412QQ_H5已接入
qqpayQQ钱包付款码9413QQ_BARCODE已接入
jdpay京东钱包9511JD_QR已接入
jdpay京东付款码9512JD_FKM已接入
unionpay银联扫码9611UNION_QR待接入
unionpay银联H59612UNION_H5待接入
unionpay银联APP9613UNION_APP待接入
unionpay银联PC网银9614UNION_WEB待接入
paypalPayPal9711PAYPAL_WEB已接入
xpay个人支付宝固码9811XPAY_ALI已接入
xpay个人微信固码9812XPAY_WX已接入
编码规律:92xx 微信 / 93xx 支付宝 / 94xx QQ钱包 / 95xx 京东 / 96xx 银联 / 97xx PayPal / 98xx 个人固码。另有 99xx(网银/快捷/预付费/翼支付等)为分类占位,当前停用未接入,携带将被入口鉴定拒绝。
请求示例
响应示例
POST https://api.60185.com/api/pay/unifiedOrder
Content-Type: application/json

{
  "merchantNo": "88001",
  "appId": "app_demo",
  "mchOrderNo": "ORD20260928001",
  "payMethod": "alipay",
  "bankCode": "9311",                    // 二级通道编码,参与签名
  "amount": 0.01,                      // 元
  "currency": "CNY",
  "subject": "测试商品",
  "notifyUrl": "https://merchant.example.com/pay/notify",
  "returnUrl": "https://merchant.example.com/pay/return",
  "expireMinutes": 30,
  "timestamp": 1758960000000,          // 毫秒
  "nonce": "a1b2c3d4e5",
  "signType": "MD5",
  "sign": "5F3A...大写MD5...9C1D"
}
{
  "code": 200,
  "msg": "success",
  "data": {
    "orderId": "P20260928000001",       // 平台订单号
    "mchOrderNo": "ORD20260928001",
    "merchantNo": "88001",
    "appId": "app_demo",
    "payMethod": "alipay",
    "amount": 0.01,                     // 元
    "currency": "CNY",
    "status": 0,                        // 0待支付 1支付中 2成功 3失败 4已退款 5部分退款 6已关闭
    "payEntryUrl": "https://api.60185.com/risk/loading?...",  // ★风控入口,必须引导用户打开
    "payData": "https://qr.alipay.com/xxx",   // 收银台URL/二维码内容(服务端留存)
    "expireTime": "2026-09-28 17:44:19",
    "traceId": "a1b2c3...",
    "routingStatus": null               // busy/none 仅入金账户异常时透出
  }
}
务必引导用户打开 payEntryUrl:原生 API 下单返回的 payEntryUrl 是风控入口链接——用户必须经此链接(先完成风控采集与预检)才会跳转到真实支付页。payData 仅为收银台 URL / 二维码内容,供商户服务端留存或展示,不得直接甩给用户跳转。

订单状态 status

status含义status含义
0待支付3支付失败
1支付中4已退款
2支付成功5部分退款
6已关闭——
路由状态 routingStatus(仅待支付且入金账户全满时透出):busy=入金账户暂时繁忙(分钟限额/冷却,稍后自动恢复,前端可转圈等待);none=当日不可恢复(日/总/产品限额或风控拦截,应直接提示失败不空等);null=正常可路由。

Jeepay 兼容层接口

面向存量 Jeepay 商户的零代码迁移入口。报文体为 JSON,金额单位分,成功码 code=0。所有接口 POST,需带 sign。

统一下单 POST /api/jeepay/pay/unifiedOrder

参数类型必填说明
mchNostring是商户号
appIdstring是应用 ID
mchOrderNostring是商户订单号,全局唯一
wayCodestring是支付方式,见 wayCode 表(如 ALI_QR / WX_JSAPI)
amountint是金额,单位分,须 > 0
currencystring否币种,默认 cny
clientIpstring否客户端 IP
subjectstring否商品标题
bodystring否商品描述
notifyUrlstring否异步通知地址(强烈建议传)
returnUrlstring否同步跳转地址
expiredTimeint否订单失效时间(秒),默认 2 小时
channelIdstring否渠道用户 ID(微信 openid,JSAPI/小程序必传)
extParamstring否扩展参数,通知原样返回
reqStrstring否随机串(防重放)
signTypestring否MD5(默认)/ HMAC_SHA256
signstring是签名,见 签名算法
请求示例
响应示例
POST https://api.60185.com/api/jeepay/pay/unifiedOrder
Content-Type: application/json

{
  "mchNo": "88001",
  "appId": "app_demo",
  "mchOrderNo": "ORD20260928001",
  "wayCode": "ALI_QR",
  "amount": 1,
  "currency": "cny",
  "subject": "测试商品",
  "notifyUrl": "https://merchant.example.com/pay/notify",
  "returnUrl": "https://merchant.example.com/pay/return",
  "signType": "MD5",
  "sign": "5F3A...大写MD5...9C1D"
}
{
  "code": 0,
  "msg": "SUCCESS",
  "data": {
    "payOrderId": "P20260928000001",   // 平台订单号
    "mchOrderNo": "ORD20260928001",
    "mchNo": "88001",
    "wayCode": "ALI_QR",
    "amount": 1,
    "currency": "cny",
    "state": 1,                         // 见 state 表,2=成功
    "orderId": "2026...CHN",            // 通道订单号
    "channelOrderNo": "2026...CHN",
    "payData": "https://qr.alipay.com/xxx",
    "payUrl": "https://qr.alipay.com/xxx",
    "payDataType": "url_scheme",        // 或 qr_content(二维码内容)
    "errMsg": ""
  }
}
引导付款:取 data.payUrl:若是 http… 链接则直接跳转/渲染二维码;payDataType=qr_content 时把 payData 内容生成二维码展示。支付结果以异步通知为准,或轮询下面的查单接口。

查询订单 POST /api/jeepay/pay/query

参数:mchNo / appId 必填,payOrderId 与 mchOrderNo 至少一个,加 signType / sign。响应 data 结构同下单。

关闭订单 POST /api/jeepay/pay/close

参数同查询(mchNo/appId + payOrderId|mchOrderNo + sign),成功返回 {code:0,msg:SUCCESS}。

验签要点

  • 参与签名的字段随接口不同而不同(下单含 amount/currency/subject/notifyUrl…,查单只含 mchNo/appId/payOrderId|mchOrderNo)——用哪几个字段就只签哪几个,与 计算器里输入的行保持一致。
  • 响应 data 当前不下发 sign,无需对响应验签。

易支付 兼容层接口

面向存量易支付(EPay)商户的零代码迁移入口。表单格式、下划线字段、金额单位元、成功码 code=1、签名 MD5 小写。注意:本兼容层只接受表单(application/x-www-form-urlencoded)或 GET 查询串,不接受 JSON 请求体。

API 下单 mapi.php
页面跳转 submit.php
查询 api.php

API 接口支付 POST /api/epay/mapi.php

参数必填说明
pid是商户 ID(= 商户号)
type否支付方式 alipay / wxpay / qqpay / bank;不传走收银台
out_trade_no是商户订单号,唯一
notify_url是异步通知地址
return_url否同步跳转地址
name否商品名称
money是金额,单位元,如 0.01
clientip否用户 IP
param否业务扩展参数,回调原样返回
sign是MD5 小写,密钥裸拼(无 &key=)
sign_type否固定 MD5,不参与签名
POST https://api.60185.com/api/epay/mapi.php
Content-Type: application/x-www-form-urlencoded

pid=1001&type=alipay&out_trade_no=ORD001¬ify_url=https://m.example.com/notify&name=test&money=0.01&sign={小写MD5}&sign_type=MD5

// 响应
{ "code":1, "msg":"SUCCESS", "trade_no":"P20260928000009", "orderid":"P20260928000009",
  "type":"alipay", "payurl":"https://qr.alipay.com/xxx", "payDataType":"url" }

页面跳转支付 GET/POST /api/epay/submit.php

参数与 mapi.php 完全相同,用于传统“表单自动提交跳转到收银台”。商户页面构造一个 form 自动 POST 即可:

<form id="epay" method="post" action="https://api.60185.com/api/epay/submit.php">
  <input name="pid" value="1001">
  <input name="type" value="alipay">
  <input name="out_trade_no" value="ORD001">
  <input name="notify_url" value="https://m.example.com/notify">
  <input name="return_url" value="https://m.example.com/return">
  <input name="name" value="test">
  <input name="money" value="0.01">
  <input name="sign" value="{小写MD5}">
  <input name="sign_type" value="MD5">
</form><script>epay.submit()</script>

查询 GET /api/epay/api.php

① 查商户信息

GET /api/epay/api.php?act=query&pid=1001&key={KEY}
// → { "code":1, "pid":1001, "key":"...", "active":1, "money":"余额" }

② 查订单

GET /api/epay/api.php?pid=1001&key={KEY}&out_trade_no=ORD001
// 或传 trade_no=P20260928000009
// → { "trade_no","out_trade_no","type","pid","addtime","endtime","name","money","status":1 }
// status: 1=已支付  0=未支付
查询接口鉴权:api.php 用 pid + key 直接校验(不走签名),请把 key 视为服务端密码,仅在服务器端调用。

异步通知(支付回调)

用户付款成功后,平台主动 POST 到你下单时的 notifyUrl。根据商户来源平台自动选择 保护伞原生 / Jeepay / 易支付 格式,Content-Type: application/json。

⚠ 异步通知不可信,必须查单完成订单状态验证。回调地址是公网可猜测的 URL,攻击者可用伪造报文(假 sign、假金额、假订单号)直接 POST 你的 notifyUrl;通知也可能丢失或重复。因此标准处理顺序固定为:验签 → 调查单接口取平台侧权威状态 → 比对金额/币种/商户订单号 → 幂等入账 → 返回 HTTP 200。未查单核实就直接发货/加款是严重对接错误,会造成伪造通知骗补、资损无法对账,请务必按上述顺序实现。
步骤 1·验签
用平台密钥/公钥重算签名比对;验签失败直接丢弃并告警,绝不返回 200。
步骤 2·查单
拿通知里的 orderId/mchOrderNo 调查单接口,以平台返回的状态为准。
步骤 3·比对
amount、currency、mchOrderNo、merchantNo 四项与本地订单逐项相等。
步骤 4·幂等
用订单号建唯一约束/分布式锁,同一订单只入账一次,再返回 200。
成功判定:平台以 HTTP 状态码 2xx 认为通知送达。你处理完业务后返回 200 即可(建议同时输出纯文本 success 以兼容原生 SDK)。非 2xx 或超时将触发重试。

重试机制

  • 最多重试 8 次,指数退避(首次约 60s,逐次 ×2);直到你返回 2xx 或耗尽。
  • 因此通知会重复到达,商户端必须做幂等(同一 trade_no 只入账一次)。
保护伞原生通知
Jeepay 通知
易支付通知
POST {notifyUrl}   Content-Type: application/json
{
  "merchantNo": "88001",
  "appId": "app_demo",
  "mchOrderNo": "ORD20260928001",   // 商户订单号
  "orderId": "P20260928000001",      // 平台订单号
  "channelOrderNo": "2026...CHN",
  "payMethod": "alipay",
  "amount": "0.01",                   // 金额,单位元
  "status": "SUCCESS",                // 支付成功
  "payTime": "2026-09-28 15:44:19",
  "channelUser": "",
  "signType": "MD5",
  "sign": "5F3A...大写MD5...9C1D"
}

验签(保护伞原生):除 sign/signType 外的非空字段 ASCII 升序拼 k=v&…&key={appKey},MD5 大写(若商户为 HMAC_SHA256/RSA2 则按对应算法,RSA2 用平台公钥验签)。校验 status==SUCCESS 且 amount 与本地订单一致才入账。验签通过后仍须调查单接口确认状态,不得仅凭通知入账。

POST {notifyUrl}   Content-Type: application/json
{
  "payOrderId": "P20260928000001",   // 平台订单号
  "mchOrderNo": "ORD20260928001",     // 商户订单号
  "mchNo": "88001", "appId": "app_demo",
  "wayCode": "ALI_QR",
  "payAmount": "1",                    // 金额,单位分
  "currency": "cny",
  "state": "2",                        // 2=支付成功
  "orderId": "2026...CHN", "channelOrderNo": "2026...CHN",
  "subject": "测试商品", "body": "",
  "paySuccTime": "2026-09-28 15:44:19", "createTime": "...",
  "extParam": "",
  "signType": "MD5",
  "sign": "5F3A...大写MD5...9C1D"
}

验签:把除 sign/signType 外的非空字段按 ASCII 升序拼 k=v&…&key={appKey},MD5 大写 与 sign 比对;再校验 state==2 且 payAmount 与本地订单金额一致才入账。验签通过后仍须调查单接口确认状态,不得仅凭通知入账。

POST {notify_url}   Content-Type: application/json
{
  "trade_no": "P20260928000009",      // 平台订单号
  "out_trade_no": "ORD001",           // 商户订单号
  "type": "alipay",
  "pid": "1001",
  "trade_status": "TRADE_SUCCESS",    // 支付成功
  "money": "0.01",                     // 金额,单位元
  "param": "",
  "sign": "a1b2...小写MD5...f9e8",
  "sign_type": "MD5"
}

验签(易支付):除 sign/sign_type 外的非空字段 ASCII 升序拼 k=v&… 后直接拼 KEY(无 &key=),MD5 小写。校验 trade_status==TRADE_SUCCESS 且 money 与订单一致。

与经典 SDK 的差异:传统易支付通知是 form-urlencoded;本平台统一用 JSON 体下发(字段名不变)。若你直接复用旧 SDK 的 $_GET/$_POST 解析,请改为先读 JSON body(例:$p = json_decode(file_get_contents('php://input'), true);),再按上式验签。

回调处理四步(防伪造、防重复、防金额篡改)

1 验签
用密钥重算 sign,不一致直接丢弃。
2 校金额
通知金额必须 == 本地订单金额。
3 幂等
查订单是否已处理,已处理直接返回成功。
4 返 200
业务处理完,输出 success + HTTP 200。

退款

保护伞原生 API 与 Jeepay 兼容层均提供商户退款接口;易支付商户如需退款请联系运营方处理。

主 API 申请退款 POST /api/pay/refund

参数类型必填说明
merchantNostring是商户号
orderIdstring是原平台订单号
mchRefundNostring是商户退款单号,唯一
refundAmountdecimal是退款金额(元),最小 0.01,且 ≤ 原订单可退余额
reasonstring否退款原因
notifyUrlstring否退款结果通知地址
timestamp / noncestring是防重放,均参与签名
signType / signstring是签名
// 响应 R<RefundVO>
{ "code":200, "msg":"success", "data":{
  "refundId":"R20260928000001", "orderId":"P20260928000001",
  "mchRefundNo":"RF001", "refundAmount":0.01, "payAmount":0.01,
  "status":0,                        // 0处理中 1成功 2失败
  "channelRefundNo":"", "reason":"", "errorMsg":"" } }

查询退款:GET /api/pay/refund/{refundId}(附 merchantNo/appId/timestamp/nonce/signType/sign)。

Jeepay 申请退款 POST /api/jeepay/refund/refundOrder

参数类型必填说明
mchNo / appIdstring是商户号 / 应用 ID
payOrderIdstring二选一原支付订单号
mchOrderNostring二选一原商户订单号
mchRefundNostring是商户退款单号,唯一
refundAmountint是退款金额(分),须 > 0
refundReasonstring否退款原因
currency / clientIp / notifyUrl / extParamstring否可选字段
signType / signstring是签名
// 响应 data
{ "code":0, "msg":"SUCCESS", "data":{
  "refundOrderId":"R20260928000001", "payOrderId":"P20260928000001",
  "mchRefundNo":"RF001", "refundAmount":1, "refundState":1,   // 1=退款中 2=成功 3=失败
  "channelRefundOrderNo":"...", "refundReason":"", "errMsg":"" } }

查询退款 POST /api/jeepay/refund/query

参数:mchNo/appId + refundOrderId|mchRefundNo 至少一个 + sign。

错误码汇总

保护伞原生 (code≠200)
Jeepay (code≠0)
易支付 (code=-1)
HTTP 层
codemsg常见原因
400参数错误必填字段缺失/校验不通过(@Valid)
1012IP不在白名单中应用配了 IP 白名单且调用方 IP 不在其中
5001应用不存在appId 错或不属于该商户
5002应用已停用应用被禁用
5006商户不存在merchantNo 错
5007商户已停用商户被禁用
8001订单不存在orderId 错或跨商户查询
8002订单状态异常关单/退款时状态不满足(如未支付却退款)
8003订单已过期订单超时失效
8004重复下单mchOrderNo 已存在
8005金额异常金额低于 0.01 / 超限额 / 与配置不符
8006通道调用失败下游支付通道异常
8007退款失败通道退款异常或超额
8016签名验证失败最常见:大小写/拼串/timestamp·nonce 未参与签名/时间戳过期/nonce 重复/字段集不一致
说明:鉴权/业务错误仍以 HTTP 200 返回,错误码在 body 的 code、信息在 msg;code=200 才是成功。
msg常见原因
缺少必填参数(mchNo/appId/mchOrderNo/wayCode)下单必传字段缺失
支付金额必须大于0amount 为 0 或负数(单位分)
商户不存在: {mchNo}mchNo 错或未开通
商户已停用商户被禁用
应用不存在或不属于该商户appId 错或不属于该 mchNo
应用已停用应用被禁用
签名验证失败最常见:大小写/拼串/字段集/编码不一致
payOrderId 和 mchOrderNo 至少传一个查单/关单未传订单号
订单不存在订单号错或不属该商户
msg常见原因
缺少必填参数(pid/out_trade_no/money/notify_url)下单必传字段缺失
商户不存在 / 商户已停用pid 错或未开通/被禁
应用不存在pid 对应的应用未配
签名验证失败最常见:用了大写/加了 &key=/空值参与签名
缺少参数 / 密钥错误api.php 查商户时 pid/key 不完整或错
缺少订单号参数 / 订单不存在查单未传 trade_no/out_trade_no
状态码含义
200请求已受理(看 body 里 code 判断业务成败)
404路径错(漏了 /api 前缀或拼写错)
405方法错(该接口只收 GET 或 POST)
502 / 504网关到支付服务不可达,稍后重试或联系运营方
说明:业务错误一般仍以 HTTP 200 返回,错误信息在 body 的 msg;只有网关/路由层错误才是 4xx/5xx。

支付方式 / 状态码对照

三套协议各自的支付方式取值与订单状态码对照,切换标签查看。保护伞原生为主,Jeepay / 易支付为兼容层。

保护伞 payMethod
保护伞 status
Jeepay wayCode
Jeepay state
易支付
payMethod含义payMethod含义
alipay支付宝wxpay微信
qqpayQQ钱包jdpay京东支付
unionpay银联paypalPayPal
xpay个人固码bank银行卡
quick快捷支付——
说明:金额单位元;payMethod 为一级大类(可带二级渠道后缀,如 alipay_scan),并建议每笔携带 bankCode(二级通道编码)做入口鉴定——完整编码见 保护伞 API · 通道编码全表(如 9311 支付宝扫码、9211 微信扫码)。平台按支付方式白名单(大类 ALIPAY / WECHAT / UNIONPAY / DCEP 等)做前缀匹配放行,未开启会返回 支付方式未在平台开启。微信 JSAPI/小程序场景需带用户标识。
status含义退款单 status含义
0待支付0退款处理中
1支付中1退款成功
2支付成功2退款失败
3支付失败——
4已退款
5部分退款
6已关闭
7已冻结

流程节点 flowNode:0 发起 → 1 进入风控 → 2 领取支付链接 → 3 已付款(单调递增,用于观测用户推进到哪一步)。接口成功码 code=200;支付最终结果必须以查单接口为准,异步通知仅作触发(见 异步通知)。

wayCode含义wayCode含义
ALI_BAR支付宝条码(被扫)WX_BAR微信条码(被扫)
ALI_QR支付宝二维码(主扫)WX_QR微信二维码(主扫)
ALI_LITE支付宝小程序WX_LITE微信小程序
ALI_JSAPI支付宝生活号/JSAPIWX_JSAPI微信公众号/JSAPI
ALI_WAP支付宝 WAPWX_H5微信 H5
ALI_PC支付宝 PC 网页WX_APP微信 APP
ALI_APP支付宝 APPYSF_BAR/QR/APP云闪付
JD_BAR/JD_QR京东支付PAYPALPayPal
QR_CASHIER聚合扫码(收银台)AUTO_BAR聚合条码(被扫)

微信 JSAPI/小程序(WX_JSAPI / WX_LITE)需传 channelId=用户 openid。

state含义refundState含义
0初始/待支付0订单生成
1支付中1退款中
2支付成功2退款成功
3支付失败3退款失败
4已关闭4关闭
6退款成功——
type含义字段取值
alipay支付宝status1=已支付 / 0=未支付
wxpay微信trade_statusTRADE_SUCCESS=成功
qqpayQQ钱包code1=成功 / -1=失败
bank银行卡type不传走收银台

易支付:金额单位元(字符串),签名 MD5 小写、密钥裸拼;详见 易支付接口。

常见问题

验签总是失败?

  • 先确认拼接方式:易支付末尾不能有 &key=(裸拼密钥),原生 / Jeepay 必须有。平台验签对 sign 大小写不敏感,所以若失败,真正的差异几乎总在待签字符串本身(字段集合 / &key=)。
  • 只用实际发送的非空字段签名(空值易支付不签);排除 sign 与 signType/sign_type。
  • 用 在线计算器 把你的参数粘进去算一个正确值,与你代码输出逐字对比。
  • 中文参数确保 UTF-8;URL 参数签名时用原始值(不要提前 URL-encode)。
  • JSON 报文下单时最容易错在:数字写成 1.00 而签 1(或反之)、传了空字符串、多传了未参与签名的字段、Content-Type 不是 application/json。

金额不对?

  • Jeepay 传分(int):1 元 = 100;易支付传元(字符串):1 元 = "1.00"。

收不到异步通知?

  • 确认 notifyUrl 公网可达、HTTPS 证书有效、返回 HTTP 200(非 2xx 会一直重试)。
  • 用查单接口主动对账兜底;通知可能因重试而多次到达,必须幂等。

下单成功但 state 不是 2?

  • state=1 仅表示订单已创建,真实支付结果以异步通知 state=2 或查单结果为准。

只按异步通知入账,行不行?

  • 不行。任何人均能向你的公网 notifyUrl 伪造/重放通知,通知也可能丢失。必须验签后再调查单接口核实状态与金额,并做幂等;同时上定时对账任务兜底。

能同一条代码兼容两套协议吗?

  • 不建议。签名与金额单位互斥;按你原系统选一套即可,平台侧运营会把你标为对应来源。