跳到主要内容

签名说明

为保证数据传输的安全,我们对请求参数进行签名验证。

签名算法

我们使用 HMAC-SHA256 签名算法。请求中必须包含 sign_type 参数:

{
"platform_id": "PF0002",
"sign_type": "HMAC-SHA256",
"sign": "..."
}

范例商户信息

以下所有示例使用相同的测试商户信息:

项目
商户 ID (platform_id)PF0002
平台密钥 (platform_key)ThisIsYourSecretKey123

签名规则

  1. 参数整理:将所有参数按参数名 ASCII 码由小到大排序
  2. 排除参数signsign_type、空值参数
  3. 拼接字符串:以 key=value 形式用 & 连接(不要做 URL encoding,使用原始值)
  4. HMAC-SHA256 签名:使用密钥对字符串进行 HMAC-SHA256 签名
  5. 转小写:将结果转为 64 位小写十六进制字符串

陣列參數處理

當請求參數包含陣列(如 last_numbers)時,需將陣列轉換為 JSON 字串格式參與簽名。

範例last_numbers["12345", "67890"] 時:

last_numbers=["12345","67890"]
注意

陣列轉換為 JSON 字串時,不可有多餘空格,元素之間僅以逗號分隔。

代收示例

请求参数

{
"platform_id": "PF0002",
"service_id": "SVC0001",
"payment_cl_id": "DEVPM00014581",
"amount": "50000",
"notify_url": "https://your-domain.com/callback",
"request_time": "1595504136",
"sign_type": "HMAC-SHA256"
}

步骤 1:排序并拼接(排除 sign、sign_type)

amount=50000&notify_url=https://your-domain.com/callback&payment_cl_id=DEVPM00014581&platform_id=PF0002&request_time=1595504136&service_id=SVC0001

步骤 2:HMAC-SHA256 签名

使用密钥 ThisIsYourSecretKey123 签名,得到:

e8a5c3f2d1b4a6e9c7f0d2b5a8e1c4f7d0b3a6e9c2f5d8b1a4e7c0f3d6b9a2e5

代码示例

cURL
# 1. 拼接参数字符串(已排序,排除 sign 和 sign_type)
PARAM_STR="amount=50000&notify_url=https://your-domain.com/callback&payment_cl_id=DEVPM00014581&platform_id=PF0002&request_time=1595504136&service_id=SVC0001"
PLATFORM_KEY="ThisIsYourSecretKey123"

# 2. HMAC-SHA256 签名
SIGN=$(echo -n "$PARAM_STR" | openssl dgst -sha256 -hmac "$PLATFORM_KEY" | awk '{print $2}')
echo $SIGN
Python
import hmac
import hashlib

def generate_sign_hmac_sha256(params: dict, platform_key: str) -> str:
filtered = {k: v for k, v in params.items()
if v and k not in ['sign', 'sign_type']}
sorted_keys = sorted(filtered.keys())
param_str = '&'.join([f'{k}={filtered[k]}' for k in sorted_keys])

return hmac.new(
platform_key.encode('utf-8'),
param_str.encode('utf-8'),
hashlib.sha256
).hexdigest().lower()
Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;

public class SignatureUtil {
public static String generateHmacSha256(Map<String, String> params, String key) {
TreeMap<String, String> filtered = new TreeMap<>();
for (Map.Entry<String, String> e : params.entrySet()) {
if (e.getValue() != null && !e.getValue().isEmpty()
&& !e.getKey().equals("sign") && !e.getKey().equals("sign_type")) {
filtered.put(e.getKey(), e.getValue());
}
}

StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> e : filtered.entrySet()) {
if (sb.length() > 0) sb.append("&");
sb.append(e.getKey()).append("=").append(e.getValue());
}

try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"));
byte[] hash = mac.doFinal(sb.toString().getBytes("UTF-8"));
StringBuilder hex = new StringBuilder();
for (byte b : hash) hex.append(String.format("%02x", b));
return hex.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
PHP
<?php
function generateSignHmacSha256(array $params, string $platformKey): string {
$filtered = array_filter($params, function($v, $k) {
return !empty($v) && !in_array($k, ['sign', 'sign_type']);
}, ARRAY_FILTER_USE_BOTH);

ksort($filtered);
$pairs = [];
foreach ($filtered as $k => $v) {
$pairs[] = $k . '=' . $v;
}
$paramStr = implode('&', $pairs);

return strtolower(hash_hmac('sha256', $paramStr, $platformKey));
}
?>
Go
package main

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"sort"
"strings"
)

func GenerateSignHmacSha256(params map[string]string, platformKey string) string {
var keys []string
for k, v := range params {
if v != "" && k != "sign" && k != "sign_type" {
keys = append(keys, k)
}
}
sort.Strings(keys)

var pairs []string
for _, k := range keys {
pairs = append(pairs, k+"="+params[k])
}
paramStr := strings.Join(pairs, "&")

h := hmac.New(sha256.New, []byte(platformKey))
h.Write([]byte(paramStr))
return strings.ToLower(hex.EncodeToString(h.Sum(nil)))
}
JavaScript
const crypto = require('crypto');

function generateSignHmacSha256(params, platformKey) {
// 过滤并排序
const filtered = Object.entries(params)
.filter(([k, v]) => v && k !== 'sign' && k !== 'sign_type')
.sort(([a], [b]) => a.localeCompare(b));

// 拼接
const paramStr = filtered.map(([k, v]) => `${k}=${v}`).join('&');

// HMAC-SHA256
return crypto
.createHmac('sha256', platformKey)
.update(paramStr)
.digest('hex')
.toLowerCase();
}

验证回调签名

收到回调通知时,请验证签名以确保数据未被篡改:

  1. 使用 HMAC-SHA256 算法重新计算签名
  2. 比对计算结果与 sign 是否一致
安全建议

务必验证回调签名,防止伪造回调攻击。


常见问题

签名错误 (error_code: 0004)

常见原因:

  1. 参数值做了 URL encoding - 签名字符串使用原始值拼接,不要做 URL encoding(例如 notify_url 应保持 https://... 原样,而非 https%3A%2F%2F...
  2. 参数值不一致 - 确保签名时的参数值与实际请求完全一致
  3. 未排除 sign_type - 签名计算时应排除 signsign_type
  4. 编码问题 - 确保使用 UTF-8 编码