签名
在支付过程中,签名通常应用于交易请求和响应。商家使用秘钥对交易数据进行组装签名。这确保了支付信息的安全和可靠性。
签名方式:sha256
签名过程
获取商户秘钥 :登入Onerway后台 >> 点击"账户中心" >> "账户信息" >> 获取页面上 Secret key 的值
需要签名的数据:首先获取请求参数中
签名
列为YES
的参数
。所有非空请求参数,根据参数名称的ASCII
码排序,然后以vaule1vaule2vaule3...
的方式将值拼接起来,再在字符串末尾加上商户秘钥
;将字符串进行
sha256
签名 ;
异步通知有单独的签名方式
签名示例
json
// 未排序的参数
{
"test3": "test3value",
"test2": "test2value",
"test1": "0",
"test5": "",
"test4": "test4value",
"test6": null
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
1. 首先获取签名
列为YES
以及值为不空的参数
即剔除掉test2
,test5
,test6
text
{
"test2": "test2value", // 签名列为NO,不参与签名
"test1": "0",
"test3": "test3value",
"test5": "",
"test4": "test4value",
"test6": null
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
2. 将参数字段升序排序
text
{
"test1": "0",
"test2": "test2value", // 签名列为NO,不参与签名
"test3": "test3value",
"test4": "test4value",
"test5": "",
"test6": null
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
3. 然后将值拼接成字符串
0test3valuetest4value
4. 在拼接字符串的末尾加上商户秘钥
商户密钥: 3b5e10b65bff4172a5b9ca2d2ec00a6e
拼接后的字符串: 0test3valuetest4value3b5e10b65bff4172a5b9ca2d2ec00a6e
5. 最后进行sha256
签名
836831ae68fce5e61d4a64363bb72c636efe6e94b62ecaa8d1c95fc58cc9cbed
加签代码示例
签名注意事项
- 进行签名时,所有key对应的value都应该是字符串。
- 如果value是
object
,请先将object
转换成字符串。参考下方billingInformation
、shippingInformation
字段 - 如果value是
object
且object
嵌套有object
,请先将内层object
转换成字符串,再转换外层object
。参考下方txnOrderMsg
字段,其中products
字段的值是一个由数组转换成字符串再赋值的字符串
以收银台支付请求参数为例:
json
{
"billingInformation": "{\"country\":\"DE\",\"email\":\"abel.wang@onerway.com\",\"firstName\":\"şş\",\"lastName\":\"café\",\"phone\":\"17700492982\",\"address\":\"Apt. 870\",\"city\":\"Akşehir\",\"postalCode\":\"66977\",\"identityNumber\":\"12345678\",\"province\":\"Akşehir\"}",
"merchantCustId": "1723097638000",
"merchantNo": "800209",
"merchantTxnId": "1723097638000",
"merchantTxnTime": "2024-08-08 14:13:58",
"merchantTxnTimeZone": "+08:00",
"orderAmount": "100",
"orderCurrency": "USD",
"productType": "CARD",
"shippingInformation": "{\"country\":\"DE\",\"email\":\"abel.wang@onerway.com\",\"firstName\":\"şş\",\"lastName\":\"café\",\"phone\":\"17700492982\",\"address\":\"Apt. 870\",\"city\":\"Akşehir\",\"postalCode\":\"66977\",\"identityNumber\":\"12345678\",\"province\":\"Akşehir\"}",
"sign": "d23532edf2d8d4c6cb21b622bfbef4f067ef9ac2796e89117578037d11b04e98",
"subProductType": "DIRECT",
"txnOrderMsg": "{\"products\":\"[{\\\"price\\\":\\\"110.00\\\",\\\"num\\\":\\\"1\\\",\\\"name\\\":\\\"iphone11\\\",\\\"currency\\\":\\\"USD\\\"}]\",\"returnUrl\":\"https://docs.onerway.com/\",\"transactionIp\":\"127.0.0.1\",\"appId\":\"1739545982264549376\",\"javaEnabled\":false,\"colorDepth\":\"24\",\"screenHeight\":\"1080\",\"screenWidth\":\"1920\",\"timeZoneOffset\":\"-480\",\"accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\",\"userAgent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36\",\"contentLength\":\"340\",\"language\":\"zh-CN\"}",
"txnType": "SALE"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
php
/*
$params 需要加签的数据;
参数类型一维数组array;
$PrivateKey 商户秘钥;
参数类型字符串;
*/
//ASCII码排序加密
function ASCII_HASH($params , $payment_pacypayment_secret){
$PrivateKey = $payment_pacypayment_secret;
if(!empty($params)){
$p = ksort($params);
// 异步通知待剔除的不参与验签字段
$badkey = array('originTransactionId','originMerchantTxnId','customsDeclarationAmount','customsDeclarationCurrency','paymentMethod','walletTypeName','periodValue','tokenExpireTime','sign'); // 不参与验签字段
if($p){
$strs = '';
foreach ($params as $k=>$val){
if((!empty($val) || $val == 0) && $k != 'sign' && $k != 'route' && !in_array($k, $badkey))
{
$strs .= $val ;
}
}
$strs = $strs.$PrivateKey ;
return hash('sha256' , $strs);
}
}
return 'error';
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
java
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.TreeMap;
@Slf4j
public class HashUtil {
public static void main(String[] args) {
TreeMap<String, Object> params = new TreeMap();
params.put("merchantNo","800135");
params.put("test","yesdas1dsa");
params.put("bizContent","dsaaass1dsag");
params.put("as","12334567");
params.put("bc","098754");
// TODO: 改成自己的商户密钥
String secret = "你的商户密钥";
// 将请求参数和商户密钥拼接成字符串
String data = concatValue(params) + secret;
// 生成签名
String sign = hash(data);
System.out.println("生成的签名 = " + sign);
}
/**
* 剔除空值和不参与验签的参数,拼接value成字符串
*
* @param data 请求参数
* @return 拼接的字符串
*/
public static String concatValue(TreeMap<String, Object> data) {
// 异步通知待剔除的不参与验签字段,退款接口originTransactionId不能过滤
List<String> filteredOut = Arrays.asList("originTransactionId", "originMerchantTxnId", "customsDeclarationAmount", "customsDeclarationCurrency", "paymentMethod", "walletTypeName", "periodValue", "tokenExpireTime", "sign");
StringBuilder sb = new StringBuilder();
for (String key : data.keySet()) {
if (data.get(key) != null && !data.get(key).equals("") && !filteredOut.contains(entry.getKey())) {
sb.append(data.get(key));
}
}
return sb.toString();
}
/**
* 将拼接的字符串进行SHA-256加密
*
* @param concatStr 拼接的字符串
* @return 加密后的字符串
*/
public static String hash(String concatStr) {
String sign = null;
final String algorithm = "SHA-256";
try {
MessageDigest md = MessageDigest.getInstance(algorithm);
System.out.println("拼接的字符串 = " + concatStr);
md.update(concatStr.getBytes(StandardCharsets.UTF_8));
sign = byte2Hex(md.digest());
} catch (NoSuchAlgorithmException e) {
log.error("Error: NoSuchAlgorithmException, Algorithm {} is not available", algorithm, e);
} catch (Exception e) {
log.error("Error while encrypting message: {}", e.getMessage());
}
return sign;
}
/**
* 将字节数组转换为十六进制字符串表示形式。
*
* @param bytes 需要转换的字节数组
* @return 字节数组的十六进制字符串表示形式
*/
public static String byte2Hex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte value : bytes) {
String hexValue = Integer.toHexString(value & 0xFF);
if (hexValue.length() == 1) {
sb.append("0");
}
sb.append(hexValue);
}
return sb.toString();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
python
import hashlib
from collections import OrderedDict
class HashUtil:
@staticmethod
def concat_value(data):
"""
剔除空值和不参与签名字段,拼接value成字符串
"""
# 异步通知待剔除的不参与验签字段,退款接口originTransactionId不能过滤
filtered_out = ["originTransactionId", "originMerchantTxnId", "customsDeclarationAmount", "customsDeclarationCurrency", "paymentMethod", "walletTypeName", "periodValue", "tokenExpireTime", "sign"] // [!code error]
return ''.join(str(value) for key, value in data.items() if value is not None and key not in filtered_out)
@staticmethod
def hash(concat_str):
"""
将拼接的字符串进行SHA-256加密
"""
try:
# Encode the concatenated string to bytes
concat_bytes = concat_str.encode('utf-8')
# Create a SHA-256 hash object
sha_signature = hashlib.sha256(concat_bytes).hexdigest()
print("Concatenated String:", concat_str)
return sha_signature
except Exception as e:
print("Error while encrypting message:", str(e))
return None
@staticmethod
def byte2hex(bytes_val):
"""
将字节数组转换为十六进制字符串表示形式。
"""
return ''.join(f'{byte:02x}' for byte in bytes_val)
if __name__ == "__main__":
# 对字段按字母顺序排序
params = OrderedDict([
("as", "12334567"),
("bc", "098754"),
("bizContent", "dsaaass1dsag"),
("merchantNo", "800135"),
("test", "yesdas1dsa"),
])
# TODO: 改成自己的商户密钥
secret = "你的商户密钥"
# 将请求参数和商户密钥拼接成字符串
data = HashUtil.concat_value(params) + secret
# 生成签名
sign = HashUtil.hash(data)
print("生成的签名 =", sign)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55