API开发文档

API documentation

文档概述

本文档面向使用

K2Gpay

商户自服务系统的技术架构师、研发工程师、系统运维工程师。通过本文档,商户可了解接入的技术、接入的产品业务、接入的流程、接入规范等信息,以便于商户顺利完成接入工作。

接入网关

请登录商户中心,API管理 开发文档中 查看网关地址和API对接参数。

统一下单接口

提交方式:POST
地址: 接入网关/Pay_Index.html
参数名 参数含义 是否必填 参与签名 参数说明
pay_memberid 商户号 平台分配商户号
pay_orderid 订单号 上送订单号唯一, 字符长度20
pay_applydate 提交时间 时间格式:2016-12-26 18:18:18
pay_bankcode 支付通道编码 在商户中心查询
pay_notifyurl 服务端通知 服务端返回地址.(POST返回数据)
pay_callbackurl 页面跳转通知 页面跳转返回地址(POST返回数据)
pay_amount 订单金额 单位:参照通道
pay_md5sign MD5签名 请查看签名算法
pay_productname 商品名称
pay_productnum 商户品数量
pay_productdesc 商品描述
pay_producturl 商户链接地址

请求示例

Content-Type: application/x-www-form-urlencoded,POST 至 {gateway}/Pay_Index.html;pay_md5sign 算法见「签名算法」章节。
POST {gateway}/Pay_Index.html
pay_memberid=10001
pay_orderid=20240708120000123456
pay_applydate=2024-07-08 12:00:00
pay_bankcode=903
pay_notifyurl=https://merchant.example.com/notify
pay_callbackurl=https://merchant.example.com/callback
pay_amount=100.00
pay_md5sign=ABCDEF1234567890ABCDEF1234567890
pay_productname=Test Product

API 返回说明

返回 JSON,Content-Type: application/json; charset=utf-8。默认返回 pay_url 等字段;传 pay_return_type=html(或 content_type=html)时成功将 302 跳转至 pay_url。
参数名 参数含义 是否必填 参与签名 参数说明
status 状态 成功:success 失败:error(不代表业务成功)
msg 状态描述
return_type 返回类型 url=跳转链接;qrcode=二维码链接;form=表单 HTML(见 data.pay_form_html)
pay_url 支付地址 跳转或展示的支付链接
qrcode_url 二维码地址 return_type=qrcode 时返回
data 扩展数据 含 orderid(平台订单号)、pay_url 等,具体字段因上游通道而异
成功返回示例(return_type=url)
{ "status": "success", "msg": "Order submitted", "return_type": "url", "pay_url": "https://pay.example.com/checkout/xxx", "data": { "orderid": "2024070812000012345678", "pay_url": "https://pay.example.com/checkout/xxx", "return_type": "url" } }
成功返回示例(return_type=qrcode)
{ "status": "success", "msg": "Order submitted", "return_type": "qrcode", "pay_url": "https://pay.example.com/qrcode/xxx", "qrcode_url": "https://pay.example.com/qrcode/xxx", "data": { "orderid": "2024070812000012345678", "pay_url": "https://pay.example.com/qrcode/xxx", "qrcode_url": "https://pay.example.com/qrcode/xxx", "return_type": "qrcode" } }
失败返回示例
{ "status": "error", "msg": "Signature verification failed", "data": {} }
注:status=success 表示下单受理成功,不代表支付完成;请以异步通知或订单查询接口确认最终支付状态。

异步通知

提交方式:POST
接收到服务器点对点通讯时,在页面输出“OK”(无双引号;大小写不敏感,返回 OK 或 ok 均可),否则系统将按后台「补发次数」配置自动重试(默认 5 次)。
参数名 参数含义 是否必填 参与签名 参数说明
memberid 商户编号 平台分配商户号
orderid 订单号 上送订单号唯一, 字符长度20
amount 订单金额
transaction_id 交易流水号
datetime 交易时间
returncode 交易状态 00表示成功;其它表示失败(如未支付 NOTPAY、超时 TIMEOUT)
attach 扩展返回 商户附加数据原样回传;不参与签名(签名后追加到通知参数)
sign MD5签名

订单查询

提交方式:POST
地址: 接入网关/Pay_Trade_query.html
参数名 参数含义 是否必填 参与签名 参数说明
pay_memberid 商户编号
pay_orderid 商户订单号
pay_md5sign MD5签名
返回值
返回值为扁平 JSON(无嵌套 data);下列字段均在顶层。
参数名 参数含义 是否必填 参与签名 参数说明
memberid 商户编号
orderid 商户订单号
amount 订单金额 单位:参照通道
time_end 支付成功时间
transaction_id 平台订单号
returncode 请求状态 00表示成功,其它表示失败
trade_state 支付状态 SUCCESS:支付成功,NOTPAY:未支付
sign MD5签名

代付提交接口

提交方式:POST
提交地址:网关地址+/Payment_Dfpay_add.html
参数名 参数含义 是否必填 参与签名 参数说明
mchid 商户号 平台分配商户号
out_trade_no 商户订单号 保证唯一值
money 订单金额 单位:元
bankname 开户行名称
bankCode 银行代码 有值时 选填;印尼等通道建议传(如 014=BCA、002=BRI、DANA/OVO);也可传 bank_code。有值时参与签名;亦可放入 extends.bankCode
subbranch 支行名称
accountname 开户名
cardnumber 收款账号(银行卡号 / IBAN 等,4-64 位字母或数字,输入可含空格或横线)
province 省份
city 城市
currency_type 货币类型 有值时 选填,默认 CNY;有值时参与签名。如:CNY、USD、EUR、IDR 等
notifyUrl 代付终态结果异步通知地址 有值时 选填;http/https 公网地址;终态(如 refCode=1/2/5)时 POST 通知。报文与代付查询成功响应基本一致(含 sign),另含 notify_id(签后追加,不参与签名),不含 sys_trade_no;商户返回含 ok 即可(大小写不敏感)。
notifyurl 代付终态结果异步通知地址 有值时 与 notifyUrl 相同,小写别名
sign MD5签名 二选一 MD5签名字段:传 pay_md5sign 或 sign 其一即可(二选一),该字段本身不参与签名计算
extends 附加字段 有值时 否(通道配置了扩展字段时必填,否则不传);不为空时参与签名
pay_md5sign MD5签名 二选一 MD5签名字段:传 pay_md5sign 或 sign 其一即可(二选一),该字段本身不参与签名计算
注:extends字段具体需要上送哪些字段以及字段的含义,对接时请咨询平台方,如果不需要扩展字段则不传。不为空时,格式:先转化为JSON,接着进行Base64加密
例如:扩展字段为phone和idno,extends[phone]=13800138000,extends[idno]=111111111111111111。首先将extends转化为JSON格式得到{"phone":"13800138000","idno":"111111111111111111"},然后将此JSON字符串进行base64加密得到eyJwaG9uZSI6IjEzODAwMTM4MDAwIiwiaWRubyI6IjExMTExMTExMTExMTExMTExMSJ9,最终得到extends=eyJwaG9uZSI6IjEzODAwMTM4MDAwIiwiaWRubyI6IjExMTExMTExMTExMTExMTExMSJ9。
返回值
注:代付提交受理响应不含签名字段(无 sign / pay_md5sign)。
参数名 参数含义 是否必填 参与签名 参数说明
status 状态 成功:success 失败:error(不代表业务成功)
code 错误码 0000=受理成功;接口返回 msg 固定为英文
msg 状态描述
refCode 业务状态 受理成功时:3=处理中(自动审核),6=待审核(人工审核)
refMsg 业务描述
transaction_id 商户订单号(同 out_trade_no) 商户订单号 out_trade_no(非系统 trade_no)
out_trade_no 商户订单号 成功时返回商户订单号
注:status=success 表示请求受理成功,不代表打款成功;最终状态请调用代付查询接口,refCode=1 时表示转账成功。
代付结果异步通知(notifyUrl)
当代付达到终态(转账成功 refCode=1、失败 refCode=2、审核拒绝 refCode=5 等)时,系统向提交时传入的 notifyUrl 发起 POST。字段与「代付查询」成功响应基本一致(含 sign),另附加 notify_id(签名后追加、不参与签名),不含 sys_trade_no。商户处理成功后请返回含 ok 的正文(大小写不敏感),否则将按系统「补发次数」重试。
受理成功时 refCode 说明:
状态码 说明
3处理中
6待审核
错误码说明(code 字段;接口 msg 固定英文,下表随页面语言切换)
错误码 说明 分类
0000 代付申请成功
0001 提交成功(上游调用失败:{reason},请手动查询) upstream_error
1000 请求数据为空 param_error
1001 代付API未开启 system_error
1002 商户ID不能为空 param_error
1003 商户不存在 param_error
1004 商户未开启此功能 permission_error
1005 缺少签名参数 signature_failed
1006 签名验证失败 signature_failed
1007 请求来源域名与报备域名不一致 security_error
1008 IP地址与报备IP不一致 security_error
2001 节假日暂时无法提款 business_rule_error
2002 提款已关闭 business_rule_error
2003 不在提现时间,请换个时间再来 business_rule_error
2010 金额格式错误或超出范围 param_error
2011 金额格式错误 param_error
2012 单笔最低提款额度:{amount} business_rule_error
2013 单笔最大提款额度:{amount} business_rule_error
2014 提款金额不符合提款额度要求 business_rule_error
2015 金额 {amount} 不在允许范围内。最低:{min},最高:{max} business_rule_error
2016 金额 {submit_amount} 低于最低限额 {min}。允许范围:最低={min},最高={max} business_rule_error
2017 金额 {submit_amount} 高于最高限额 {max}。允许范围:最低={min},最高={max} business_rule_error
2018 notifyUrl 无效(须为 http/https 且非内网地址) param_error
2020 银行名称不能为空 param_error
2021 银行名称格式错误 param_error
2022 支行名称不能为空 param_error
2023 支行名称格式错误 param_error
2024 开户名不能为空 param_error
2025 开户名格式错误 param_error
2026 银行卡号不能为空 param_error
2027 收款账号格式错误(支持4-64位字母或数字,输入可含空格/横线) param_error
2028 省份不能为空 param_error
2029 省份格式错误 param_error
2030 城市不能为空 param_error
2031 城市格式错误 param_error
2032 货币类型格式错误 param_error
2033 订单号不能为空 param_error
2034 订单号格式错误 param_error
2040 扩展字段不能为空 param_error
2041 扩展字段【{alias}】不能为空 param_error
3001 货币类型 {currency} 通道可用金额超限!当前可用:{available},需要:{required}(订单已创建,未提交上游) balance_insufficient
3002 超出商户当日提款总次数限制({limit}) business_rule_error
3003 超出商户当日提款总额度限制({limit}) business_rule_error
3004 超出商户当日自动代付笔数限制({limit}) business_rule_error
3005 超出商户当日自动代付总额度限制({limit}) business_rule_error
3006 超出当日提款额度 business_rule_error
3007 通道余额不足或已被其他请求扣除 balance_insufficient
3008 余额不足以扣除手续费 balance_insufficient
4001 存在重复订单号 param_error
4002 系统暂时不支持 {currency} 货币类型的代付,请联系管理员!{detail} currency_not_supported
4003 商户尚未开通任何货币类型的代付功能,请联系管理员配置 currency_not_supported
4004 尚未开通货币类型 {currency} 的代付!当前已开通的代付货币类型:{opened} currency_not_supported
4005 订单不存在 order_not_found
5001 系统错误 system_error
5002 提交失败 system_error

代付查询接口

提交方式:POST
提交地址:网关地址+/Payment_Dfpay_query.html
参数名 参数含义 是否必填 参与签名 参数说明
out_trade_no 商户订单号
mchid 商户号
pay_md5sign 签名
返回值
参数名 参数含义 是否必填 参与签名 参数说明
status 状态 success:请求成功(不代表业务成功),error:请求失败
msg 状态描述
mchid 商户号 status=success时返回
out_trade_no 商户订单号 status=success时返回
amount 金额 status=success时返回
code 错误码 查询成功为 0000;订单不存在为 4005(refCode=7)
currency_type 货币类型 选填,默认 CNY;有值时参与签名。如:CNY、USD、EUR、IDR 等
transaction_id 商户订单号(同 out_trade_no) 商户订单号 out_trade_no(非系统 trade_no)
sys_trade_no 系统代付单号(trade_no) 与商户 out_trade_no 一一对应,资金流水 transid 通常为此号
refCode 业务状态 status=success时返回
refMsg 业务描述 status=success时返回
success_time 成功时间 status=success,refCode=1时返回
sign MD5签名
refCode返回值
状态码 说明
1成功
2失败
3处理中
4待处理
5审核驳回
6待审核
7交易不存在
8未知状态
注:当status=success和refCode=1同时成立时才表示转账成功

账户余额查询接口

提交方式:POST
接口地址:网关地址+/Payment_Dfpay_balance.html
参数名 参数含义 是否必填 参与签名 参数说明
mchid 商户编号 平台分配商户号
currency_type 货币类型 有值时 选填,默认 CNY;传入时须参与签名。返回的 balance / blockedbalance 为该币种代付产品可用/冻结余额(与代付扣款一致)
pay_md5sign MD5签名
返回值
参数名 参数含义 是否必填 参与签名 参数说明
status 状态 success:请求成功,error:请求失败
code 错误码 查询成功为 0000
msg 状态描述
mchid 商户号 status=success时返回
currency_type 货币类型 选填,默认 CNY;传入时须参与签名。返回的 balance / blockedbalance 为该币种代付产品可用/冻结余额(与代付扣款一致)
balance 可提现余额 status=success时返回
blockedbalance 冻结余额 status=success 时返回;该币种代付产品冻结余额
pay_md5sign MD5签名 MD5签名字段:传 pay_md5sign 或 sign 其一即可(二选一),该字段本身不参与签名计算

渠道编码

请登录商户中心,API管理 通道费率中 查看所属应用的支付通道编码。

签名算法

签名生成的通用步骤如下:
第一步,将参与签名的参数按参数名 ASCII 码从小到大排序(字典序),使用 URL 键值对格式(key1=value1&key2=value2…)拼接成字符串。代收(统一下单/订单查询/异步通知):空字符串也会参与拼接;代付相关:空值跳过。pay_md5sign/sign 不参与;代收异步通知的 attach 不参与签名。
第二步,在stringA最后拼接上应用key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
stringSignTemp="pay_amount=pay_amount&pay_applydate=pay_applydate
&pay_bankcode=pay_bankcode&pay_callbackurl=pay_callbackurl
&pay_memberid=pay_memberid&pay_notifyurl=pay_notifyurl&pay_orderid=pay_orderid&key=key"
sign=MD5(stringSignTemp).toUpperCase()

开发语言 Demo

以下为常用语言对接示例,覆盖签名、统一下单、订单查询、代付提交/查询、余额查询与异步通知验签。请将网关地址、商户号、密钥替换为实际值。

说明:请求均为 POST + application/x-www-form-urlencoded。代收(统一下单/订单查询/异步通知)签名:参与字段按 ASCII 排序后拼接,空字符串也会参与,排除 pay_md5sign/sign;异步通知的 attach 不参与签名。代付(提交/查询/余额)签名:非空参数参与,排除 pay_md5sign/sign;bankCode 有值时需参与签名。

签名算法

// 代付/余额:空值跳过(默认)
function createSign(array $params, $apiKey) {
    ksort($params);
    $buf = '';
    foreach ($params as $k => $v) {
        if ($v === '' || $v === null) continue;
        if ($k === 'pay_md5sign' || $k === 'sign') continue;
        $buf .= $k . '=' . $v . '&';
    }
    return strtoupper(md5($buf . 'key=' . $apiKey));
}
// 代收下单/查询/异步通知验签:空字符串也参与
function createSignPay(array $params, $apiKey) {
    ksort($params);
    $buf = '';
    foreach ($params as $k => $v) {
        if ($k === 'pay_md5sign' || $k === 'sign' || $k === 'attach') continue;
        $buf .= $k . '=' . $v . '&';
    }
    return strtoupper(md5($buf . 'key=' . $apiKey));
}

统一下单

$gateway = 'https://your-gateway.example'; // 网关根地址
$apiKey  = 'YOUR_API_KEY';
$params = [
    'pay_memberid'  => '10001',
    'pay_orderid'   => 'ORD' . date('YmdHis') . mt_rand(1000, 9999),
    'pay_applydate' => date('Y-m-d H:i:s'),
    'pay_bankcode'  => '902', // 渠道编码,见「渠道编码」
    'pay_notifyurl' => 'https://merchant.example/notify',
    'pay_callbackurl'=> 'https://merchant.example/return',
    'pay_amount'    => '100.00',
    'pay_productname'=> 'Demo',
];
$params['pay_md5sign'] = createSignPay($params, $apiKey);

$ch = curl_init($gateway . '/Pay_Index.html');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query($params),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/x-www-form-urlencoded'],
]);
$resp = curl_exec($ch);
curl_close($ch);
echo $resp; // JSON: status / pay_url / data

订单查询

$params = [
    'pay_memberid' => '10001',
    'pay_orderid'  => 'ORD202408010001',
];
$params['pay_md5sign'] = createSignPay($params, $apiKey);
// POST {gateway}/Pay_Trade_query.html

代付提交

$params = [
    'mchid'        => '10001',
    'out_trade_no' => 'DF' . date('YmdHis') . mt_rand(1000, 9999),
    'money'        => '50.00',
    'bankname'     => 'BCA(Bank Central Asia)',
    'bankCode'     => '014', // 印尼等通道建议传
    'subbranch'    => '-',
    'accountname'  => 'John Doe',
    'cardnumber'   => '1234567890',
    'province'     => '-',
    'city'         => '-',
    'currency_type'=> 'IDR',
    'notifyUrl'    => 'https://merchant.example/df/notify',
];
$params['pay_md5sign'] = createSign($params, $apiKey);
// POST {gateway}/Payment_Dfpay_add.html

代付查询

$params = [
    'mchid'        => '10001',
    'out_trade_no' => 'DF202408010001',
];
$params['pay_md5sign'] = createSign($params, $apiKey);
// POST {gateway}/Payment_Dfpay_query.html

账户余额查询

$params = [
    'mchid'          => '10001',
    'currency_type'  => 'IDR', // 选填,有值须参与签名
];
$params['pay_md5sign'] = createSign($params, $apiKey);
// POST {gateway}/Payment_Dfpay_balance.html

异步通知验签

// 异步通知:验签字段不含 attach/sign;处理成功后返回 OK 或 ok(大小写不敏感)
$data = $_POST;
$sign = isset($data['pay_md5sign']) ? $data['pay_md5sign'] : (isset($data['sign']) ? $data['sign'] : '');
$local = createSignPay($data, $apiKey);
if (strcasecmp($local, $sign) !== 0) {
    exit('sign error');
}
// TODO: 按 memberid + orderid 幂等更新订单
echo 'ok';

签名算法

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;

public static String createSign(Map<String, String> params, String apiKey) throws Exception {
    List<String> keys = new ArrayList<>(params.keySet());
    Collections.sort(keys);
    StringBuilder sb = new StringBuilder();
    for (String k : keys) {
        String v = params.get(k);
        if (v == null || v.isEmpty()) continue;
        if ("pay_md5sign".equals(k) || "sign".equals(k)) continue;
        sb.append(k).append('=').append(v).append('&');
    }
    sb.append("key=").append(apiKey);
    MessageDigest md = MessageDigest.getInstance("MD5");
    byte[] dig = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));
    StringBuilder hex = new StringBuilder();
    for (byte b : dig) hex.append(String.format("%02X", b));
    return hex.toString();
}

统一下单

// 使用 HttpURLConnection / OkHttp 等 POST application/x-www-form-urlencoded
Map<String, String> p = new LinkedHashMap<>();
p.put("pay_memberid", "10001");
p.put("pay_orderid", "ORD" + System.currentTimeMillis());
p.put("pay_applydate", "2024-08-01 12:00:00");
p.put("pay_bankcode", "902");
p.put("pay_notifyurl", "https://merchant.example/notify");
p.put("pay_callbackurl", "https://merchant.example/return");
p.put("pay_amount", "100.00");
p.put("pay_productname", "Demo");
p.put("pay_md5sign", createSign(p, apiKey));
// POST {gateway}/Pay_Index.html
// 代付:Payment_Dfpay_add.html(可加 bankCode)
// 查询:Pay_Trade_query.html / Payment_Dfpay_query.html
// 余额:Payment_Dfpay_balance.html

异步通知验签

// Servlet/Controller 读取 form 参数 → createSign 验签 → 业务处理 → response.getWriter().print("ok");

签名算法

import hashlib
import requests
from urllib.parse import urlencode

def create_sign(params: dict, api_key: str) -> str:
    items = []
    for k in sorted(params.keys()):
        if k in ('pay_md5sign', 'sign'):
            continue
        v = params[k]
        if v is None or v == '':
            continue
        items.append(f'{k}={v}')
    raw = '&'.join(items) + f'&key={api_key}'
    return hashlib.md5(raw.encode('utf-8')).hexdigest().upper()

统一下单

gateway = 'https://your-gateway.example'
api_key = 'YOUR_API_KEY'
params = {
    'pay_memberid': '10001',
    'pay_orderid': 'ORD202408010001',
    'pay_applydate': '2024-08-01 12:00:00',
    'pay_bankcode': '902',
    'pay_notifyurl': 'https://merchant.example/notify',
    'pay_callbackurl': 'https://merchant.example/return',
    'pay_amount': '100.00',
    'pay_productname': 'Demo',
}
params['pay_md5sign'] = create_sign(params, api_key)
r = requests.post(gateway + '/Pay_Index.html', data=params, timeout=30)
print(r.text)

# 代付
df = {
    'mchid': '10001', 'out_trade_no': 'DF202408010001', 'money': '50.00',
    'bankname': 'BCA(Bank Central Asia)', 'bankCode': '014',
    'subbranch': '-', 'accountname': 'John Doe', 'cardnumber': '1234567890',
    'province': '-', 'city': '-', 'currency_type': 'IDR',
    'notifyUrl': 'https://merchant.example/df/notify',
}
df['pay_md5sign'] = create_sign(df, api_key)
requests.post(gateway + '/Payment_Dfpay_add.html', data=df, timeout=30)

异步通知验签

# Flask 示例
@app.route('/notify', methods=['POST'])
def notify():
    data = request.form.to_dict()
    sign = data.get('pay_md5sign') or data.get('sign', '')
    if create_sign(data, api_key).upper() != sign.upper():
        return 'sign error', 400
    # TODO: 幂等更新订单
    return 'ok'

签名算法

const crypto = require('crypto');
const axios = require('axios');
const qs = require('querystring');

function createSign(params, apiKey) {
  const keys = Object.keys(params).sort();
  let buf = '';
  for (const k of keys) {
    if (k === 'pay_md5sign' || k === 'sign') continue;
    const v = params[k];
    if (v === undefined || v === null || v === '') continue;
    buf += `${k}=${v}&`;
  }
  return crypto.createHash('md5').update(buf + 'key=' + apiKey, 'utf8').digest('hex').toUpperCase();
}

统一下单

async function payOrder(gateway, apiKey, params) {
  params.pay_md5sign = createSign(params, apiKey);
  const { data } = await axios.post(gateway + '/Pay_Index.html', qs.stringify(params), {
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    timeout: 30000,
  });
  return data;
}

// 代付 / 查询 / 余额:同样 createSign 后 POST
// Payment_Dfpay_add.html | Payment_Dfpay_query.html | Payment_Dfpay_balance.html

异步通知验签

// Express
app.post('/notify', express.urlencoded({ extended: false }), (req, res) => {
  const data = req.body;
  const sign = data.pay_md5sign || data.sign || '';
  if (createSign(data, apiKey).toUpperCase() !== String(sign).toUpperCase()) {
    return res.status(400).send('sign error');
  }
  // TODO: 幂等更新
  res.send('ok');
});

签名算法

package pay

import (
    "crypto/md5"
    "encoding/hex"
    "net/http"
    "net/url"
    "sort"
    "strings"
)

func CreateSign(params map[string]string, apiKey string) string {
    keys := make([]string, 0, len(params))
    for k := range params {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    var b strings.Builder
    for _, k := range keys {
        if k == "pay_md5sign" || k == "sign" {
            continue
        }
        v := params[k]
        if v == "" {
            continue
        }
        b.WriteString(k)
        b.WriteByte('=')
        b.WriteString(v)
        b.WriteByte('&')
    }
    b.WriteString("key=")
    b.WriteString(apiKey)
    sum := md5.Sum([]byte(b.String()))
    return strings.ToUpper(hex.EncodeToString(sum[:]))
}

func PostForm(endpoint string, params map[string]string) (*http.Response, error) {
    form := url.Values{}
    for k, v := range params {
        form.Set(k, v)
    }
    return http.PostForm(endpoint, form)
}

统一下单

params := map[string]string{
    "pay_memberid": "10001",
    "pay_orderid": "ORD202408010001",
    "pay_applydate": "2024-08-01 12:00:00",
    "pay_bankcode": "902",
    "pay_notifyurl": "https://merchant.example/notify",
    "pay_callbackurl": "https://merchant.example/return",
    "pay_amount": "100.00",
    "pay_productname": "Demo",
}
params["pay_md5sign"] = CreateSign(params, apiKey)
PostForm(gateway+"/Pay_Index.html", params)
// 代付带 bankCode: Payment_Dfpay_add.html

签名算法

using System.Security.Cryptography;
using System.Text;

static string CreateSign(SortedDictionary<string, string> parameters, string apiKey)
{
    var sb = new StringBuilder();
    foreach (var kv in parameters)
    {
        if (kv.Key == "pay_md5sign" || kv.Key == "sign") continue;
        if (string.IsNullOrEmpty(kv.Value)) continue;
        sb.Append(kv.Key).Append('=').Append(kv.Value).Append('&');
    }
    sb.Append("key=").Append(apiKey);
    using var md5 = MD5.Create();
    var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString()));
    var hex = new StringBuilder();
    foreach (var b in hash) hex.Append(b.ToString("X2"));
    return hex.ToString();
}

统一下单

// HttpClient + FormUrlEncodedContent
var p = new SortedDictionary<string, string>
{
    ["pay_memberid"] = "10001",
    ["pay_orderid"] = "ORD202408010001",
    ["pay_applydate"] = "2024-08-01 12:00:00",
    ["pay_bankcode"] = "902",
    ["pay_notifyurl"] = "https://merchant.example/notify",
    ["pay_callbackurl"] = "https://merchant.example/return",
    ["pay_amount"] = "100.00",
    ["pay_productname"] = "Demo",
};
p["pay_md5sign"] = CreateSign(p, apiKey);
using var client = new HttpClient();
var content = new FormUrlEncodedContent(p);
var resp = await client.PostAsync(gateway + "/Pay_Index.html", content);
var body = await resp.Content.ReadAsStringAsync();
// 代付 / 查询 / 余额同理,注意 bankCode 有值时参与签名

异步通知验签

// ASP.NET Core: 读取 form → CreateSign 验签 → 返回 Content("ok")