签名规约
# 签名规约
PingPongCheckout API v3通过验证签名来保证请求的真实性和数据的完整性。
# 签名类型
签名类型 | 描述 |
---|---|
MD5 | 表示选择 MD5 算法,商户使用 Salt 对报文进行摘要签名和验签 |
SHA256 | 表示选择 SHA256算法,商户使用 Salt 对报文进行摘要签名和验签 |
# 请求签名
商户需要使用自身的私钥对消息体中关键数据的组合进行签名。没有携带签名或者签名验证不通过的请求,都不会被执行,并返回错误。
注意 :
请求参数都应该trim,加签应该在trim之后
请求签名是部分加签后,加签规则参考以下的请求签名范围
以下为PingPongCheckout API v3与调用方约定的请求加签参数列表
参数名 | 描述 |
---|---|
accId | PingPong商户店铺号 |
amount | 交易金额 |
clientId | PingPong商户号,当请求参数中没有传入clientId,不加入签名 |
cardNum | 卡号 |
currency | 交易币种 |
merchantTransactionId | 商户交易流水号 |
requestId | 请求ID |
signType | 加签类型 |
transactionId | PingPong交易流水号 |
# 请求签名范围
注意 :
1. 加签以每个接口的请求参数为基础。
2. 在加签参数列表的请求参数参与加签
3. 请求参数中没有,加签参数列表有,不参与加签
4. 请求参数中有,加签参数列表没有,不参与加签
5. 综上:
设待加签参数集合C
设请求参数集合A
设加签参数集合B
则 C=A∩B 取两者交集
# 应答签名
对于签名验证成功的请求,PingPong支付API v3会对应答进行签名。为了保证安全性,应对应答进行验签。
注意 :
sign 字段不参与签名
# 异步通知签名
签名范围和签名方法同请求签名
# 签名串组装
字典序:按首字母进行排序;
queryString:用'=' 进行参数名和参数值(trim 后的值)的拼接,用'&'进行多 个参数之间的拼接,即 key1=val1&key2=val2&key3=val3
对参数名按字典序排序后,按照queryString方式组装。
签名秘钥(salt)放入签名串的位置为: 签名串的开头 , 即{salt}key1=val2&key2=val2&key3=val3
# 计算签名串
推荐使用SHA256签名方式,安全度高于MD5
# 签名工具类
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import java.security.MessageDigest;
import java.util.Map;
import java.util.TreeMap;
@Slf4j
public class Sign {
/**
* 部分参数签名,参与签名的字段
*/
private static final String[] includeFields = {"accId", "amount", "clientId", "cardNum", "currency", "merchantTransactionId",
"requestId", "signType", "transactionId"};
/**
* 签名秘钥
*/
private String salt = null;
public Sign(String salt) {
this.salt = salt;
}
/**
* 执行签名
*
* @param signType 签名类型
* @param signMap 待签名串
*/
public String signature(String signType, TreeMap<String, Object> signMap) {
String signContent = getPartSignParams(signMap);
log.debug("signContent:{}", signContent);
if (StringUtils.equalsIgnoreCase("MD5",signType)) {
return md5Sign(salt, signContent);
} else if (StringUtils.equalsIgnoreCase("SHA256",signType)) {
return sha256(signContent, salt);
}
return null;
}
/**
* 获取待签名串(部分字段签名)
*/
private static String getPartSignParams(TreeMap<String, Object> signMap) {
//添加需要签名的字段
TreeMap<String, Object> resultMap = Maps.newTreeMap();
for (String param : includeFields) {
String value = (String) signMap.get(param);
if (StringUtils.isNotBlank(value)) {
resultMap.put(param, value);
}
}
return getSignParams(resultMap);
}
/**
* 获取待签名串
*/
private static String getSignParams(TreeMap<String, Object> resultMap) {
StringBuilder stringBuilder = new StringBuilder();
int paramNum = 0;
for (Map.Entry<String, Object> signEntry : resultMap.entrySet()) {
paramNum++;
stringBuilder.append(signEntry.getKey());
stringBuilder.append("=");
stringBuilder.append(signEntry.getValue());
if (paramNum < resultMap.size()) {
stringBuilder.append("&");
}
}
log.debug("content:【{}】", stringBuilder);
return stringBuilder.toString();
}
private static String md5Sign(String salt, String content) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(salt.getBytes());
md.update(content.getBytes());
byte[] digest = md.digest();
return byteToHexString(digest);
} catch (Exception e) {
log.error("md5签名失败", e);
}
return null;
}
private static String sha256(String content, String salt) {
try {
if (StringUtils.isBlank(salt)) {
throw new RuntimeException("salt is null");
}
String contentStr = salt.concat(content);
return DigestUtils.sha256Hex(contentStr.getBytes("UTF-8")).toUpperCase();
} catch (Exception e) {
log.error("sha256", e);
}
return null;
}
public static String byteToHexString(byte[] b) {
StringBuilder hexString = new StringBuilder();
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
hexString.append(hex.toUpperCase());
}
return hexString.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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// Make sure to add code blocks to your code group
上次更新: 2022/07/21, 12:09:10