Developer Docs

API 开发文档

RESTful 风格接口,JSON 传输,MD5 签名。商户号、应用 ID、密钥与网关地址在开通后提供,开通请联系 Telegram @Crab2476 / @zhouv66。

接入概述

  1. 开通账号:联系 Telegram @Crab2476 / @zhouv66,获取商户号 mchNo、应用 ID appId、应用密钥 key 以及网关地址。
  2. 沙箱联调:使用测试参数在沙箱环境完成下单、查询、回调全流程(见沙箱环境)。
  3. 配置生产参数:在商户后台配置异步通知地址、服务器出口 IP 白名单。
  4. 上线:切换为生产密钥与生产网关地址,小额验证后正式放量。

密钥只能保存在服务端,切勿写入 App、小程序或网页前端代码。

请求规范

  • 协议:HTTPS,请求方式 POST
  • 格式:Content-Type: application/json,字符编码 UTF-8
  • 金额:单位为分,整数,例如 100 表示 1.00 元
  • 时间戳:reqTime 为 13 位毫秒时间戳,与服务器时间相差超过 10 分钟的请求将被拒绝

公共参数

参数类型必填说明
mchNoString是商户号
appIdString是应用 ID
reqTimeLong是请求时间,13 位毫秒时间戳
versionString是接口版本,固定 1.0
signTypeString是签名类型,固定 MD5
signString是签名值,见签名规则

公共返回

{
  "code": 0,          // 0 表示成功,其余为失败
  "msg": "SUCCESS",   // 失败时为错误描述
  "data": { ... },    // 业务数据
  "sign": "..."       // 对 data 内字段的签名,建议验签
}

签名规则

  1. 取除 sign 外所有非空参数,按参数名 ASCII 码从小到大排序。
  2. 以 key1=value1&key2=value2 的格式拼接成字符串 stringA。
  3. 在末尾拼接密钥:stringSignTemp = stringA + "&key=" + 应用密钥。
  4. 对 stringSignTemp 做 MD5,结果转为大写,即为 sign。

示例

参数:amount=100, appId=A1001, mchNo=M1001, mchOrderNo=T20261016001, reqTime=1760600000000
stringA        = amount=100&appId=A1001&mchNo=M1001&mchOrderNo=T20261016001&reqTime=1760600000000
stringSignTemp = stringA + "&key=你的应用密钥"
sign           = MD5(stringSignTemp).toUpperCase()

各语言签名代码见 SDK 下载。

统一下单

POST /api/pay/unifiedOrder

请求参数(另含公共参数)

参数类型必填说明
mchOrderNoString(64)是商户订单号,商户系统内唯一
wayCodeString是支付方式编码,开通后提供可用列表
amountLong是支付金额,单位分
currencyString是币种,如 cny
subjectString(64)是商品标题
bodyString(256)是商品描述
notifyUrlString否异步通知地址,不填使用后台配置
returnUrlString否支付完成后的跳转地址
clientIpString否用户端 IP,用于风控
expiredTimeInteger否订单失效时间,单位秒,默认 7200
extParamString否商户扩展参数,回调时原样返回

请求示例

POST https://{网关地址}/api/pay/unifiedOrder
{
  "mchNo": "M1001",
  "appId": "A1001",
  "mchOrderNo": "T20261016001",
  "wayCode": "ALI_WAP",
  "amount": 128000,
  "currency": "cny",
  "subject": "会员充值",
  "body": "会员充值 1280 元",
  "notifyUrl": "https://merchant.example.com/pay/notify",
  "reqTime": 1760600000000,
  "version": "1.0",
  "signType": "MD5",
  "sign": "C380BEC2BFD727A4B6845133519F3AD6"
}

返回 data 字段

参数类型必填说明
payOrderIdString是平台支付订单号
mchOrderNoString是商户订单号
orderStateInteger是订单状态:0 订单生成,1 支付中,2 支付成功,3 支付失败,4 已撤销,5 已退款,6 订单关闭
payDataTypeString是支付数据类型:payurl 跳转链接,codeUrl 二维码地址,form 表单
payDataString否支付数据,按 payDataType 使用
errCodeString否渠道错误码
errMsgString否渠道错误描述

查询订单

POST /api/pay/query

payOrderId 与 mchOrderNo 二选一,同时传入时以 payOrderId 为准。 建议在未收到回调时主动查询,不要以查询代替回调作为唯一的到账依据。

参数类型必填说明
payOrderIdString二选一平台支付订单号
mchOrderNoString二选一商户订单号

返回 data 包含订单号、金额、orderState、successTime 等字段,含义同统一下单。

异步通知

订单状态变为成功或失败后,平台以 POST(application/x-www-form-urlencoded)方式请求notifyUrl,参数为订单信息加 sign。

  • 先验签:用同样的签名规则计算后与 sign 比对,一致才处理。
  • 校验金额与订单号,并做幂等处理,同一订单可能收到多次通知。
  • 处理成功后响应纯文本 SUCCESS;否则平台按 0s / 30s / 60s / 90s / 120s / 150s 递增间隔重试,最多 6 次。
payOrderId=P1981...&mchNo=M1001&appId=A1001&mchOrderNo=T20261016001
&amount=128000&state=2&successTime=1760600123000&extParam=&reqTime=1760600123456&sign=...

代付下单

POST /api/transferOrder

参数类型必填说明
mchOrderNoString(64)是商户代付单号,商户系统内唯一
ifCodeString是代付通道编码,开通后提供
entryTypeString是入账方式,如 BANK_CARD
amountLong是代付金额,单位分
currencyString是币种,如 cny
accountNoString是收款账号
accountNameString否收款人姓名
bankNameString否收款银行名称
transferDescString是代付备注
notifyUrlString否代付结果通知地址

代付结果以异步通知或 POST /api/transfer/query 查询为准,状态:0 订单生成,1 转账中,2 转账成功,3 转账失败,4 订单关闭。

返回码

code说明处理建议
0请求成功订单是否支付成功以 orderState / 回调为准
非 0请求失败,原因见 msg参数或签名错误请修正后重试,不要原单号重复提交已成功的订单
9999系统异常稍后使用查询接口确认订单状态,确认失败后再重试

常见失败原因:签名错误、商户或应用未启用、支付方式未开通、商户订单号重复、金额超出单笔限额、IP 不在白名单。

SDK 下载

以下为各语言的签名实现,配合任意 HTTP 客户端即可完成接入。Java / PHP / Python / Node.js / Go 完整 SDK 包与 Demo 工程请联系 Telegram @Crab2476 / @zhouv66 获取。

Java

public static String sign(Map<String, Object> params, String key) {
    String raw = new TreeMap<>(params).entrySet().stream()
        .filter(e -> e.getValue() != null && !"".equals(e.getValue().toString()) && !"sign".equals(e.getKey()))
        .map(e -> e.getKey() + "=" + e.getValue())
        .collect(Collectors.joining("&"));
    return DigestUtils.md5Hex(raw + "&key=" + key).toUpperCase();
}

PHP

function sign(array $params, string $key): string {
    unset($params['sign']);
    $params = array_filter($params, fn($v) => $v !== null && $v !== '');
    ksort($params);
    $raw = urldecode(http_build_query($params));
    return strtoupper(md5($raw . '&key=' . $key));
}

Python

import hashlib

def sign(params: dict, key: str) -> str:
    items = sorted((k, v) for k, v in params.items() if k != "sign" and v not in (None, ""))
    raw = "&".join(f"{k}={v}" for k, v in items)
    return hashlib.md5(f"{raw}&key={key}".encode("utf-8")).hexdigest().upper()

Node.js

const crypto = require("crypto");

function sign(params, key) {
  const raw = Object.keys(params)
    .filter((k) => k !== "sign" && params[k] !== undefined && params[k] !== null && params[k] !== "")
    .sort()
    .map((k) => `${k}=${params[k]}`)
    .join("&");
  return crypto.createHash("md5").update(`${raw}&key=${key}`, "utf8").digest("hex").toUpperCase();
}

Go

func Sign(params map[string]string, key string) string {
    keys := make([]string, 0, len(params))
    for k, v := range params {
        if k != "sign" && v != "" {
            keys = append(keys, k)
        }
    }
    sort.Strings(keys)
    parts := make([]string, len(keys))
    for i, k := range keys {
        parts[i] = k + "=" + params[k]
    }
    sum := md5.Sum([]byte(strings.Join(parts, "&") + "&key=" + key))
    return strings.ToUpper(hex.EncodeToString(sum[:]))
}

沙箱环境

沙箱与生产环境接口、参数、签名规则完全一致,仅网关地址和密钥不同,不产生真实资金流动。

  • 测试商户号、应用 ID、密钥与沙箱网关地址:联系 Telegram @Crab2476 / @zhouv66 开通。
  • 沙箱下单后可由技术支持协助模拟支付成功 / 失败,用于验证回调处理逻辑。
  • 联调通过后,替换为生产参数即可上线,无需修改代码。