授权

平台通过请求头里的商户号秘钥/签名,来验证商户的API请求。

商户号:标记所属商户,请求头里设置为:X-SN

秘钥:验证请求合法性的默认手段,请求头里设置为:X-SECRET

测试环境下:

KEYVALUE
X-SNuS2gJE
X-SECRETFTOFCAPKVPTEKUCWLWSZ3WSUONYGJGTV

📘

生产环境的商户号和秘钥,在开户邮件里获取,也可以通过支付后台查看和重置

签名验证请求合法性的升级手段,它和秘钥是二选一。默认情况下是关闭的,但是一旦您在支付后台开了验签,则必须使用签名

签名:验证请求合法性的升级手段,请求头里设置为:X-SIGN

如何签名

  1. 使用HMAC-SHA256算法加密。HMAC-SHA256算法接收2个参数:秘钥和加密数据。秘钥就是商户秘钥X-SECRET,加密数据就是整个请求体的字符串(注意不要对字符串进行排序,直接使用请求体字符串)。如果请求体是空,加密数据使用空字符串:“”
  2. 对上面得到的加密后的字节数据进行Base64编码

验签示例

以下示例最终签名结果是:3YGTuvnoXQCVfPwrbRkyhX2AWA1aM7CyShu/dM+yaDY=

package org.example;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class HmacSha256Signature {
    public static void main(String[] args) {
        String secretKey = "FTOFCAPKVPTEKUCWLWSZ3WSUONYGJGTV";
        /* 请注意body是无序的,不需要排序再加签和验签 */
        String callbackBody = "{\"referenceId\":\"sj-test-order-1724315286\",\"orders\":[{\"id\":\"1826536970790375425\",\"msn\":\"220240822152843894281\",\"url\":\"https://kr9bp.app.link/pay?bizNo=20240822111212800110166024102271085&timestamp=1724315325304&originSourcePlatform=IPG&mid=216620000001268751652&did=216650000001701499656&sid=216660000001701500654&sign=g9nYgix741KbQfvbYrESgYRzWQYnKYc3b9ZgYLrYDGWB%2BSsWGnURjCl6EKo3jc41gUaFhcegaF5H0OskXHDvhOp3alCbsna%2Bfy%2FCHK2lx69n4AC%2B7h%2FrGud%2Bxh0jcDTNeXiyeL%2BWSy2qQ59tHG2PwEoUYb1imIpZItv0AaUqOvVph1csLX%2Bb5CN3IODEBdPi9%2BIBIy6acShN56juAjAMlXFAnvTY%2BSeU6fnGW1a31TWhv8x%2FTLy73l02bkjB9KFbvrzavWijMv6mk91AtDNmJtKPpXFug9qUftYGWuTy%2Fntk3jDQQqv62q1%2B5x%2FC1bKweLJjHakaA0Yc%2B%2BdKxsLCVA%3D%3D&phoneNumber=895322906971&forceToH5=false\",\"urlType\":\"URL\",\"method\":\"DANA\",\"status\":\"SUCCEED\",\"amount\":10000,\"receivedAmount\":10000,\"serviceFee\":100.0000,\"receivedTime\":1724315346000}]}";
        try {
            String signature = hmacSha256(secretKey, callbackBody);
            System.out.println("Signature: " + signature);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
        }
    }
    public static String hmacSha256(String key, String data) throws NoSuchAlgorithmException, InvalidKeyException {
        Mac sha256Hmac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "HmacSHA256");
        sha256Hmac.init(secretKey);
        byte[] signedBytes = sha256Hmac.doFinal(data.getBytes());
        return Base64.getEncoder().encodeToString(signedBytes);
    }
}
import hmac
import hashlib
import base64
# 请注意,callbackBody 是无序的,不需要排序再加签和验签
callback_body = '{"referenceId":"sj-test-order-1724315286","orders":[{"id":"1826536970790375425","msn":"220240822152843894281","url":"https://kr9bp.app.link/pay?bizNo=20240822111212800110166024102271085&timestamp=1724315325304&originSourcePlatform=IPG&mid=216620000001268751652&did=216650000001701499656&sid=216660000001701500654&sign=g9nYgix741KbQfvbYrESgYRzWQYnKYc3b9ZgYLrYDGWB%2BSsWGnURjCl6EKo3jc41gUaFhcegaF5H0OskXHDvhOp3alCbsna%2Bfy%2FCHK2lx69n4AC%2B7h%2FrGud%2Bxh0jcDTNeXiyeL%2BWSy2qQ59tHG2PwEoUYb1imIpZItv0AaUqOvVph1csLX%2Bb5CN3IODEBdPi9%2BIBIy6acShN56juAjAMlXFAnvTY%2BSeU6fnGW1a31TWhv8x%2FTLy73l02bkjB9KFbvrzavWijMv6mk91AtDNmJtKPpXFug9qUftYGWuTy%2Fntk3jDQQqv62q1%2B5x%2FC1bKweLJjHakaA0Yc%2B%2BdKxsLCVA%3D%3D&phoneNumber=895322906971&forceToH5=false","urlType":"URL","method":"DANA","status":"SUCCEED","amount":10000,"receivedAmount":10000,"serviceFee":100.0000,"receivedTime":1724315346000}]}'
secret_key = "FTOFCAPKVPTEKUCWLWSZ3WSUONYGJGTV"
# 使用 HMAC-SHA256 对 callbackBody 进行加密
hmac_digest = hmac.new(secret_key.encode(), callback_body.encode(), hashlib.sha256).digest()
# 将二进制结果转换为 Base64 编码
signature = base64.b64encode(hmac_digest).decode()
print("Signature:", signature)
// 请注意,$callbackBody 是无序的,不需要排序再加签和验签
$callbackBody = '{"referenceId":"sj-test-order-1724315286","orders":[{"id":"1826536970790375425","msn":"220240822152843894281","url":"https://kr9bp.app.link/pay?bizNo=20240822111212800110166024102271085&timestamp=1724315325304&originSourcePlatform=IPG&mid=216620000001268751652&did=216650000001701499656&sid=216660000001701500654&sign=g9nYgix741KbQfvbYrESgYRzWQYnKYc3b9ZgYLrYDGWB%2BSsWGnURjCl6EKo3jc41gUaFhcegaF5H0OskXHDvhOp3alCbsna%2Bfy%2FCHK2lx69n4AC%2B7h%2FrGud%2Bxh0jcDTNeXiyeL%2BWSy2qQ59tHG2PwEoUYb1imIpZItv0AaUqOvVph1csLX%2Bb5CN3IODEBdPi9%2BIBIy6acShN56juAjAMlXFAnvTY%2BSeU6fnGW1a31TWhv8x%2FTLy73l02bkjB9KFbvrzavWijMv6mk91AtDNmJtKPpXFug9qUftYGWuTy%2Fntk3jDQQqv62q1%2B5x%2FC1bKweLJjHakaA0Yc%2B%2BdKxsLCVA%3D%3D&phoneNumber=895322906971&forceToH5=false","urlType":"URL","method":"DANA","status":"SUCCEED","amount":10000,"receivedAmount":10000,"serviceFee":100.0000,"receivedTime":1724315346000}]}';
$secretKey = "FTOFCAPKVPTEKUCWLWSZ3WSUONYGJGTV";
$signature = base64_encode(hash_hmac('sha256', $callbackBody, $secretKey, true));
echo "Signature: " . $signature;
?>
using System;
using System.Security.Cryptography;
using System.Text;
public class HmacSha256Signature
{
    public static void Main(string[] args)
    {
        // 请注意,$callbackBody 是无序的,不需要排序再加签和验签
        string secretKey = "FTOFCAPKVPTEKUCWLWSZ3WSUONYGJGTV";
        string callbackBody = "{\"referenceId\":\"sj-test-order-1724315286\",\"orders\":[{\"id\":\"1826536970790375425\",\"msn\":\"220240822152843894281\",\"url\":\"https://kr9bp.app.link/pay?bizNo=20240822111212800110166024102271085&timestamp=1724315325304&originSourcePlatform=IPG&mid=216620000001268751652&did=216650000001701499656&sid=216660000001701500654&sign=g9nYgix741KbQfvbYrESgYRzWQYnKYc3b9ZgYLrYDGWB%2BSsWGnURjCl6EKo3jc41gUaFhcegaF5H0OskXHDvhOp3alCbsna%2Bfy%2FCHK2lx69n4AC%2B7h%2FrGud%2Bxh0jcDTNeXiyeL%2BWSy2qQ59tHG2PwEoUYb1imIpZItv0AaUqOvVph1csLX%2Bb5CN3IODEBdPi9%2BIBIy6acShN56juAjAMlXFAnvTY%2BSeU6fnGW1a31TWhv8x%2FTLy73l02bkjB9KFbvrzavWijMv6mk91AtDNmJtKPpXFug9qUftYGWuTy%2Fntk3jDQQqv62q1%2B5x%2FC1bKweLJjHakaA0Yc%2B%2BdKxsLCVA%3D%3D&phoneNumber=895322906971&forceToH5=false\",\"urlType\":\"URL\",\"method\":\"DANA\",\"status\":\"SUCCEED\",\"amount\":10000,\"receivedAmount\":10000,\"serviceFee\":100.0000,\"receivedTime\":1724315346000}]}";
        string signature = CreateToken(callbackBody, secretKey);
        Console.WriteLine("Signature: " + signature);
    }
    private static string CreateToken(string message, string secret)
    {
        secret = secret ?? "";
        var encoding = new ASCIIEncoding();
        byte[] keyByte = encoding.GetBytes(secret);
        byte[] messageBytes = encoding.GetBytes(message);
        using (var hmacsha256 = new HMACSHA256(keyByte))
        {
            byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
            return Convert.ToBase64String(hashmessage);
        }
    }
}
package main
import (
 "crypto/hmac"
 "crypto/sha256"
 "encoding/base64"
 "fmt"
)
func main() {
// 请注意,$callbackBody 是无序的,不需要排序再加签和验签
 callbackBody := `{"referenceId":"sj-test-order-1724315286","orders":[{"id":"1826536970790375425","msn":"220240822152843894281","url":"https://kr9bp.app.link/pay?bizNo=20240822111212800110166024102271085&timestamp=1724315325304&originSourcePlatform=IPG&mid=216620000001268751652&did=216650000001701499656&sid=216660000001701500654&sign=g9nYgix741KbQfvbYrESgYRzWQYnKYc3b9ZgYLrYDGWB%2BSsWGnURjCl6EKo3jc41gUaFhcegaF5H0OskXHDvhOp3alCbsna%2Bfy%2FCHK2lx69n4AC%2B7h%2FrGud%2Bxh0jcDTNeXiyeL%2BWSy2qQ59tHG2PwEoUYb1imIpZItv0AaUqOvVph1csLX%2Bb5CN3IODEBdPi9%2BIBIy6acShN56juAjAMlXFAnvTY%2BSeU6fnGW1a31TWhv8x%2FTLy73l02bkjB9KFbvrzavWijMv6mk91AtDNmJtKPpXFug9qUftYGWuTy%2Fntk3jDQQqv62q1%2B5x%2FC1bKweLJjHakaA0Yc%2B%2BdKxsLCVA%3D%3D&phoneNumber=895322906971&forceToH5=false","urlType":"URL","method":"DANA","status":"SUCCEED","amount":10000,"receivedAmount":10000,"serviceFee":100.0000,"receivedTime":1724315346000}]}`
 secretKey := "FTOFCAPKVPTEKUCWLWSZ3WSUONYGJGTV"
 signature := generateHmacSha256(callbackBody, secretKey)
 fmt.Println("Signature:", signature)
}
func generateHmacSha256(data, key string) string {
 mac := hmac.New(sha256.New, []byte(key))
 mac.Write([]byte(data))
 expectedMAC := mac.Sum(nil)
 return base64.StdEncoding.EncodeToString(expectedMAC)
}
const crypto = require('crypto');
/**
 * 请注意,callbackBody 是无序的,不需要排序再加签和验签
 */
const callbackBody = '{"referenceId":"sj-test-order-1724315286","orders":[{"id":"1826536970790375425","msn":"220240822152843894281","url":"https://kr9bp.app.link/pay?bizNo=20240822111212800110166024102271085&timestamp=1724315325304&originSourcePlatform=IPG&mid=216620000001268751652&did=216650000001701499656&sid=216660000001701500654&sign=g9nYgix741KbQfvbYrESgYRzWQYnKYc3b9ZgYLrYDGWB%2BSsWGnURjCl6EKo3jc41gUaFhcegaF5H0OskXHDvhOp3alCbsna%2Bfy%2FCHK2lx69n4AC%2B7h%2FrGud%2Bxh0jcDTNeXiyeL%2BWSy2qQ59tHG2PwEoUYb1imIpZItv0AaUqOvVph1csLX%2Bb5CN3IODEBdPi9%2BIBIy6acShN56juAjAMlXFAnvTY%2BSeU6fnGW1a31TWhv8x%2FTLy73l02bkjB9KFbvrzavWijMv6mk91AtDNmJtKPpXFug9qUftYGWuTy%2Fntk3jDQQqv62q1%2B5x%2FC1bKweLJjHakaA0Yc%2B%2BdKxsLCVA%3D%3D&phoneNumber=895322906971&forceToH5=false","urlType":"URL","method":"DANA","status":"SUCCEED","amount":10000,"receivedAmount":10000,"serviceFee":100.0000,"receivedTime":1724315346000}]}';
const secretKey = "FTOFCAPKVPTEKUCWLWSZ3WSUONYGJGTV";
const signature = generateHmacSha256(callbackBody, secretKey);
console.log("Signature:", signature);
function generateHmacSha256(data, key) {
  const hmac = crypto.createHmac('sha256', key);
  hmac.update(data);
  return hmac.digest('base64');
}

回调验签

平台给商户的回调都是有签名,也是在header里标记为X-SIGN,商户可以自行决定要不要对支付的回调信息进行验签。

❗️

加签是直接对请求体字符串进行签名,不要排序之后再签名。验签也是这个过程,不要把请求体字符串转成JSON对象或者POJO之后再验签,因为各个语言、系统对格式、数字精度默认处理不一样,可能会造成验签失败