feat(pay): 新增收货地址DTO和签名服务实现
- 新增ShippingDTO用于收货地址信息传输 - 新增SignatureService接口定义签名生成与验证方法 - 实现SignatureServiceImpl支持PingPong支付签名逻辑 - 支持MD5和SHA256两种签名算法 - 添加签名参数过滤和排序功能 - 集成PingPong配置属性进行签名处理
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
package com.mtkj.mtpay.dto.risk;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 收货地址DTO
|
||||
*/
|
||||
@Data
|
||||
public class ShippingDTO implements Serializable {
|
||||
|
||||
private String firstName;
|
||||
|
||||
private String lastName;
|
||||
|
||||
private String phone;
|
||||
|
||||
private String email;
|
||||
|
||||
private String street;
|
||||
|
||||
private String postcode;
|
||||
|
||||
private String city;
|
||||
|
||||
private String state;
|
||||
|
||||
private String country;
|
||||
|
||||
private String lastModifierStreetTime;
|
||||
|
||||
private String lastModifierPhoneTime;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.mtkj.mtpay.service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 签名服务接口
|
||||
* 负责生成和验证PingPong支付接口的签名
|
||||
*/
|
||||
public interface SignatureService {
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
*
|
||||
* @param params 待签名参数Map
|
||||
* @return 签名(大写MD5)
|
||||
*/
|
||||
String generateSign(Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
*
|
||||
* @param params 待签名参数Map
|
||||
* @param secret 签名密钥
|
||||
* @param signType 签名类型(MD5或SHA256)
|
||||
* @return 签名(大写)
|
||||
*/
|
||||
String generateSign(Map<String, Object> params, String secret, String signType);
|
||||
|
||||
/**
|
||||
* 验证签名
|
||||
*
|
||||
* @param params 包含sign和signType的参数Map
|
||||
* @return 验证结果
|
||||
*/
|
||||
boolean verifySign(Map<String, Object> params);
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.mtkj.mtpay.service.impl;
|
||||
|
||||
import com.mtkj.mtpay.config.PingPongProperties;
|
||||
import com.mtkj.mtpay.service.SignatureService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 签名服务实现类
|
||||
* 负责生成和验证PingPong支付接口的签名
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SignatureServiceImpl implements SignatureService {
|
||||
|
||||
private final PingPongProperties pingPongProperties;
|
||||
|
||||
/**
|
||||
* 加签参数列表(根据文档定义)
|
||||
*/
|
||||
private static final Set<String> SIGN_SCOPE = Set.of(
|
||||
"clientId",
|
||||
"accId",
|
||||
"amount",
|
||||
"currency",
|
||||
"cardNum",
|
||||
"transactionId",
|
||||
"merchantTransactionId",
|
||||
"requestId",
|
||||
"signType",
|
||||
"notificationUrl",
|
||||
"shopperResultUrl"
|
||||
);
|
||||
|
||||
@Override
|
||||
public String generateSign(Map<String, Object> params) {
|
||||
return generateSign(params, pingPongProperties.getSecret(), pingPongProperties.getSignType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSign(Map<String, Object> params, String secret, String signType) {
|
||||
// 1. 筛选出需要参与签名的参数
|
||||
Map<String, Object> signParams = filterSignParams(params);
|
||||
|
||||
// 2. 按照字典序排序
|
||||
Map<String, Object> sortedParams = new TreeMap<>(signParams);
|
||||
|
||||
// 3. 构建签名串:{secret}key1=val1&key2=val2...
|
||||
StringBuilder signStr = new StringBuilder(secret);
|
||||
for (Map.Entry<String, Object> entry : sortedParams.entrySet()) {
|
||||
String value = entry.getValue() != null ? entry.getValue().toString().trim() : "";
|
||||
signStr.append(entry.getKey()).append("=").append(value).append("&");
|
||||
}
|
||||
|
||||
// 移除最后一个&
|
||||
if (signStr.length() > 0 && signStr.charAt(signStr.length() - 1) == '&') {
|
||||
signStr.setLength(signStr.length() - 1);
|
||||
}
|
||||
|
||||
String signContent = signStr.toString();
|
||||
log.debug("签名串: {}", signContent);
|
||||
|
||||
// 4. 进行MD5或SHA256运算并转大写
|
||||
String sign = hash(signContent, signType).toUpperCase();
|
||||
log.debug("生成的签名: {}", sign);
|
||||
|
||||
return sign;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifySign(Map<String, Object> params) {
|
||||
String receivedSign = (String) params.get("sign");
|
||||
if (receivedSign == null || receivedSign.isEmpty()) {
|
||||
log.warn("签名参数为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
String signType = (String) params.getOrDefault("signType", pingPongProperties.getSignType());
|
||||
|
||||
// 移除sign参数后计算签名
|
||||
Map<String, Object> paramsWithoutSign = new HashMap<>(params);
|
||||
paramsWithoutSign.remove("sign");
|
||||
|
||||
String calculatedSign = generateSign(paramsWithoutSign, pingPongProperties.getSecret(), signType);
|
||||
|
||||
boolean isValid = receivedSign.equalsIgnoreCase(calculatedSign);
|
||||
if (!isValid) {
|
||||
log.warn("签名验证失败,接收到的签名: {}, 计算出的签名: {}", receivedSign, calculatedSign);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 筛选出需要参与签名的参数
|
||||
*
|
||||
* @param params 所有参数
|
||||
* @return 需要签名的参数
|
||||
*/
|
||||
private Map<String, Object> filterSignParams(Map<String, Object> params) {
|
||||
Map<String, Object> signParams = new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, Object> entry : params.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
|
||||
// 跳过sign参数本身
|
||||
if ("sign".equals(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 只保留在加签参数列表中的参数,且值不为空
|
||||
if (SIGN_SCOPE.contains(key) && value != null && !value.toString().trim().isEmpty()) {
|
||||
signParams.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return signParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* 哈希运算
|
||||
*
|
||||
* @param content 待哈希内容
|
||||
* @param algorithm 算法类型(MD5或SHA256)
|
||||
* @return 哈希值(小写)
|
||||
*/
|
||||
private String hash(String content, String algorithm) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance(algorithm);
|
||||
byte[] hashBytes = digest.digest(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : hashBytes) {
|
||||
String hex = Integer.toHexString(0xff & b);
|
||||
if (hex.length() == 1) {
|
||||
hexString.append('0');
|
||||
}
|
||||
hexString.append(hex);
|
||||
}
|
||||
|
||||
return hexString.toString();
|
||||
} catch (Exception e) {
|
||||
log.error("哈希运算失败", e);
|
||||
throw new RuntimeException("哈希运算失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user