文档概述
本文档面向使用
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
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()
&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")