店火火支付 dhhpay API 文档

https://pay.gengzhan.cn
🔍
未找到匹配的 API 接口。

🔗 支付网关 API https://pay.gengzhan.cn/api

📋 通用规范
环境地址
支付网关基础路径https://pay.gengzhan.cn/api

所有 API 请求均需在 Body(JSON)中包含以下公共参数(继承自 AbstractRQ):

说明: 以下公共参数已包含在各接口请求参数表中,此处统一说明,后续不再重复列出。

字段类型必填说明
versionString版本号,固定值 1.0
signTypeString签名类型,固定值 MD5
signString签名值
reqTimeString请求时间戳,格式 yyyyMMddHHmmss

商户相关接口还需额外包含以下参数(继承自 AbstractMchAppRQ):

字段类型必填说明
mchNoString商户号
appIdString商户应用 ID

签名算法: MD5

签名步骤:

  1. 将请求参数(除去 sign 本身)转换为 JSON 对象
  2. 按照 key 的自然排序(不区分大小写的 ASCII 升序)拼接 key=value& 字符串
  3. 在末尾拼接 key(商户 API 密钥 appSecret
  4. 计算 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>

字段类型说明
codeInteger业务响应码:0 成功,非 0 失败
msgString响应信息
dataT业务数据对象
signString签名值(对 data 的 JSON 进行签名)
{
  "code": 0,
  "msg": "SUCCESS",
  "data": { ... },
  "sign": "md5签名值"
}
codemsg说明
0SUCCESS业务处理成功
9999验签失败请求签名校验不通过
10000参数有误请求参数不符合要求
10001商户订单已存在mchOrderNo 已存在
10003不支持的支付方式wayCode 未配置
10005订单不存在订单记录不存在
10006当前订单不可关闭仅限 INIT 和 ING 状态关闭
10007当前通道不支持退款退款接口不存在
10008订单已全额退款无可退余额
10010订单状态不正确仅限支付成功的订单可退款
常量说明
0STATE_INIT订单生成 — 初始状态
1STATE_ING支付中 — 已调起上游渠道
2STATE_SUCCESS✅ 支付成功 — 终态
3STATE_FAIL❌ 支付失败 — 终态
4STATE_CANCEL已撤销
5STATE_REFUND已退款
6STATE_CLOSED订单关闭
常量说明
0STATE_INIT订单生成
1STATE_ING退款中
2STATE_SUCCESS✅ 退款成功
3STATE_FAIL❌ 退款失败
4STATE_CLOSED退款任务关闭
📋 API 速查表
方法路径说明
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}退款异步回调入口

💳 支付接口

POST /api/pay/unifiedOrder 统一下单 聚合支付接口,自动路由到对应渠道

聚合支付接口,商户传入支付方式 wayCode,网关自动路由到对应渠道完成支付。支持所有支付方式(支付宝、微信、云闪付、PayPal、网联客等)。

字段类型必填说明
versionString版本号,固定值 1.0
signTypeString签名类型,固定值 MD5
signString签名值,详见签名规则
reqTimeString请求时间戳,格式 yyyyMMddHHmmss
mchNoString商户号
appIdString商户应用 ID
mchOrderNoString商户订单号(商户侧唯一)
wayCodeString支付方式代码(见下方方式表)
amountLong支付金额,单位:分(最小 1 分)
currencyString货币代码,固定 cny
subjectString商品标题
bodyString商品描述
clientIpString客户端 IP
notifyUrlString异步通知地址(http/https)
returnUrlString同步跳转地址
expiredTimeInteger订单失效时间,单位:秒(默认 7200)
channelExtraString特定渠道额外参数(JSON 字符串)
extParamString商户扩展参数,通知时原样返回
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_PCPayPal 支付pppay
WLK_NATIVE网联客扫码支付wlk
WLK_JSAPI网联客 JSAPI 支付wlk
WLK_BAR网联客条码支付wlk
WLK_APP网联客 App 支付wlk
QR_CASHIER收银台二维码支付(聚合)
AUTO_BAR条码聚合支付(自动识别)
字段类型说明
payOrderIdString系统支付订单号
mchOrderNoString商户订单号
orderStateByte订单状态:0-生成,1-支付中,2-成功,3-失败
payDataTypeString支付参数类型:payurl / form / codeimg / none
payDataString支付参数值(url / form html / 二维码图片url)
errCodeString渠道错误码(失败时返回)
errMsgString渠道错误信息(失败时返回)
{
  "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..."
}
POST /api/pay/{wayCode}Order 单独支付方式 各支付方式独立接口(参数同统一下单)

除统一下单外,每种支付方式也提供独立接口。请求/响应数据结构与统一下单相同,wayCode 固定。

接口路径wayCode说明
POST /api/pay/aliBarOrderALI_BAR支付宝条码支付
POST /api/pay/aliJsapiOrderALI_JSAPI支付宝 JSAPI 支付
POST /api/pay/aliAppOrderALI_APP支付宝 App 支付
POST /api/pay/aliWapOrderALI_WAP支付宝 WAP 支付
POST /api/pay/aliPcOrderALI_PC支付宝 PC 支付
POST /api/pay/aliQrOrderALI_QR支付宝二维码支付
POST /api/pay/aliLiteOrderALI_LITE支付宝小程序支付
POST /api/pay/aliOcOrderALI_OC支付宝订单码支付
POST /api/pay/wxJsapiOrderWX_JSAPI微信 JSAPI 支付
POST /api/pay/wxLiteOrderWX_LITE微信小程序支付
POST /api/pay/wxBarOrderWX_BAR微信条码支付
POST /api/pay/wxNativeOrderWX_NATIVE微信扫码支付
POST /api/pay/wxH5OrderWX_H5微信 H5 支付
POST /api/pay/wxAppOrderWX_APP微信 App 支付
POST /api/pay/ysfBarOrderYSF_BAR云闪付条码支付
POST /api/pay/ysfJsapiOrderYSF_JSAPI云闪付 JSAPI 支付
POST /api/pay/ppPcOrderPP_PCPayPal 支付
POST /api/pay/wlkNativeOrderWLK_NATIVE网联客扫码支付
POST /api/pay/wlkJsapiOrderWLK_JSAPI网联客 JSAPI 支付
POST /api/pay/wlkBarOrderWLK_BAR网联客条码支付
POST /api/pay/wlkAppOrderWLK_APP网联客 App 支付
GET /api/pay/query 订单查询 查询支付订单状态

查询支付订单当前状态。支持 GET/POST,payOrderIdmchOrderNo 二选一。

字段类型必填说明
versionString版本号,固定值 1.0
signTypeString签名类型,固定值 MD5
signString签名值,详见签名规则
reqTimeString请求时间戳,格式 yyyyMMddHHmmss
mchNoString商户号
appIdString商户应用 ID
payOrderIdString二选一系统支付订单号
mchOrderNoString二选一商户订单号
字段类型说明
payOrderIdString支付订单号
mchNoString商户号
appIdString商户应用 ID
mchOrderNoString商户订单号
ifCodeString支付接口代码
wayCodeString支付方式代码
amountLong支付金额,单位:分
stateByte支付状态:0-生成,1-支付中,2-成功,3-失败
channelOrderNoString渠道订单号
errCodeString渠道错误码
errMsgString渠道错误描述
extParamString商户扩展参数
successTimeLong支付成功时间(毫秒)
createdAtLong创建时间(毫秒)
GET /api/pay/close 关闭订单 关闭未支付或支付中的订单

关闭未支付或支付中的订单。支持 GET/POST,payOrderIdmchOrderNo 二选一。仅限 STATE_INIT(0)和 STATE_ING(1)状态的订单可关闭。

字段类型必填说明
versionString版本号,固定值 1.0
signTypeString签名类型,固定值 MD5
signString签名值,详见签名规则
reqTimeString请求时间戳,格式 yyyyMMddHHmmss
mchNoString商户号
appIdString商户应用 ID
payOrderIdString二选一系统支付订单号
mchOrderNoString二选一商户订单号
GET /api/pay/notify/{ifCode}[/{payOrderId}] 渠道回调 上游支付渠道异步回调入口

上游支付渠道(支付宝、微信等)异步通知入口。该接口接收渠道的回调,解析通知数据,更新订单状态,并触发商户异步通知。此接口由上游渠道调用,商户无需直接调用。

参数说明
ifCode支付接口代码(如 alipaywxpay-v3
payOrderId可选,系统支付订单号
GET /api/pay/return/{ifCode}[/{payOrderId}] 同步跳转 支付完成后的同步跳转入口

支付完成后用户从支付渠道同步跳转的入口。处理完成后会重定向到商户的 returnUrl(携带签名的订单数据)。注意: 同步跳转仅供展示,请不要以同步跳转作为支付成功的最终依据,始终以异步通知为准。

↩️ 退款接口

POST /api/refund/refundOrder 申请退款 发起退款申请

商户发起退款申请。仅支持对支付成功的订单进行退款,支持部分退款。

字段类型必填说明
versionString版本号,固定值 1.0
signTypeString签名类型,固定值 MD5
signString签名值,详见签名规则
reqTimeString请求时间戳,格式 yyyyMMddHHmmss
mchNoString商户号
appIdString商户应用 ID
payOrderIdString二选一系统支付订单号
mchOrderNoString二选一商户订单号
mchRefundNoString商户退款单号(商户侧唯一)
refundAmountLong退款金额,单位:分(≥ 1)
currencyString货币代码,固定 cny
refundReasonString退款原因
clientIpString客户端 IP
notifyUrlString异步通知地址
channelExtraString渠道额外参数
extParamString商户扩展参数
字段类型说明
refundOrderIdString系统退款订单号
mchRefundNoString商户退款单号
payAmountLong原支付金额,单位:分
refundAmountLong退款金额,单位:分
stateByte退款状态:0-生成,1-退款中,2-成功,3-失败
channelOrderNoString渠道退款单号
errCodeString渠道错误码
errMsgString渠道错误信息
{
  "code": 0,
  "msg": "SUCCESS",
  "data": {
    "refundOrderId": "R2024010112000000001",
    "mchRefundNo": "REF20240101001",
    "payAmount": 100,
    "refundAmount": 50,
    "state": 2,
    "channelOrderNo": "4200001234202411121234567890"
  },
  "sign": "xxx"
}
GET /api/refund/query 退款查询 查询退款订单状态

查询退款订单状态。refundOrderIdmchRefundNo 二选一。

字段类型必填说明
versionString版本号,固定值 1.0
signTypeString签名类型,固定值 MD5
signString签名值,详见签名规则
reqTimeString请求时间戳,格式 yyyyMMddHHmmss
mchNoString商户号
appIdString商户应用 ID
refundOrderIdString二选一系统退款订单号
mchRefundNoString二选一商户退款单号
GET /api/refund/notify/{ifCode}[/{refundOrderId}] 退款回调 渠道退款异步回调入口

由上游渠道调用,处理退款结果回调。处理流程与支付回调类似。

📣 商户通知

📬 异步通知

当订单状态变为终态(成功或失败)时,网关会向商户配置的 notifyUrl 发送异步通知。通知以 HTTP GET 请求发送,参数以 Query String 形式传递。

GET {notifyUrl}?payOrderId=P2024010112000000001
               &mchNo=M10001
               &appId=a1b2c3d4
               &mchOrderNo=MCH20240101001
               &ifCode=alipay
               &wayCode=ALI_JSAPI
               &amount=100
               &currency=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 的序列化。

📎 附录

📊 订单状态枚举
常量说明
0STATE_INIT订单生成 — 初始状态
1STATE_ING支付中 — 已调起上游渠道
2STATE_SUCCESS✅ 支付成功 — 终态
3STATE_FAIL❌ 支付失败 — 终态
4STATE_CANCEL已撤销
5STATE_REFUND已退款
6STATE_CLOSED订单关闭
常量说明
0STATE_INIT订单生成
1STATE_ING退款中
2STATE_SUCCESS✅ 退款成功
3STATE_FAIL❌ 退款失败
4STATE_CLOSED退款任务关闭
❌ 错误码表
codemsg说明
0SUCCESS业务处理成功
9999验签失败签名验证不通过
10000参数有误请求参数不符合要求
10001商户订单已存在mchOrderNo 已存在
10002商户应用信息失败应用配置不存在
10003不支持的支付方式wayCode 未配置
10004订单状态异常订单当前状态不允许操作
10005订单不存在订单记录不存在
10006当前订单不可关闭仅限 INIT 和 ING 状态关闭
10007当前通道不支持退款退款接口不存在
10008订单已全额退款无可退余额
10009退款单已存在mchRefundNo 重复
10010订单状态不正确仅限支付成功的订单可退款
其他自定义错误信息由业务逻辑或渠道返回