参数签名
算法说明
简要说明:按 ASCII 码从小到大排序,不要空值,Querystring 方式拼接,末尾拼上&key=密钥。(默认是小写)。sign 不参数签名 签名生成的通用步骤如下:
- 设所有发送或者接收到的数据为集合 M,将集合 M 内非空参数值的参数按照参数名 ASCII 码从小到大排序(字典序),使用 URL 键值对的格式(即
key1=value1&key2=value2…)拼接成字符串stringA。 - 在
stringA最后拼接上&key=密钥得到stringSignTemp字符串,并对stringSignTemp进行 md5 运算,得到 sign 值
特别注意/重要规则
- 参数名 ASCII 码从小到大排序(字典序)
- 如果参数的值为空不参与签名;
- 所有参数名均是小写
- 验证调用返回或主动通知签名时,传送的 sign 参数不参与签名,将生成的签名与该 sign 值作校验。
- 接口可能增加字段,验证签名时必须支持增加的扩展字段
- 网址等特殊字符需要进行urlencode编码
- 对于空格是需要escape成“+”。不同语言可能“理解”会有这个差异需要注意
- 最终结果不要大写转换
- 对于出款信息数据类型时json的object。需要做如下转换acct.key1 ,acct.key2 (详情见下方acct处理)
参数示例/步骤说明
例如传递的参数如下:
{
"merid":"12345",
"amt": "1.00",
"tno": "123123123123",
"noti_url": "https://www.baidu.com",
"extra": ""
}
- 组装签名原始字符串
- 去掉空值
- url编码
- 按照参数名 ASCII 字典序排序
- 拼接字符串
key1=value1&key2=value2&key3=value3... - 末尾拼接
&key=密钥
上述数据将得到
amt=1.00&merid=12345¬i_url=https%3A%2F%2Fwww.baidu.com&tno=123123123123
- 对上一步中的字符串拼接
&key=密钥
amt=1.00&merid=12345¬i_url=https%3A%2F%2Fwww.baidu.com&tno=123123123123&key=xxxxxxxxx
- 对上一步中字符串取 md5 值
signresult=md5("amt=1.00&merid=12345¬i_url=https%3A%2F%2Fwww.baidu.com&tno=123123123123&key=xxxxxxxxx")
最终提交参数
{
"merid":"12345",
"amt": "100.00",
"tno": "123123123123",
"noti_url": "https://www.baidu.com",
"extra": "",
"sign":"signresult"
}
acct处理
对于出款acct的处理方案如下。假如是如下数据
{
"acct": {
"code": "PH_GCASH",
"name": "GCash",
"number": "09451187823",
"type": "bankcard",
"user": "EMHER AAA"},
"amt": "2100.00",
"cur": "PHP",
"merid": "99999",
"notify_url": "http://cb.abcd.com/callback.do",
"tno": "72245523588",
}
最终拼接字符串是 :
acct.code=PH_GCASH&acct.name=GCash&acct.number=09451187823&acct.type=bankcard&acct.user=EMHER+AAA&amt=2100.00&cur=PHP&merid=96182¬ify_url=http%3A%2F%2Fcb.abcd.com%2Fcallback.do&tno=72245523588
之后的md5计算方式同之前一致即
md5(str+"&key="key)
!! 注意:
签名过程中,参数名和参数值都需要进行 urlencode 编码,遵循的是 `application/x-www-form-urlencoded` 编码规则。不是`RFC 3986` 下方代码是由GPT转换而来。所以如果对应urlencode函数有问题,请注意此项标准
签名过程中,参数名和参数值都需要进行 urlencode 编码,遵循的是 `application/x-www-form-urlencoded` 编码规则。不是`RFC 3986` 下方代码是由GPT转换而来。所以如果对应urlencode函数有问题,请注意此项标准
参考代码
PHP 示例代码
<?php
function data_encode(array $data)
{
$flat = [];
flatten_data('', $data, $flat);
if (empty($flat)) return '';
ksort($flat); // asc
$out = [];
foreach ($flat as $k => $v) {
if ($v === '') continue; // strip = 1
$out[] = urlencode($k) . '=' . urlencode($v); // urlencode = 1
}
return implode('&', $out);
}
function flatten_data(string $prefix, array $data, array &$out)
{
foreach ($data as $k => $v) {
$key = $prefix ? $prefix . '.' . $k : $k;
if (is_array($v)) {
foreach ($v as $kk => $vv) {
$out[$key . '.' . $kk] = (string)$vv;
}
} else {
$out[$key] = (string)$v;
}
}
}
$data = [
"merid" => "12345",
"amt" => "100.01",
"noti_url" => "https://www.baidu.com",
"acct" => [
"type" => "bank",
"card" => "22222222",
],
];
$your_api_key="keyekeykeykeyabcd";
echo "data-string".data_encode($data)."<br/>";
echo "sign-string".data_encode($data)."&key=".$your_api_key;
C# 示例代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
class Program
{
static string DataEncode(Dictionary<string, object> data)
{
var flat = new Dictionary<string, string>();
FlattenData("", data, flat);
if (flat.Count == 0) return "";
var keys = flat.Keys.OrderBy(k => k).ToList();
var outList = new List<string>();
foreach (var k in keys)
{
var v = flat[k];
if (v == "") continue;
outList.Add(WebUtility.UrlEncode(k) + "=" + WebUtility.UrlEncode(v));
}
return string.Join("&", outList);
}
static void FlattenData(string prefix, Dictionary<string, object> data, Dictionary<string, string> flat)
{
foreach (var kv in data)
{
string key = prefix == "" ? kv.Key : prefix + "." + kv.Key;
if (kv.Value is Dictionary<string, object> map)
{
foreach (var m in map)
{
flat[key + "." + m.Key] = m.Value.ToString();
}
}
else
{
flat[key] = kv.Value.ToString();
}
}
}
static void Main()
{
var data = new Dictionary<string, object>
{
{"merid", "12345"},
{"amt", "100.01"},
{"noti_url", "https://www.baidu.com"},
{ "acct", new Dictionary<string, object>{
{"type", "bank"},
{"card", "22222222"},
}},
};
string apiKey = "keyekeykeykeyabcd";
Console.WriteLine("data-string " + DataEncode(data));
Console.WriteLine("sign-string " + DataEncode(data) + "&key=" + apiKey);
}
}
java 示例代码
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class Main {
static String dataEncode(Map<String, Object> data) {
Map<String, String> flat = new HashMap<>();
flattenData("", data, flat);
if (flat.isEmpty()) return "";
List<String> keys = new ArrayList<>(flat.keySet());
Collections.sort(keys);
List<String> out = new ArrayList<>();
for (String k : keys) {
String v = flat.get(k);
if (v.equals("")) continue;
out.add(url(k) + "=" + url(v));
}
return String.join("&", out);
}
static void flattenData(String prefix, Map<String, Object> data, Map<String, String> out) {
for (Map.Entry<String, Object> e : data.entrySet()) {
String key = prefix.isEmpty() ? e.getKey() : prefix + "." + e.getKey();
Object v = e.getValue();
if (v instanceof Map) {
Map<?, ?> m = (Map<?, ?>) v;
for (Map.Entry<?, ?> sub : m.entrySet()) {
out.put(key + "." + sub.getKey(), sub.getValue().toString());
}
} else {
out.put(key, v.toString());
}
}
}
static String url(String s) {
return URLEncoder.encode(s, StandardCharsets.UTF_8);
}
public static void main(String[] args) {
Map<String, Object> data = new HashMap<>();
data.put("merid", "12345");
data.put("amt", "100.01");
data.put("noti_url", "https://www.baidu.com");
Map<String, Object> acct = new HashMap<>();
acct.put("type", "bank");
acct.put("card", "22222222");
data.put("acct", acct);
String apiKey = "keyekeykeykeyabcd";
System.out.println("data-string " + dataEncode(data));
System.out.println("sign-string " + dataEncode(data) + "&key=" + apiKey);
}
}
golang 示例代码
package main
import (
"fmt"
"net/url"
"sort"
"strings"
)
func dataEncode(data map[string]any) string {
flat := map[string]string{}
flattenData("", data, flat)
if len(flat) == 0 {
return ""
}
keys := make([]string, 0, len(flat))
for k := range flat {
keys = append(keys, k)
}
sort.Strings(keys) // asc
var out []string
for _, k := range keys {
v := flat[k]
if v == "" { // strip = 1
continue
}
out = append(out, url.QueryEscape(k)+"="+url.QueryEscape(v)) // urlencode = 1
}
return strings.Join(out, "&")
}
func flattenData(prefix string, data map[string]any, out map[string]string) {
for k, v := range data {
key := k
if prefix != "" {
key = prefix + "." + k
}
switch vv := v.(type) {
case map[string]any:
for kk, vvv := range vv {
out[key+"."+kk] = fmt.Sprintf("%v", vvv)
}
default:
out[key] = fmt.Sprintf("%v", v)
}
}
}
func main() {
data := map[string]any{
"merid": "12345",
"amt": "100.01",
"noti_url": "https://www.baidu.com",
"acct": map[string]any{
"type": "bank",
"card": "22222222",
},
}
yourAPIKey := "keyekeykeykeyabcd"
fmt.Println("data-string", dataEncode(data))
fmt.Println("sign-string", dataEncode(data)+"&key="+yourAPIKey)
}
python 示例代码
import urllib.parse
def data_encode(data):
flat = {}
flatten_data("", data, flat)
if not flat:
return ""
out = []
for k in sorted(flat.keys()):
v = flat[k]
if v == "":
continue
# 强制 Python 的编码行为与 Go 的 QueryEscape 一致
key_enc = urllib.parse.quote(k, safe="")
val_enc = urllib.parse.quote(v, safe="").replace("%20", "+") # 空格转 +
out.append(key_enc + "=" + val_enc)
return "&".join(out)
def flatten_data(prefix, data, out):
for k, v in data.items():
key = k if prefix == "" else prefix + "." + k
if isinstance(v, dict):
for kk, vv in v.items():
out[key + "." + kk] = str(vv)
else:
out[key] = str(v)
data = {
"merid": "12345",
"amt": "100.01",
"ctm": "2025-11-21 01:37:27",
"noti_url": "https://api.kj1616.com/api/pay/callback/2o6i9i7rrsHJ9ghFn64u8o",
"acct": {
"type": "bank",
"card": "22222222",
},
}
api_key = "keyekeykeykeyabcd"
print("data-string", data_encode(data))
print("sign-string", data_encode(data) + "&key=" + api_key)