🔗 支付网关 API https://pay.gengzhan.cn/api
| 环境 | 地址 |
| 支付网关基础路径 | https://pay.gengzhan.cn/api |
所有 API 请求均需在 Body(JSON)中包含以下公共参数(继承自 AbstractRQ):
说明: 以下公共参数已包含在各接口请求参数表中,此处统一说明,后续不再重复列出。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
version | String | 是 | 版本号,固定值 1.0 |
signType | String | 是 | 签名类型,固定值 MD5 |
sign | String | 是 | 签名值 |
reqTime | String | 是 | 请求时间戳,格式 yyyyMMddHHmmss |
商户相关接口还需额外包含以下参数(继承自 AbstractMchAppRQ):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
mchNo | String | 是 | 商户号 |
appId | String | 是 | 商户应用 ID |
签名算法: MD5
签名步骤:
- 将请求参数(除去
sign本身)转换为 JSON 对象 - 按照 key 的自然排序(不区分大小写的 ASCII 升序)拼接
key=value&字符串 - 在末尾拼接
key(商户 API 密钥appSecret) - 计算 MD5 并转为大写
各语言签名示例:
Java
import java.security.MessageDigest;
import java.util.*;
public class SignUtil {
public static String genSign(Map<String, Object> params, String appSecret) throws Exception {
List<String> list = new ArrayList<>();
for (Map.Entry<String, Object> entry : params.entrySet()) {
if (entry.getValue() != null && !"".equals(entry.getValue())) {
list.add(entry.getKey() + "=" + entry.getValue() + "&");
}
}
String[] arr = list.toArray(new String[0]);
Arrays.sort(arr, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder();
for (String s : arr) sb.append(s);
sb.append("key=").append(appSecret);
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
StringBuilder hex = new StringBuilder();
for (byte b : digest) hex.append(String.format("%02X", b));
return hex.toString();
}
public static boolean verifySign(Map<String, Object> data, String appSecret, String sign) throws Exception {
return genSign(data, appSecret).equalsIgnoreCase(sign);
}
}
Python
import hashlib
def gen_sign(params: dict, app_secret: str) -> str:
items = [f"{k}={v}&" for k, v in params.items()
if v is not None and v != '']
items.sort(key=str.lower)
raw = ''.join(items) + f"key={app_secret}"
return hashlib.md5(raw.encode('utf-8')).hexdigest().upper()
def verify_sign(data: dict, app_secret: str, sign: str) -> bool:
return gen_sign(data, app_secret) == sign.upper()
JavaScript (Node.js)
const crypto = require('crypto');
function genSign(params, appSecret) {
const items = [];
for (const [k, v] of Object.entries(params)) {
if (v !== null && v !== undefined && v !== '') {
items.push(`${k}=${v}&`);
}
}
items.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
const raw = items.join('') + `key=${appSecret}`;
return crypto.createHash('md5').update(raw, 'utf8').digest('hex').toUpperCase();
}
function verifySign(data, appSecret, sign) {
return genSign(data, appSecret) === sign.toUpperCase();
}
PHP
function genSign(array $params, string $appSecret): string {
$items = [];
foreach ($params as $k => $v) {
if ($v !== null && $v !== '') {
$items[] = "$k=$v&";
}
}
sort($items, SORT_STRING | SORT_FLAG_CASE);
$raw = implode('', $items) . "key=$appSecret";
return strtoupper(md5($raw));
}
function verifySign(array $data, string $appSecret, string $sign): bool {
return strtoupper(genSign($data, $appSecret)) === strtoupper($sign);
}
Go
import (
"crypto/md5"
"fmt"
"sort"
"strings"
)
func GenSign(params map[string]interface{}, appSecret string) string {
items := make([]string, 0)
for k, v := range params {
if v != nil && fmt.Sprintf("%v", v) != "" {
items = append(items, fmt.Sprintf("%s=%v&", k, v))
}
}
sort.Slice(items, func(i, j int) bool {
return strings.ToLower(items[i]) < strings.ToLower(items[j])
})
raw := strings.Join(items, "") + "key=" + appSecret
hash := md5.Sum([]byte(raw))
return fmt.Sprintf("%X", hash)
}
func VerifySign(data map[string]interface{}, appSecret, sign string) bool {
return strings.EqualFold(GenSign(data, appSecret), sign)
}
C#
using System.Security.Cryptography;
using System.Text;
public static class SignUtil
{
public static string GenSign(Dictionary<string, object> paramsDict, string appSecret)
{
var items = new List<string>();
foreach (var kv in paramsDict)
{
if (kv.Value != null && kv.Value.ToString() != "")
{
items.Add($"{kv.Key}={kv.Value}&");
}
}
items.Sort(StringComparer.OrdinalIgnoreCase);
var raw = string.Concat(items) + $"key={appSecret}";
var bytes = MD5.HashData(Encoding.UTF8.GetBytes(raw));
return Convert.ToHexString(bytes);
}
public static bool VerifySign(Dictionary<string, object> data, string appSecret, string sign)
{
return string.Equals(GenSign(data, appSecret), sign,
StringComparison.OrdinalIgnoreCase);
}
}
所有 API 响应使用统一结构体 ApiRes<T>:
| 字段 | 类型 | 说明 |
|---|---|---|
code | Integer | 业务响应码:0 成功,非 0 失败 |
msg | String | 响应信息 |
data | T | 业务数据对象 |
sign | String | 签名值(对 data 的 JSON 进行签名) |
{
"code": 0,
"msg": "SUCCESS",
"data": { ... },
"sign": "md5签名值"
}
| code | msg | 说明 |
|---|---|---|
0 | SUCCESS | 业务处理成功 |
9999 | 验签失败 | 请求签名校验不通过 |
10000 | 参数有误 | 请求参数不符合要求 |
10001 | 商户订单已存在 | mchOrderNo 已存在 |
10003 | 不支持的支付方式 | wayCode 未配置 |
10005 | 订单不存在 | 订单记录不存在 |
10006 | 当前订单不可关闭 | 仅限 INIT 和 ING 状态关闭 |
10007 | 当前通道不支持退款 | 退款接口不存在 |
10008 | 订单已全额退款 | 无可退余额 |
10010 | 订单状态不正确 | 仅限支付成功的订单可退款 |
| 值 | 常量 | 说明 |
|---|---|---|
0 | STATE_INIT | 订单生成 — 初始状态 |
1 | STATE_ING | 支付中 — 已调起上游渠道 |
2 | STATE_SUCCESS | ✅ 支付成功 — 终态 |
3 | STATE_FAIL | ❌ 支付失败 — 终态 |
4 | STATE_CANCEL | 已撤销 |
5 | STATE_REFUND | 已退款 |
6 | STATE_CLOSED | 订单关闭 |
| 值 | 常量 | 说明 |
|---|---|---|
0 | STATE_INIT | 订单生成 |
1 | STATE_ING | 退款中 |
2 | STATE_SUCCESS | ✅ 退款成功 |
3 | STATE_FAIL | ❌ 退款失败 |
4 | STATE_CLOSED | 退款任务关闭 |
| 方法 | 路径 | 说明 |
|---|---|---|
POST | /api/pay/unifiedOrder | 统一下单(聚合支付) |
POST/GET | /api/pay/query | 订单查询 |
POST/GET | /api/pay/close | 关闭订单 |
POST | /api/refund/refundOrder | 申请退款 |
POST/GET | /api/refund/query | 退款查询 |
GET | /api/pay/notify/{ifCode} | 渠道异步回调入口 |
GET | /api/pay/return/{ifCode} | 渠道同步跳转入口 |
GET | /api/refund/notify/{ifCode} | 退款异步回调入口 |
💳 支付接口
聚合支付接口,商户传入支付方式 wayCode,网关自动路由到对应渠道完成支付。支持所有支付方式(支付宝、微信、云闪付、PayPal、网联客等)。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
version | String | 是 | 版本号,固定值 1.0 |
signType | String | 是 | 签名类型,固定值 MD5 |
sign | String | 是 | 签名值,详见签名规则 |
reqTime | String | 是 | 请求时间戳,格式 yyyyMMddHHmmss |
mchNo | String | 是 | 商户号 |
appId | String | 是 | 商户应用 ID |
mchOrderNo | String | 是 | 商户订单号(商户侧唯一) |
wayCode | String | 是 | 支付方式代码(见下方方式表) |
amount | Long | 是 | 支付金额,单位:分(最小 1 分) |
currency | String | 是 | 货币代码,固定 cny |
subject | String | 是 | 商品标题 |
body | String | 是 | 商品描述 |
clientIp | String | 否 | 客户端 IP |
notifyUrl | String | 否 | 异步通知地址(http/https) |
returnUrl | String | 否 | 同步跳转地址 |
expiredTime | Integer | 否 | 订单失效时间,单位:秒(默认 7200) |
channelExtra | String | 否 | 特定渠道额外参数(JSON 字符串) |
extParam | String | 否 | 商户扩展参数,通知时原样返回 |
| wayCode | 说明 | 渠道 |
|---|---|---|
ALI_BAR | 支付宝条码支付(被扫) | alipay |
ALI_JSAPI | 支付宝服务窗支付 | alipay |
ALI_LITE | 支付宝小程序支付 | alipay |
ALI_APP | 支付宝 App 支付 | alipay |
ALI_PC | 支付宝电脑网站支付 | alipay |
ALI_WAP | 支付宝手机网站支付 | alipay |
ALI_QR | 支付宝二维码支付(主扫) | alipay |
ALI_OC | 支付宝订单码支付 | alipay |
WX_JSAPI | 微信公众号/服务窗支付 | wxpay / wxpay-v3 |
WX_LITE | 微信小程序支付 | wxpay / wxpay-v3 |
WX_BAR | 微信条码支付(被扫) | wxpay / wxpay-v3 |
WX_NATIVE | 微信扫码支付(主扫) | wxpay / wxpay-v3 |
WX_H5 | 微信 H5 支付 | wxpay / wxpay-v3 |
WX_APP | 微信 App 支付 | wxpay / wxpay-v3 |
YSF_BAR | 云闪付条码支付 | ysfpay |
YSF_JSAPI | 云闪付 JSAPI 支付 | ysfpay |
PP_PC | PayPal 支付 | pppay |
WLK_NATIVE | 网联客扫码支付 | wlk |
WLK_JSAPI | 网联客 JSAPI 支付 | wlk |
WLK_BAR | 网联客条码支付 | wlk |
WLK_APP | 网联客 App 支付 | wlk |
QR_CASHIER | 收银台二维码支付(聚合) | — |
AUTO_BAR | 条码聚合支付(自动识别) | — |
| 字段 | 类型 | 说明 |
|---|---|---|
payOrderId | String | 系统支付订单号 |
mchOrderNo | String | 商户订单号 |
orderState | Byte | 订单状态:0-生成,1-支付中,2-成功,3-失败 |
payDataType | String | 支付参数类型:payurl / form / codeimg / none |
payData | String | 支付参数值(url / form html / 二维码图片url) |
errCode | String | 渠道错误码(失败时返回) |
errMsg | String | 渠道错误信息(失败时返回) |
{
"version": "1.0",
"signType": "MD5",
"sign": "xxx",
"reqTime": "20240101120000",
"mchNo": "M10001",
"appId": "a1b2c3d4",
"mchOrderNo": "MCH20240101001",
"wayCode": "WX_NATIVE",
"amount": 100,
"currency": "cny",
"subject": "测试商品",
"body": "测试商品描述",
"notifyUrl": "https://商户域名/notify",
"returnUrl": "https://商户域名/return",
"expiredTime": 7200
}
{
"code": 0,
"msg": "SUCCESS",
"data": {
"payOrderId": "P2024010112000000001",
"mchOrderNo": "MCH20240101001",
"orderState": 2,
"payDataType": "codeimg",
"payData": "https://api.dhhpay.com/scan/imgs/xxx.png",
"errCode": null,
"errMsg": null
},
"sign": "a1b2c3d4e5f6..."
}
除统一下单外,每种支付方式也提供独立接口。请求/响应数据结构与统一下单相同,wayCode 固定。
| 接口路径 | wayCode | 说明 |
|---|---|---|
POST /api/pay/aliBarOrder | ALI_BAR | 支付宝条码支付 |
POST /api/pay/aliJsapiOrder | ALI_JSAPI | 支付宝 JSAPI 支付 |
POST /api/pay/aliAppOrder | ALI_APP | 支付宝 App 支付 |
POST /api/pay/aliWapOrder | ALI_WAP | 支付宝 WAP 支付 |
POST /api/pay/aliPcOrder | ALI_PC | 支付宝 PC 支付 |
POST /api/pay/aliQrOrder | ALI_QR | 支付宝二维码支付 |
POST /api/pay/aliLiteOrder | ALI_LITE | 支付宝小程序支付 |
POST /api/pay/aliOcOrder | ALI_OC | 支付宝订单码支付 |
POST /api/pay/wxJsapiOrder | WX_JSAPI | 微信 JSAPI 支付 |
POST /api/pay/wxLiteOrder | WX_LITE | 微信小程序支付 |
POST /api/pay/wxBarOrder | WX_BAR | 微信条码支付 |
POST /api/pay/wxNativeOrder | WX_NATIVE | 微信扫码支付 |
POST /api/pay/wxH5Order | WX_H5 | 微信 H5 支付 |
POST /api/pay/wxAppOrder | WX_APP | 微信 App 支付 |
POST /api/pay/ysfBarOrder | YSF_BAR | 云闪付条码支付 |
POST /api/pay/ysfJsapiOrder | YSF_JSAPI | 云闪付 JSAPI 支付 |
POST /api/pay/ppPcOrder | PP_PC | PayPal 支付 |
POST /api/pay/wlkNativeOrder | WLK_NATIVE | 网联客扫码支付 |
POST /api/pay/wlkJsapiOrder | WLK_JSAPI | 网联客 JSAPI 支付 |
POST /api/pay/wlkBarOrder | WLK_BAR | 网联客条码支付 |
POST /api/pay/wlkAppOrder | WLK_APP | 网联客 App 支付 |
查询支付订单当前状态。支持 GET/POST,payOrderId 与 mchOrderNo 二选一。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
version | String | 是 | 版本号,固定值 1.0 |
signType | String | 是 | 签名类型,固定值 MD5 |
sign | String | 是 | 签名值,详见签名规则 |
reqTime | String | 是 | 请求时间戳,格式 yyyyMMddHHmmss |
mchNo | String | 是 | 商户号 |
appId | String | 是 | 商户应用 ID |
payOrderId | String | 二选一 | 系统支付订单号 |
mchOrderNo | String | 二选一 | 商户订单号 |
| 字段 | 类型 | 说明 |
|---|---|---|
payOrderId | String | 支付订单号 |
mchNo | String | 商户号 |
appId | String | 商户应用 ID |
mchOrderNo | String | 商户订单号 |
ifCode | String | 支付接口代码 |
wayCode | String | 支付方式代码 |
amount | Long | 支付金额,单位:分 |
state | Byte | 支付状态:0-生成,1-支付中,2-成功,3-失败 |
channelOrderNo | String | 渠道订单号 |
errCode | String | 渠道错误码 |
errMsg | String | 渠道错误描述 |
extParam | String | 商户扩展参数 |
successTime | Long | 支付成功时间(毫秒) |
createdAt | Long | 创建时间(毫秒) |
关闭未支付或支付中的订单。支持 GET/POST,payOrderId 与 mchOrderNo 二选一。仅限 STATE_INIT(0)和 STATE_ING(1)状态的订单可关闭。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
version | String | 是 | 版本号,固定值 1.0 |
signType | String | 是 | 签名类型,固定值 MD5 |
sign | String | 是 | 签名值,详见签名规则 |
reqTime | String | 是 | 请求时间戳,格式 yyyyMMddHHmmss |
mchNo | String | 是 | 商户号 |
appId | String | 是 | 商户应用 ID |
payOrderId | String | 二选一 | 系统支付订单号 |
mchOrderNo | String | 二选一 | 商户订单号 |
上游支付渠道(支付宝、微信等)异步通知入口。该接口接收渠道的回调,解析通知数据,更新订单状态,并触发商户异步通知。此接口由上游渠道调用,商户无需直接调用。
| 参数 | 说明 |
|---|---|
ifCode | 支付接口代码(如 alipay、wxpay-v3) |
payOrderId | 可选,系统支付订单号 |
支付完成后用户从支付渠道同步跳转的入口。处理完成后会重定向到商户的 returnUrl(携带签名的订单数据)。注意: 同步跳转仅供展示,请不要以同步跳转作为支付成功的最终依据,始终以异步通知为准。
↩️ 退款接口
商户发起退款申请。仅支持对支付成功的订单进行退款,支持部分退款。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
version | String | 是 | 版本号,固定值 1.0 |
signType | String | 是 | 签名类型,固定值 MD5 |
sign | String | 是 | 签名值,详见签名规则 |
reqTime | String | 是 | 请求时间戳,格式 yyyyMMddHHmmss |
mchNo | String | 是 | 商户号 |
appId | String | 是 | 商户应用 ID |
payOrderId | String | 二选一 | 系统支付订单号 |
mchOrderNo | String | 二选一 | 商户订单号 |
mchRefundNo | String | 是 | 商户退款单号(商户侧唯一) |
refundAmount | Long | 是 | 退款金额,单位:分(≥ 1) |
currency | String | 是 | 货币代码,固定 cny |
refundReason | String | 是 | 退款原因 |
clientIp | String | 否 | 客户端 IP |
notifyUrl | String | 否 | 异步通知地址 |
channelExtra | String | 否 | 渠道额外参数 |
extParam | String | 否 | 商户扩展参数 |
| 字段 | 类型 | 说明 |
|---|---|---|
refundOrderId | String | 系统退款订单号 |
mchRefundNo | String | 商户退款单号 |
payAmount | Long | 原支付金额,单位:分 |
refundAmount | Long | 退款金额,单位:分 |
state | Byte | 退款状态:0-生成,1-退款中,2-成功,3-失败 |
channelOrderNo | String | 渠道退款单号 |
errCode | String | 渠道错误码 |
errMsg | String | 渠道错误信息 |
{
"code": 0,
"msg": "SUCCESS",
"data": {
"refundOrderId": "R2024010112000000001",
"mchRefundNo": "REF20240101001",
"payAmount": 100,
"refundAmount": 50,
"state": 2,
"channelOrderNo": "4200001234202411121234567890"
},
"sign": "xxx"
}
查询退款订单状态。refundOrderId 与 mchRefundNo 二选一。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
version | String | 是 | 版本号,固定值 1.0 |
signType | String | 是 | 签名类型,固定值 MD5 |
sign | String | 是 | 签名值,详见签名规则 |
reqTime | String | 是 | 请求时间戳,格式 yyyyMMddHHmmss |
mchNo | String | 是 | 商户号 |
appId | String | 是 | 商户应用 ID |
refundOrderId | String | 二选一 | 系统退款订单号 |
mchRefundNo | String | 二选一 | 商户退款单号 |
由上游渠道调用,处理退款结果回调。处理流程与支付回调类似。
📣 商户通知
当订单状态变为终态(成功或失败)时,网关会向商户配置的 notifyUrl 发送异步通知。通知以 HTTP GET 请求发送,参数以 Query String 形式传递。
GET {notifyUrl}?payOrderId=P2024010112000000001
&mchNo=M10001
&appId=a1b2c3d4
&mchOrderNo=MCH20240101001
&ifCode=alipay
&wayCode=ALI_JSAPI
&amount=100
¤cy=cny
&state=2
&clientIp=127.0.0.1
&subject=商品标题
&body=商品描述
&channelOrderNo=4200001234202411121234567890
&errCode=
&errMsg=
&extParam=
&successTime=1731398400000
&createdAt=1731398300000
&reqTime=1731398400123
&sign=xxxx
通过 MQ 异步发送,异步通知记录表 t_mch_notify_record 管理重试次数,直至商户返回成功响应。
- 收到通知后需验签(见下方通知验签)
- 验签通过后更新订单状态
- 返回
HTTP 200表示通知成功,否则网关将继续重试
支付完成后,用户从支付渠道跳回 returnUrl。跳转方式为 HTTP 30x 重定向。
与异步通知参数相同,以 Query String 形式拼接在 returnUrl 后,包含完整签名。
⚠️ 注意: 同步跳转可能被用户手动关闭或网络中断,切勿仅依靠同步跳转确认支付结果,始终以异步通知为准。
商户收到通知后,需对通知参数进行验签:
// 1. 取所有通知参数(除去 sign)形成 JSON 对象
// 2. 按 key 的 ASCII 升序排序
// 3. 拼接 key1=value1&key2=value2&...&key=appSecret
// 4. 计算 MD5 并转为大写
// 5. 与 sign 参数比对
String calculatedSign = DhhpayKit.getSign(paramsJsonObject, appSecret);
if (!calculatedSign.equalsIgnoreCase(receivedSign)) {
// 验签失败,可能被篡改
}
退款通知 的格式和验签方式同上,区别在于 data 部分为 QueryRefundOrderRS 的序列化。
📎 附录
| 值 | 常量 | 说明 |
|---|---|---|
0 | STATE_INIT | 订单生成 — 初始状态 |
1 | STATE_ING | 支付中 — 已调起上游渠道 |
2 | STATE_SUCCESS | ✅ 支付成功 — 终态 |
3 | STATE_FAIL | ❌ 支付失败 — 终态 |
4 | STATE_CANCEL | 已撤销 |
5 | STATE_REFUND | 已退款 |
6 | STATE_CLOSED | 订单关闭 |
| 值 | 常量 | 说明 |
|---|---|---|
0 | STATE_INIT | 订单生成 |
1 | STATE_ING | 退款中 |
2 | STATE_SUCCESS | ✅ 退款成功 |
3 | STATE_FAIL | ❌ 退款失败 |
4 | STATE_CLOSED | 退款任务关闭 |
| code | msg | 说明 |
|---|---|---|
0 | SUCCESS | 业务处理成功 |
9999 | 验签失败 | 签名验证不通过 |
10000 | 参数有误 | 请求参数不符合要求 |
10001 | 商户订单已存在 | mchOrderNo 已存在 |
10002 | 商户应用信息失败 | 应用配置不存在 |
10003 | 不支持的支付方式 | wayCode 未配置 |
10004 | 订单状态异常 | 订单当前状态不允许操作 |
10005 | 订单不存在 | 订单记录不存在 |
10006 | 当前订单不可关闭 | 仅限 INIT 和 ING 状态关闭 |
10007 | 当前通道不支持退款 | 退款接口不存在 |
10008 | 订单已全额退款 | 无可退余额 |
10009 | 退款单已存在 | mchRefundNo 重复 |
10010 | 订单状态不正确 | 仅限支付成功的订单可退款 |
| 其他 | 自定义错误信息 | 由业务逻辑或渠道返回 |