平台分配的商户访问标识
签名说明
概述
所有 API 请求都必须经过认证和签名,以确保请求的安全性和完整性。
认证方式
Header 认证
每个请求必须在请求头中包含以下字段:
请求签名
示例
http
POST /api/v2/pay/submit
Content-Type: application/json
x-access-key: MERCHANT_001
signature: a1b2c3d4e5f6...签名算法
签名计算步骤
步骤一:准备参数
收集所有 body 参数,并排除:
signature字段本身- 值为
null或空字符串的参数
请求参数示例
json
{
"timestamp": "1742205605123",
"merchantOrderNo": "PHC202604170001",
"orderAmount": "1200",
"payMethod": "MAYA",
"callbackUrl": "https://merchant.example.com/payments/notify",
"redirectUrl": "https://merchant.example.com/payments/return"
}步骤二:参数排序
按参数名字母顺序(a-z)对所有参数进行排序。
示例
排序后:
callbackUrl, merchantOrderNo, orderAmount, payMethod, redirectUrl, timestamp步骤三:拼接字符串
将排序后的参数按 key=value 格式拼接,参数之间用 & 连接。参数值按请求中的原始字符串参与签名。
示例
callbackUrl=https://merchant.example.com/payments/notify&merchantOrderNo=PHC202604170001&orderAmount=1200&payMethod=MAYA&redirectUrl=https://merchant.example.com/payments/return×tamp=1742205605123步骤四:追加密钥
将商户密钥(Secret Key)追加到拼接字符串的末尾。
示例
callbackUrl=https://merchant.example.com/payments/notify&merchantOrderNo=PHC202604170001&orderAmount=1200&payMethod=MAYA&redirectUrl=https://merchant.example.com/payments/return×tamp=1742205605123{SECRET_KEY}步骤五:计算哈希
使用 SHA256 算法计算哈希值。
步骤六:转小写
将哈希结果转换为小写十六进制字符串。
最终签名
如果以上示例使用的密钥为 demo_secret_key_ph,则签名结果为:
baaa22c91470a152155a34284dbf32d6e21bd761a4067755db3d70e5bad28c30异步通知的验签规则与以上一致。回调中的签名字段同样为 signature,验签时应排除 signature 字段本身。
签名示例代码
Java
java
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
public class SignUtil {
public static String sign(Map<String, Object> params, String secretKey) throws NoSuchAlgorithmException {
TreeMap<String, String> sortedParams = new TreeMap<>();
for (Map.Entry<String, Object> entry : params.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value != null && !"".equals(value) && !"signature".equals(key)) {
sortedParams.put(key, value.toString());
}
}
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
if (sb.length() > 0) {
sb.append("&");
}
sb.append(entry.getKey()).append("=").append(entry.getValue());
}
sb.append(secretKey);
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(b & 0xff);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString().toLowerCase();
}
}Node.js
javascript
const crypto = require('crypto');
function generateSignature(params, secretKey) {
const filteredParams = {};
for (const [key, value] of Object.entries(params)) {
if (value !== null && value !== '' && key !== 'signature') {
filteredParams[key] = value;
}
}
const sortedKeys = Object.keys(filteredParams).sort();
const signStr = sortedKeys
.map(key => `${key}=${filteredParams[key]}`)
.join('&');
return crypto.createHash('sha256').update(signStr + secretKey).digest('hex').toLowerCase();
}Java 调用示例
java
Map<String, Object> params = new HashMap<>();
params.put("timestamp", "1742205605123");
params.put("merchantOrderNo", "PHC202604170001");
params.put("orderAmount", "1200");
params.put("payMethod", "MAYA");
params.put("callbackUrl", "https://merchant.example.com/payments/notify");
params.put("redirectUrl", "https://merchant.example.com/payments/return");
String signature = SignUtil.sign(params, "demo_secret_key_ph");
System.out.println(signature);
// baaa22c91470a152155a34284dbf32d6e21bd761a4067755db3d70e5bad28c30Node.js 调用示例
javascript
const params = {
timestamp: '1742205605123',
merchantOrderNo: 'PHC202604170001',
orderAmount: '1200',
payMethod: 'MAYA',
callbackUrl: 'https://merchant.example.com/payments/notify',
redirectUrl: 'https://merchant.example.com/payments/return',
};
const signature = generateSignature(params, 'demo_secret_key_ph');
console.log(signature);
// baaa22c91470a152155a34284dbf32d6e21bd761a4067755db3d70e5bad28c30