Docs
API文档
  • 简体中文
  • English
API文档
  • 简体中文
  • English
  • API文档

    • 统一约定
    • 入账/收款
    • 出账/代付
    • 账户余额
    • 交易查询
    • 异步通知(入账/出账)
    • 参数签名
    • 账户(Acct)说明
    • 特别说明/精度对账

参数签名

算法说明

简要说明:按 ASCII 码从小到大排序,不要空值,Querystring 方式拼接,末尾拼上&key=密钥。(默认是小写)。sign 不参数签名 签名生成的通用步骤如下:

  1. 设所有发送或者接收到的数据为集合 M,将集合 M 内非空参数值的参数按照参数名 ASCII 码从小到大排序(字典序),使用 URL 键值对的格式(即 key1=value1&key2=value2…)拼接成字符串 stringA。
  2. 在 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": ""
}
  1. 组装签名原始字符串
  • 去掉空值
  • url编码
  • 按照参数名 ASCII 字典序排序
  • 拼接字符串 key1=value1&key2=value2&key3=value3...
  • 末尾拼接&key=密钥

上述数据将得到

amt=1.00&merid=12345&noti_url=https%3A%2F%2Fwww.baidu.com&tno=123123123123
  1. 对上一步中的字符串拼接&key=密钥
amt=1.00&merid=12345&noti_url=https%3A%2F%2Fwww.baidu.com&tno=123123123123&key=xxxxxxxxx
  1. 对上一步中字符串取 md5 值
signresult=md5("amt=1.00&merid=12345&noti_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&notify_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函数有问题,请注意此项标准

参考代码

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)
Prev
异步通知(入账/出账)
Next
账户(Acct)说明