33333333333

This commit is contained in:
777
2025-03-31 17:04:14 +08:00
parent 0986494e20
commit 0b8a3055d1
12 changed files with 1055 additions and 1 deletions

View File

@@ -128,6 +128,7 @@ public enum TrdPayTypeEnum {
* 支付宝 8 微信 4
*/
V11("http://bgxa.peiqi.zhifusg.com","/Pay_Index.html","/Pay_Trade_query.html","/api/pay/trd/notify/v11","OK"),
V12("https://cashapi.sandpay.com.cn","/gateway/trade","/Pay_Trade_query.html","/api/pay/trd/notify/v11","OK"),
;
private final String gatewayUrl;

View File

@@ -0,0 +1,136 @@
package com.ruoyi.cai.trdpay.handle;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.cai.domain.PayTrdConfig;
import com.ruoyi.cai.pay.PayManager;
import com.ruoyi.cai.pay.PayOrderInfoDTO;
import com.ruoyi.cai.pay.PayReturnResp;
import com.ruoyi.cai.service.OrderLogsService;
import com.ruoyi.cai.trdpay.PayTrdService;
import com.ruoyi.cai.trdpay.TrdPayTypeEnum;
import com.ruoyi.cai.trdpay.dto.NotifyResp;
import com.ruoyi.cai.trdpay.handle.v12.SandPayClient;
import com.ruoyi.cai.trdpay.handle.v12.SandPayConfig;
import com.ruoyi.cai.trdpay.handle.v12.SandpayConfigUtil;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
@Slf4j
public class PayTrdV12Service implements PayTrdService {
@Autowired
private OrderLogsService orderLogsService;
@Autowired
private PayManager payManager;
private SandPayClient sandPayClient = null;
public synchronized void init(String accessMid){
if(sandPayClient == null){
// 初始化sandpay参数全局设置一次
SandPayConfig config = new SandPayConfig();
// 接入商户号
config.setAccessMid(accessMid);
// 加签 加密参数设置
config.setEncryptType(SandpayConfigUtil.AES_STR);
config.setPrivateKeyPath("D:\\mmm\\6888806128148.pfx");//存放目录src\main\resources\cert\prod
config.setPrivateKeyPassword("926645");
config.setSandPublicKeyPath("D:\\mmm\\sand_pro.cer");//存放目录src\main\resources\cert\prod
// 请求版本号
config.setVersion("4.0.0");
sandPayClient = new SandPayClient(config);
}
}
@Override
public TrdPayTypeEnum getType() {
return TrdPayTypeEnum.V12;
}
@Override
public PayReturnResp createOrderAli(PayOrderInfoDTO payOrderInfoDTO, PayTrdConfig payTrdConfig,boolean wx) {
init(payTrdConfig.getMchId());
TrdPayTypeEnum type = getType();
JSONObject bizData = new JSONObject();
bizData.put("mid","6888806128148");
bizData.put("outOrderNo",payOrderInfoDTO.getOrderNo());
bizData.put("description","知予-"+payOrderInfoDTO.getSubject());
bizData.put("goodsClass","99");
bizData.put("amount", payOrderInfoDTO.getPrice());
JSONArray payExtra = new JSONArray();
if(wx){
bizData.put("funcCodeList", new String[]{"02010005"});
/**
* subAppId : wxe78b5fdab7ddebbc
* ghOriId : gh_40fd947b7d3f
* pathUrl : pages/zf/index?
* miniProgramType : 0
*/
JSONObject exPayExtra = new JSONObject();
exPayExtra.put("funcCode","02010005");
exPayExtra.put("subAppId","wxe78b5fdab7ddebbc");
exPayExtra.put("ghOriId","gh_40fd947b7d3f");
exPayExtra.put("pathUrl","pages/zf/index?");
exPayExtra.put("miniProgramType","0");
payExtra.add(exPayExtra);
}else{
bizData.put("funcCodeList", new String[]{"02020004"});
}
String notifyUrl = type.getNotifyUrl(payTrdConfig.getNotifyUrl(), wx);
JSONObject payerInfo = new JSONObject();
payerInfo.put("frontUrl", notifyUrl);
payerInfo.put("payExtra",payExtra);
bizData.put("payerInfo",payerInfo);
bizData.put("notifyUrl", notifyUrl);
JSONObject reqReserved = new JSONObject();
reqReserved.put("sourceIp", ServletUtils.getClientIP());
bizData.put("sdCashierType", "SDK");
bizData.put("metaOption", "[{\"s\":\"Android\",\"n\":\"\",\"id\":\"\",\"sc\":\"\"},{\"s\":\"IOS\",\"n\":\"\",\"id\":\"\",\"sc\":\"\"}]");
bizData.put("reqReserved",reqReserved);
String gatewayUrl = getGatewayUrl(payTrdConfig);
String createOrderUrl = gatewayUrl + type.getCreateOrderUrl();
JSONObject responseJson = sandPayClient.execute(createOrderUrl, bizData);
if(responseJson == null || !"accept".equals(responseJson.getString("resultStatus"))){
orderLogsService.createAliPayLogs(payOrderInfoDTO.getOrderNo(), createOrderUrl+JSON.toJSONString(responseJson), com.alibaba.fastjson2.JSONObject.from(responseJson), false, type, getStepName(wx));
log.info("第三方支付失败 V12 统一支付失败失败 url={} params={} body={}, payTrdConfig={}", createOrderUrl, JSON.toJSONString(bizData), JSON.toJSONString(responseJson), JSON.toJSONString(payTrdConfig));
throw new ServiceException("调用支付失败");
}else{
orderLogsService.createAliPayLogs(payOrderInfoDTO.getOrderNo(), createOrderUrl+JSON.toJSONString(responseJson), com.alibaba.fastjson2.JSONObject.from(responseJson), true, type, getStepName(wx));
}
return PayReturnResp.createH5(responseJson.getString("cashierUrl"));
}
@Override
public NotifyResp getNotifyResp(Map<String, String> sourceData) {
String mchOrderNo = sourceData.get("orderid");
String payOrderId = sourceData.get("transaction_id");
String status = sourceData.get("returncode");
NotifyResp resp = new NotifyResp();
resp.setOrderNo(mchOrderNo);
resp.setTrdOrderNo(payOrderId);
resp.setPayTypeEnum(getType());
resp.setSourceData(sourceData);
resp.setSuccess("00".equals(status));
return resp;
}
@Override
public com.alibaba.fastjson2.JSONObject queryOrder(String orderNo, PayTrdConfig payTrdConfig) {
throw new RuntimeException("暂时不支持订单查询");
}
@Override
public com.alibaba.fastjson2.JSONObject resetOrder(String orderNo, PayTrdConfig payTrdConfig, boolean updateData) {
throw new RuntimeException("暂时不支持订单充值");
}
}

View File

@@ -68,7 +68,7 @@ public class PayTrdV7Service implements PayTrdService {
boolean success = checkSuccess(jsonObject);
orderLogsService.createAliPayLogs(payOrderInfoDTO.getOrderNo(), createOrderUrl+JSON.toJSONString(map), jsonObject, success, type, getStepName(wx));
if(!success){
log.info("第三方支付失败 V7 统一支付失败失败 url={} params={} body={}, payTrdConfig={}, typeEnum={}", createOrderUrl,JSON.toJSONString(map), body, JSON.toJSONString(payTrdConfig), JSON.toJSONString(jsonObject));
log.info("第三方支付失败 V7 统一支付失败失败 url={} params={} body={}, payTrdConfig={}", createOrderUrl,JSON.toJSONString(map), body, JSON.toJSONString(payTrdConfig));
throw new ServiceException("调用支付失败");
}
String payUrl = jsonObject.getJSONObject("data").getString("payUrl");

View File

@@ -0,0 +1,43 @@
package com.ruoyi.cai.trdpay.handle.v12;
import com.ruoyi.common.utils.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AESUtils {
private static final String KEY_ALGORITHM = "AES";
private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
public AESUtils() {
}
public static byte[] encrypt(byte[] plainBytes, byte[] keyBytes, String IV) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
if (StringUtils.isNotBlank(IV)) {
IvParameterSpec ips = new IvParameterSpec(IV.getBytes());
cipher.init(1, secretKey, ips);
} else {
cipher.init(1, secretKey);
}
return cipher.doFinal(plainBytes);
}
public static byte[] decrypt(byte[] encryptedBytes, byte[] keyBytes, String IV) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
if (StringUtils.isNotBlank(IV)) {
IvParameterSpec ips = new IvParameterSpec(IV.getBytes());
cipher.init(2, secretKey, ips);
} else {
cipher.init(2, secretKey);
}
return cipher.doFinal(encryptedBytes);
}
}

View File

@@ -0,0 +1,76 @@
package com.ruoyi.cai.trdpay.handle.v12;
import com.ruoyi.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class FileUtils {
public static final Logger logger = LoggerFactory.getLogger(FileUtils.class);
public FileUtils() {
}
public static InputStream loadFile(String fileName) {
if (StringUtils.isNotBlank(fileName)) {
return loadFromAbsoluteFile(fileName);
// return absolutePathStart(fileName) ? loadFromAbsoluteFile(fileName) : loadFromClasspathFile(fileName);
} else {
return null;
}
}
private static InputStream loadFromAbsoluteFile(String fileName) {
try {
File f = new File(fileName);
return !f.exists() ? null : new FileInputStream(f);
} catch (Throwable var3) {
logger.warn("load file[" + fileName + "] fail", var3);
return null;
}
}
private static boolean absolutePathStart(String path) {
File[] files = File.listRoots();
File[] var2 = files;
int var3 = files.length;
for(int var4 = 0; var4 < var3; ++var4) {
File file = var2[var4];
if (path.startsWith(file.getPath())) {
return true;
}
}
return false;
}
private static InputStream loadFromClasspathFile(String fileName) {
try {
// return ClassLoader.getSystemResourceAsStream(fileName);
return FileUtils.class.getClassLoader().getResourceAsStream(fileName);
} catch (Throwable var2) {
logger.warn("load file[" + fileName + "] fail", var2);
return null;
}
}
private static InputStream loadFromRelativeFile(String fileName) {
String userDir = System.getProperty("user.dir");
String realFilePath = addSeparator(userDir) + fileName;
return loadFromAbsoluteFile(realFilePath);
}
private static String addSeparator(String dir) {
if (!dir.endsWith(File.separator)) {
dir = dir + File.separator;
}
return dir;
}
}

View File

@@ -0,0 +1,65 @@
package com.ruoyi.cai.trdpay.handle.v12;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
public class HttpClientUtils {
public static final Logger logger = LoggerFactory.getLogger(HttpClientUtils.class);
public HttpClientUtils() {
}
public static String sendPost(String url, String param, int connectTimeout, int readTimeout) {
PrintWriter out = null;
BufferedReader in = null;
try {
URL realUrl = new URL(url);
URLConnection conn = realUrl.openConnection();
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setConnectTimeout(connectTimeout);
conn.setReadTimeout(readTimeout);
out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.flush();
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder sb = new StringBuilder();
String line = null;
while((line = in.readLine()) != null) {
sb.append(line);
}
String var10 = sb.toString();
return var10;
} catch (Exception var19) {
throw new SandPayException("请求服务器失败url:" + url + " err" + var19.getMessage(), var19);
} finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException var18) {
logger.error("发送POST方法的请求关闭流异常", var18);
}
}
}
}

View File

@@ -0,0 +1,174 @@
package com.ruoyi.cai.trdpay.handle.v12;
import com.ruoyi.common.utils.StringUtils;
import javax.crypto.Cipher;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Enumeration;
import java.util.Objects;
public class RSAUtils {
private static final String ALGORITHM = "RSA/ECB/PKCS1Padding";
private static final int KEY_LENGTH = 2048;
private static final int RESERVE_SIZE = 11;
public RSAUtils() {
}
public static String encryptToBase64Text(String plainTextBytes, PublicKey publicKey) throws Exception {
byte[] aesKeyBytes = plainTextBytes.getBytes();
byte[] encryptKeyBytes = encrypt(aesKeyBytes, publicKey);
return Base64.getEncoder().encodeToString(encryptKeyBytes);
}
public static String decryptBase64ToText(String encText, PrivateKey privateKey) throws Exception {
byte[] encTextBytes = Base64.getDecoder().decode(encText);
byte[] contentBytes = decrypt(encTextBytes, privateKey);
String decryptText = new String(contentBytes, StandardCharsets.UTF_8);
return decryptText;
}
public static byte[] encrypt(byte[] plainTextBytes, PublicKey publicKey) throws Exception {
boolean keyByteSize = true;
boolean encryptBlockSize = true;
int nBlock = plainTextBytes.length / 245;
if (plainTextBytes.length % 245 != 0) {
++nBlock;
}
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(1, publicKey);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(nBlock * 256);
for(int offset = 0; offset < plainTextBytes.length; offset += 245) {
int inputLen = plainTextBytes.length - offset;
if (inputLen > 245) {
inputLen = 245;
}
byte[] decryptedBlock = cipher.doFinal(plainTextBytes, offset, inputLen);
byteArrayOutputStream.write(decryptedBlock);
}
byteArrayOutputStream.flush();
byteArrayOutputStream.close();
return byteArrayOutputStream.toByteArray();
}
public static byte[] decrypt(byte[] cipherTextBytes, PrivateKey privateKey) throws Exception {
boolean keyByteSize = true;
boolean decryptBlockSize = true;
int nBlock = cipherTextBytes.length / 256;
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(2, privateKey);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(nBlock * 245);
for(int offset = 0; offset < cipherTextBytes.length; offset += 256) {
int inputLen = cipherTextBytes.length - offset;
if (inputLen > 256) {
inputLen = 256;
}
byte[] decryptedBlock = cipher.doFinal(cipherTextBytes, offset, inputLen);
byteArrayOutputStream.write(decryptedBlock);
}
byteArrayOutputStream.flush();
byteArrayOutputStream.close();
return byteArrayOutputStream.toByteArray();
}
public static String generateSign(String content, PrivateKey privateKey, String signType, String charset) throws Exception {
Signature signature = null;
if (!"RSA".equals(signType) && !"SHA256WithRSA".equals(signType)) {
throw new SandPayException("不是支持的签名类型 : signType=" + signType);
} else {
signature = Signature.getInstance("SHA256WithRSA");
signature.initSign(privateKey);
if (StringUtils.isBlank(charset)) {
signature.update(content.getBytes());
} else {
signature.update(content.getBytes(charset));
}
byte[] signed = signature.sign();
return Base64.getEncoder().encodeToString(signed);
}
}
/**
* @Description: 验签
* @param content 响应报文bizData
* @param sign 响应报文中的sign
* @param signType 响应报文中的signType
* @param publicKey 杉德公钥
* @param charset 编码格式
* @return: boolean
*/
public static boolean verifySign(String content, String sign, String signType, PublicKey publicKey, String charset) throws Exception {
Signature signature = Signature.getInstance(signType);
signature.initVerify(publicKey);
if (StringUtils.isBlank(charset)) {
signature.update(content.getBytes());
} else {
signature.update(content.getBytes(charset));
}
return signature.verify(Base64.getDecoder().decode(sign.getBytes()));
}
public static PublicKey getPublicKeyByStr(String publicKeyStr) throws Exception {
return loadPublicKey(new ByteArrayInputStream(publicKeyStr.getBytes()));
}
public static PrivateKey getPrivateKeyByStr(String priKeyStr) throws Exception {
return loadPrivateKey(new ByteArrayInputStream(priKeyStr.getBytes()), (String)null);
}
public static PrivateKey loadPrivateKey(InputStream ins, String password) throws Exception {
KeyStore ks = KeyStore.getInstance("PKCS12");
char[] nPassword;
if (password != null && !password.trim().equals("")) {
nPassword = password.toCharArray();
} else {
nPassword = null;
}
ks.load(ins, nPassword);
Enumeration<String> enumas = ks.aliases();
String keyAlias = null;
if (enumas.hasMoreElements()) {
keyAlias = (String)enumas.nextElement();
}
return (PrivateKey)ks.getKey(keyAlias, nPassword);
}
public static PublicKey getSandPublicKey(String sandPublicKeyPath) throws Exception {
InputStream publicIns = FileUtils.loadFile(sandPublicKeyPath);
if (Objects.isNull(publicIns)) {
throw new SandPayException("获取公钥失败");
} else {
return RSAUtils.loadPublicKey(publicIns);
}
}
public static PublicKey loadPublicKey(InputStream ins) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate oCert = (X509Certificate)cf.generateCertificate(ins);
PublicKey publicKey = oCert.getPublicKey();
return publicKey;
}
}

View File

@@ -0,0 +1,10 @@
package com.ruoyi.cai.trdpay.handle.v12;
import cn.hutool.core.util.RandomUtil;
public class RandomV12Util {
public static String genRandomString(int length) {
return RandomUtil.randomString("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",length);
}
}

View File

@@ -0,0 +1,305 @@
package com.ruoyi.cai.trdpay.handle.v12;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @ClassName : SandPayClient
* @Description : 加密、签名、验签
**/
@Slf4j
public class SandPayClient {
private SandpayConfigUtil sandpayConfigUtil = new SandpayConfigUtil();
public SandPayClient() {
}
public SandPayClient(SandPayConfig sandPayConfig) {
if (sandPayConfig != null) {
try {
this.sandpayConfigUtil.setBaseUrl(sandPayConfig.getBaseUrl());
this.sandpayConfigUtil.setAccessMid(sandPayConfig.getAccessMid());
this.sandpayConfigUtil.setMid(sandPayConfig.getMid());
this.sandpayConfigUtil.setPlMid(sandPayConfig.getPlMid());
this.sandpayConfigUtil.setVersion(sandPayConfig.getVersion());
// this.sandpayConfigUtil.setCertNo(sandPayConfig.getCertNo());//预留
String privateKeyPath = sandPayConfig.getPrivateKeyPath();
String sandPublicKeyPath = sandPayConfig.getSandPublicKeyPath();
if (!StringUtils.isBlank(privateKeyPath) || !StringUtils.isBlank(sandPublicKeyPath)) {
this.sandpayConfigUtil.setPrivateKey(this.sandpayConfigUtil.getPrivateKey(sandPayConfig.getPrivateKeyPath(), sandPayConfig.getPrivateKeyPassword()));
this.sandpayConfigUtil.setSandPublicKey(this.sandpayConfigUtil.getSandPublicKey(sandPayConfig.getSandPublicKeyPath()));
this.sandpayConfigUtil.setEncryptType(sandPayConfig.getEncryptType());
this.sandpayConfigUtil.setSignType(sandPayConfig.getSignType());
Integer connectTimeout = sandPayConfig.getConnectTimeout();
if (Objects.nonNull(connectTimeout)) {
this.sandpayConfigUtil.setConnectTimeout(connectTimeout);
}
Integer readTimeout = sandPayConfig.getReadTimeout();
if (Objects.nonNull(readTimeout)) {
this.sandpayConfigUtil.setReadTimeout(readTimeout);
}
}
} catch (Exception var6) {
log.error(var6.getMessage());
throw new SandPayException("初始化SandPayClient异常", var6);
}
}
}
public JSONObject execute(String url, JSONObject bizData) {
return (JSONObject) this.requestCoreReturnObject(url, bizData, JSONObject.class);
}
public JSONObject execute(String url, JSONObject bizData, String encryptType) {
return (JSONObject) this.requestCoreReturnObject(url, bizData, JSONObject.class, encryptType);
}
private <T> T requestCoreReturnObject(String url, Object bizRequest, Class<T> returnClass, String encryptType) {
String bizRequestJson = JSON.toJSONString(bizRequest);
JSONObject sandPayCommonRequest = this.buildRequest(bizRequestJson, encryptType);
log.info("请求Sand报文{}", sandPayCommonRequest);
String responseStr = HttpClientUtils.sendPost(url, JSON.toJSONString(sandPayCommonRequest), this.sandpayConfigUtil.getConnectTimeout(), this.sandpayConfigUtil.getReadTimeout());
log.info("Sand返回报文{}", responseStr);
return this.handleResponse(responseStr, returnClass, encryptType);
}
private <T> T requestCoreReturnObject(String url, Object bizRequest, Class<T> returnClass) throws SandPayException {
String encryptType = this.sandpayConfigUtil.getEncryptType();
return this.requestCoreReturnObject(url, bizRequest, returnClass, encryptType);
}
/**
* @Description: 加密、签名接口请求报文并组装公共请求报文
* @param content 接口请求报文明文
* @param encryptType 加密方式
* @return: JSONObject 公共请求报文
*/
public JSONObject buildRequest(String content, String encryptType) throws SandPayException {
String signType = "RSA";
String encryptKey;
String bizData;
String sign;
if (StringUtils.isBlank(content)) {
throw new SandPayException("签名内容为空 : content=" + content);
} else if (!"AES".equals(encryptType)) {
throw new SandPayException("不是支持的加密类型 : encryptType=" + encryptType);
} else {
//1.生成一个16位的随机字符串aesKey该字符串仅包含大小写字母及数字。
String aesKey = RandomV12Util.genRandomString(16);
//2.将随机字符串转为byte数组aesKeyBytes编码格式为UTF_8。
//3.将请求报文中明文bizData域转为byte数组编码格式为UTF_8并用aesKeyBytes用AES算法AES/ECB/PKCS5Padding对其加密并对结果进行base64转码得到加密报文体bizData。
bizData = this.aesEncrypt(content, aesKey);
//4.将随机字符串byte数组aesKeyBytes使用杉德公钥进行RSA算法RSA/ECB/PKCS1Padding加密并将结果进行base64转码即得到encryptKey。
encryptKey = rsaEncrypt(aesKey, this.sandpayConfigUtil.getSandPublicKey());
//5.将加密报文体使用商户私钥进行RSA算法SHA256WithRSA签名得到sign。
sign = this.generateSign(bizData, signType, this.sandpayConfigUtil.getPrivateKey());
}
//6.组装公共请求报文并返回
JSONObject commonRequest = new JSONObject();
commonRequest.put("accessMid", this.sandpayConfigUtil.getAccessMid());
commonRequest.put("encryptType", encryptType);
commonRequest.put("encryptKey", encryptKey);
// commonRequest.put("certNo", this.sandpayConfigUtil.getCertNo());//预留
commonRequest.put("version", this.sandpayConfigUtil.getVersion());
commonRequest.put("timestamp", this.getTimestamp());
commonRequest.put("signType", signType);
commonRequest.put("sign", sign);
commonRequest.put("bizData", bizData);
return commonRequest;
}
//解析响应报文
public JSONObject parseResponse(String content, String encryptType) throws SandPayException {
return (JSONObject) this.handleResponse(content, JSONObject.class, encryptType);
}
//验签回调
public boolean verifySign(String data, String sign, String signType, String charset) throws SandPayException {
try {
return RSAUtils.verifySign(data, sign, signType, this.sandpayConfigUtil.getSandPublicKey(), charset);
} catch (Exception var6) {
throw new SandPayException("验证回调签名异常", var6);
}
}
//处理响应报文
private <T> T handleResponse(String responseStr, Class<T> returnClass, String encryptType) throws SandPayException {
JSONObject commonResponse = (JSONObject) JSON.parseObject(responseStr, JSONObject.class);
String respCode = (String) commonResponse.get("respCode");
if (respCode == null) {
throw new SandPayException("处理返回报文失败,原始报文" + responseStr);
} else {
String bizData;
if (!"success".equals(respCode)) {
bizData = (String) commonResponse.get("respDesc");
throw new SandPayException(respCode, bizData, responseStr);
} else {
bizData = commonResponse.get("bizData").toString();
String sign = (String) commonResponse.get("sign");
String signType = (String) commonResponse.get("signType");
this.verifySign(bizData, sign, signType, this.sandpayConfigUtil.getSandPublicKey());
if (!"AES".equals(encryptType)) {
throw new SandPayException("不是支持的加密类型: encryptType=" + encryptType);
} else {
String encryptKey = (String) commonResponse.get("encryptKey");
String respAesKey = this.rsaDecrypt(encryptKey, this.sandpayConfigUtil.getPrivateKey());
bizData = this.aesDecrypt(bizData, respAesKey);
if (returnClass == String.class) {
return (T) bizData;
} else {
try {
return JSON.parseObject(bizData, returnClass);
} catch (Exception var11) {
throw new SandPayException("解析报文失败," + responseStr, var11);
}
}
}
}
}
}
//AES加密
private String aesEncrypt(String content, String aesKey) {
byte[] aesKeyBytes = aesKey.getBytes(StandardCharsets.UTF_8);
try {
byte[] encryptValueBytes = AESUtils.encrypt(content.getBytes(StandardCharsets.UTF_8), aesKeyBytes, (String) null);
return Base64.getEncoder().encodeToString(encryptValueBytes);
} catch (Exception var5) {
throw new SandPayException("AES加密异常", var5);
}
}
//RSA加密
public static String rsaEncrypt(String content, PublicKey publicKey) {
try {
byte[] aesKeyBytes = content.getBytes();
byte[] encryptKeyBytes = RSAUtils.encrypt(aesKeyBytes, publicKey);
return Base64.getEncoder().encodeToString(encryptKeyBytes);
} catch (Exception var4) {
throw new SandPayException("RSA加密异常", var4);
}
}
//RSA解密响应参数
private String rsaDecrypt(String content, PrivateKey privateKey) {
byte[] decryptKeyBytes = Base64.getDecoder().decode(content);
try {
byte[] contentBytes = RSAUtils.decrypt(decryptKeyBytes, privateKey);
String decryptKey = new String(contentBytes, StandardCharsets.UTF_8);
return decryptKey;
} catch (Exception var6) {
throw new SandPayException("RSA解密随机加密串失败", var6);
}
}
//AES解密响应参数
private String aesDecrypt(String content, String aesKey) {
byte[] decryptDataBase64 = Base64.getDecoder().decode(content);
try {
byte[] decryptDataBytes = AESUtils.decrypt(decryptDataBase64, aesKey.getBytes(StandardCharsets.UTF_8), (String) null);
String decryptData = new String(decryptDataBytes);
return decryptData;
} catch (Exception var6) {
throw new SandPayException("AES解密返回参数失败", var6);
}
}
//签名
public String generateSign(String content, String signType, PrivateKey privateKey) {
try {
if ("RSA".equals(signType)) {
signType = "SHA256WithRSA";
}
return RSAUtils.generateSign(content, privateKey, signType, "UTF-8");
} catch (Exception var5) {
throw new SandPayException("请求签名异常", var5);
}
}
/**
* @Description: 验签响应报文
* @param content 响应报文中的bizData
* @param sign 响应报文中的sign
* @param signType 响应报文中的signType
* @param publicKey 杉德公钥
* @return: boolean 验签结果
*/
private boolean verifySign(String content, String sign, String signType, PublicKey publicKey) {
try {
if ("RSA".equals(signType)) {
signType = "SHA256WithRSA";
}
return RSAUtils.verifySign(content, sign, signType, publicKey, "UTF-8");
} catch (Exception var6) {
throw new SandPayException("验证响应报文签名异常", var6);
}
}
//生成时间戳 yyyy-MM-dd HH:mm:ss
private String getTimestamp() {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
df.setTimeZone(TimeZone.getTimeZone("GMT+8"));
return df.format(new Date(System.currentTimeMillis()));
}
//请求时间 yyyyMMddHHmmss
public String getCurrentTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));
return sdf.format(new Date());
}
//获取当前时间*小时后的时间 yyyyMMddHHmmss
public String getTimeOutTime(int hour) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR, hour);
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));
return sdf.format(calendar.getTime());
}
//生成订单号
public String getOutOrderNo() {
return this.getOutOrderNo("", 25);
}
public String getOutOrderNo(String prefix, int length) {
if (prefix == null) {
prefix = "";
}
if (length < 25) {
length = 25;
}
DateFormat df = new SimpleDateFormat("yyyyMMddHHmmsss");
df.setTimeZone(TimeZone.getTimeZone("GMT+8"));
String currentTime = df.format(new Date(System.currentTimeMillis()));
String outOrderNo = prefix.concat(currentTime);
int randomLen = length - outOrderNo.length();
outOrderNo = prefix.concat(currentTime).concat(RandomV12Util.genRandomString(randomLen));
return outOrderNo;
}
public SandpayConfigUtil getSandpayConfigUtil() {
return this.sandpayConfigUtil;
}
}

View File

@@ -0,0 +1,27 @@
package com.ruoyi.cai.trdpay.handle.v12;
import lombok.Data;
import java.security.PrivateKey;
import java.security.PublicKey;
@Data
public class SandPayConfig {
private String baseUrl;
private String version = "4.0.0";
private String accessMid;
private String plMid;
private String mid;
private PrivateKey privateKey;
private String privateKeyPassword;
private String privateKeyPath;
private String certNo;
private PublicKey sandPublicKey;
private String sandPublicKeyPath;
private String signType = "RSA";
private String encryptType = "AES";
private int connectTimeout = 5000;
private int readTimeout = 5000;
}

View File

@@ -0,0 +1,59 @@
package com.ruoyi.cai.trdpay.handle.v12;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class SandPayException extends RuntimeException {
private String respCode;
private String respDesc;
private String responseMsg;
public SandPayException() {
}
public SandPayException(String message) {
super(message);
this.respDesc = message;
}
public SandPayException(String message, Throwable cause) {
super(message, cause);
this.respDesc = message;
}
public SandPayException(String respCode, String respDesc, String responseMsg) {
super(respCode + ":" + respDesc);
this.respCode = respCode;
this.respDesc = respDesc;
this.responseMsg = responseMsg;
}
public String toString() {
return this.toJSON().toString();
}
public JSONObject toJSON() {
JSONObject sandResp = new JSONObject();
sandResp.put("respCode", this.getRespCode());
sandResp.put("respDesc", this.getRespDesc());
if (this.getResponseMsg() != null && this.getResponseMsg().length() > 0) {
JSONObject responseMsg = (JSONObject) JSON.parseObject(this.getResponseMsg(), JSONObject.class);
sandResp.put("responseMsg", responseMsg);
}
return sandResp;
}
public String getRespCode() {
return this.respCode;
}
public String getRespDesc() {
return this.respDesc;
}
public String getResponseMsg() {
return this.responseMsg;
}
}

View File

@@ -0,0 +1,158 @@
package com.ruoyi.cai.trdpay.handle.v12;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Objects;
public class SandpayConfigUtil {
public static final Logger logger = LoggerFactory.getLogger(SandpayConfigUtil.class);
private String baseUrl;
private String version;
private String accessMid;
private String plMid;
private String mid;
private PrivateKey privateKey;
private String certNo;
private PublicKey sandPublicKey;
private String signType = "SHA256WithRSA";
private String encryptType = "AES";
private int connectTimeout = 10000;
private int readTimeout = 10000;
public static final String CHARSET = "UTF-8";
public static final String AES_STR = "AES";
public static final String RSA_STR = "RSA";
public static final String SHA_256_WITH_RSA = "SHA256WithRSA";
public static final String SUCCESS_STR = "success";
public static final String RESP_CODE_STR = "respCode";
public static final String RESP_DESC_STR = "respDesc";
public static final String BIZ_DATA_STR = "bizData";
public static final String SIGN_STR = "sign";
public static final String SIGN_TYPE_STR = "signType";
public static final String ENCRYPT_TYPE_STR = "encryptType";
public static final String ENCRYPT_KEY_STR = "encryptKey";
public SandpayConfigUtil() {
}
public PrivateKey getPrivateKey(String privateKeyPath, String privateKeyPassword) throws Exception {
logger.debug("商户私钥路径 => "+privateKeyPath);
logger.debug("商户私钥密码 => "+privateKeyPassword);
InputStream privateIns = FileUtils.loadFile(privateKeyPath);
if (Objects.isNull(privateIns)) {
throw new SandPayException("获取私钥失败");
} else {
return RSAUtils.loadPrivateKey(privateIns, privateKeyPassword);
}
}
public PublicKey getSandPublicKey(String sandPublicKeyPath) throws Exception {
logger.debug("杉德公钥路径 => "+ sandPublicKeyPath);
InputStream publicIns = FileUtils.loadFile(sandPublicKeyPath);
if (Objects.isNull(publicIns)) {
throw new SandPayException("获取公钥失败");
} else {
return RSAUtils.loadPublicKey(publicIns);
}
}
public String getBaseUrl() {
return this.baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version = version;
}
public String getAccessMid() {
return this.accessMid;
}
public void setAccessMid(String accessMid) {
this.accessMid = accessMid;
}
public String getPlMid() {
return this.plMid;
}
public void setPlMid(String plMid) {
this.plMid = plMid;
}
public String getMid() {
return this.mid;
}
public void setMid(String mid) {
this.mid = mid;
}
public PrivateKey getPrivateKey() {
return this.privateKey;
}
public void setPrivateKey(PrivateKey privateKey) {
this.privateKey = privateKey;
}
public String getCertNo() {
return this.certNo;
}
public void setCertNo(String certNo) {
this.certNo = certNo;
}
public PublicKey getSandPublicKey() {
return this.sandPublicKey;
}
public void setSandPublicKey(PublicKey sandPublicKey) {
this.sandPublicKey = sandPublicKey;
}
public String getSignType() {
return this.signType;
}
public void setSignType(String signType) {
this.signType = signType;
}
public String getEncryptType() {
return this.encryptType;
}
public void setEncryptType(String encryptType) {
this.encryptType = encryptType;
}
public int getConnectTimeout() {
return this.connectTimeout;
}
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
public int getReadTimeout() {
return this.readTimeout;
}
public void setReadTimeout(int readTimeout) {
this.readTimeout = readTimeout;
}
}