package com.ruoyi.order.util.payment.wx;
|
|
|
import com.alibaba.fastjson2.JSON;
|
import com.ruoyi.common.core.domain.R;
|
import com.ruoyi.order.util.payment.MD5AndKL;
|
import com.ruoyi.order.util.payment.wx.vo.PayResult;
|
import com.ruoyi.order.util.payment.wx.vo.RefundCallbackResult;
|
import org.apache.commons.codec.digest.DigestUtils;
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
import org.bouncycastle.crypto.engines.AESEngine;
|
import org.bouncycastle.crypto.paddings.PKCS7Padding;
|
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
import org.bouncycastle.crypto.params.KeyParameter;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.openssl.PEMParser;
|
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
import org.dom4j.DocumentException;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
import org.springframework.util.StringUtils;
|
import sun.misc.BASE64Decoder;
|
import sun.security.util.DerInputStream;
|
import sun.security.util.DerValue;
|
|
import javax.crypto.BadPaddingException;
|
import javax.crypto.Cipher;
|
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.SecretKeySpec;
|
import javax.net.ssl.HttpsURLConnection;
|
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.SSLContext;
|
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletResponse;
|
import java.io.*;
|
import java.math.BigDecimal;
|
import java.math.BigInteger;
|
import java.net.InetAddress;
|
import java.net.URL;
|
import java.net.URLEncoder;
|
import java.net.UnknownHostException;
|
import java.nio.charset.StandardCharsets;
|
import java.nio.file.Files;
|
import java.nio.file.Path;
|
import java.nio.file.Paths;
|
import java.security.*;
|
import java.security.spec.RSAPublicKeySpec;
|
import java.security.spec.X509EncodedKeySpec;
|
import java.util.*;
|
import java.util.stream.Collectors;
|
|
/**
|
* 微信支付服务类
|
*/
|
@Service
|
public class WechatPayService {
|
|
@Autowired
|
private WechatPayConfig wechatPayConfig;
|
private static final String RSA_PUBLIC_KEY_FILENAME = "wechat_rsa_public_key.pem";
|
private static final String CERT_FOLDER = "cert/";
|
/**
|
* 统一下单
|
* @param orderNumber 订单号
|
* @param totalFee 总金额(分)
|
* @param body 商品描述
|
* @param openid 用户openid
|
* @return 预支付订单信息
|
*/
|
public R unifiedOrder(String orderId,String orderNumber, String totalFee, String body, String openid, String callbackPath) throws Exception {
|
int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue();
|
String hostAddress = null;
|
try {
|
hostAddress = InetAddress.getLocalHost().getHostAddress();
|
} catch (UnknownHostException e) {
|
e.printStackTrace();
|
}
|
// 构建请求参数
|
Map<String, String> params = new HashMap<>();
|
params.put("appid", wechatPayConfig.getAppId());
|
params.put("mch_id", wechatPayConfig.getMchId());
|
params.put("nonce_str", generateNonceStr());
|
params.put("body", body);
|
params.put("out_trade_no", orderNumber);
|
params.put("total_fee", String.valueOf(i) );
|
params.put("spbill_create_ip", "221.182.45.100"); // 实际应用中应获取客户端IP
|
params.put("notify_url", wechatPayConfig.getCallbackPath()+callbackPath);
|
params.put("trade_type", "JSAPI");
|
params.put("openid", openid);
|
|
// 生成签名
|
String sign = weixinSignature(params);
|
params.put("sign", sign);
|
|
// 将参数转换为XML
|
String xmlParams = XMLUtil.mapToXml(params).replaceFirst("^<\\?xml.+?\\?>\\s*", "");
|
|
// 发送请求到微信支付统一下单接口
|
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
|
String result = HttpUtil.post(url, xmlParams);
|
|
// 解析返回结果
|
Map<String, String> resultMap = XMLUtil.xmlToMap(result);
|
|
// 验证签名
|
if (!verifySign(resultMap, wechatPayConfig.getKey())) {
|
return R.fail("微信支付签名验证失败");
|
// throw new Exception("微信支付签名验证失败");
|
}
|
if (!resultMap.get("return_code").equals("SUCCESS")) {
|
return R.fail(resultMap.get("return_msg"));
|
}
|
|
// 构建小程序支付所需参数
|
Map<String, String> payParams = new HashMap<>();
|
payParams.put("appId", wechatPayConfig.getAppId());
|
payParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
|
payParams.put("nonceStr", generateNonceStr());
|
payParams.put("package", "prepay_id=" + resultMap.get("prepay_id"));
|
payParams.put("signType", "MD5");
|
|
// 生成支付签名
|
String paySign = weixinSignature(payParams);
|
payParams.put("paySign", paySign);
|
|
//给前端标识
|
payParams.put("payMethod","1");
|
payParams.put("orderId", orderId);
|
return R.ok(JSON.toJSONString(payParams));
|
}
|
/**
|
* 微信下单的签名算法
|
* @param map
|
* @return
|
*/
|
private String weixinSignature(Map<String, String> map){
|
try {
|
Set<Map.Entry<String, String>> entries = map.entrySet();
|
List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(entries);
|
// 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
|
Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {
|
public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
|
return (o1.getKey()).toString().compareTo(o2.getKey());
|
}
|
});
|
// 构造签名键值对的格式
|
StringBuilder sb = new StringBuilder();
|
for (Map.Entry<String, String> item : infoIds) {
|
if (item.getKey() != null || item.getKey() != "") {
|
String key = item.getKey();
|
Object val = item.getValue();
|
if (!(val == "" || val == null)) {
|
sb.append(key + "=" + val + "&");
|
}
|
}
|
}
|
sb.append("key=" + wechatPayConfig.getKey());
|
String sign = MD5AndKL.MD5Encode(sb.toString(), "UTF-8").toUpperCase(); //注:MD5签名方式
|
return sign;
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
return null;
|
}
|
|
/**
|
* 处理支付结果通知
|
* @param request HTTP请求
|
* @return 处理结果
|
*/
|
public PayResult processNotify(HttpServletRequest request) throws Exception {
|
// 读取请求内容
|
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8));
|
String line;
|
StringBuilder xml = new StringBuilder();
|
while ((line = br.readLine()) != null) {
|
xml.append(line);
|
}
|
br.close();
|
|
// 解析XML
|
Map<String, String> resultMap = XMLUtil.xmlToMap(xml.toString());
|
|
// 验证签名
|
if (!verifySign(resultMap, wechatPayConfig.getKey())) {
|
throw new Exception("微信支付签名验证失败");
|
}
|
|
// 验证支付结果
|
if (!"SUCCESS".equals(resultMap.get("return_code")) || !"SUCCESS".equals(resultMap.get("result_code"))) {
|
throw new Exception("微信支付结果异常");
|
}
|
|
// 处理业务逻辑
|
PayResult payResult = new PayResult();
|
payResult.setOrderNumber(resultMap.get("out_trade_no"));
|
payResult.setTransactionId(resultMap.get("transaction_id"));
|
payResult.setTotalFee(resultMap.get("total_fee"));
|
|
return payResult;
|
}
|
|
/**
|
* 生成随机字符串
|
*/
|
private String generateNonceStr() {
|
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
|
}
|
|
/**
|
* 微信支付API V2签名算法
|
* @param params 请求参数
|
* @param apiKey 商户API密钥
|
* @return 签名结果
|
*/
|
public static String generateSign(Map<String, String> params, String apiKey) throws Exception {
|
// 1. 过滤空值参数
|
Map<String, String> filteredParams = new HashMap<>();
|
for (Map.Entry<String, String> entry : params.entrySet()) {
|
String key = entry.getKey();
|
String value = entry.getValue();
|
// 排除sign字段和空值字段
|
if (!"sign".equals(key) && value != null && !value.isEmpty()) {
|
filteredParams.put(key, value);
|
}
|
}
|
|
// 2. 按照ASCII码排序参数名
|
List<String> keys = new ArrayList<>(filteredParams.keySet());
|
Collections.sort(keys);
|
|
// 3. 构建签名原始字符串
|
StringBuilder sb = new StringBuilder();
|
for (String key : keys) {
|
String value = filteredParams.get(key);
|
sb.append(key).append("=").append(value).append("&");
|
}
|
|
// 4. 添加API密钥
|
sb.append("key=").append(apiKey);
|
|
// 5. MD5加密并转为大写
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
|
|
// 6. 转换为十六进制字符串
|
StringBuilder sign = new StringBuilder();
|
for (byte b : digest) {
|
sign.append(String.format("%02x", b & 0xff));
|
}
|
|
return sign.toString().toUpperCase();
|
}
|
/**
|
* 验证签名
|
*/
|
private boolean verifySign(Map<String, String> params, String key) throws Exception {
|
String sign = params.get("sign");
|
if (StringUtils.isEmpty(sign)) {
|
return false;
|
}
|
|
// 移除sign字段
|
Map<String, String> newParams = new HashMap<>(params);
|
newParams.remove("sign");
|
|
// 生成新签名
|
String newSign = generateSign(newParams, key);
|
|
return sign.equals(newSign);
|
}
|
|
/**
|
* 关闭订单
|
*/
|
public Map<String, String> closeOrder(String orderId){
|
// 构建请求参数
|
Map<String, String> params = new HashMap<>();
|
params.put("appid", wechatPayConfig.getAppId());
|
params.put("mch_id", wechatPayConfig.getMchId());
|
params.put("nonce_str", generateNonceStr());
|
params.put("out_trade_no", orderId);
|
|
// 生成签名
|
String sign = weixinSignature(params);
|
params.put("sign", sign);
|
|
// 将参数转换为XML
|
String xmlParams = XMLUtil.mapToXml(params);
|
|
// 发送请求到微信支付关闭订单接口
|
String url = "https://api.mch.weixin.qq.com/pay/closeorder";
|
String result = null;
|
try {
|
result = HttpUtil.post(url, xmlParams);
|
} catch (Exception e) {
|
throw new RuntimeException(e);
|
}
|
|
// 解析返回结果
|
try {
|
return XMLUtil.xmlToMap(result);
|
} catch (DocumentException e) {
|
throw new RuntimeException(e);
|
}
|
}
|
|
/**
|
* 查询订单
|
*/
|
public Map<String, String> queryOrder(String orderId) throws Exception {
|
// 构建请求参数
|
Map<String, String> params = new HashMap<>();
|
params.put("appid", wechatPayConfig.getAppId());
|
params.put("mch_id", wechatPayConfig.getMchId());
|
params.put("nonce_str", generateNonceStr());
|
params.put("out_trade_no", orderId);
|
|
// 生成签名
|
String sign = generateSign(params, wechatPayConfig.getKey());
|
params.put("sign", sign);
|
|
// 将参数转换为XML
|
String xmlParams = XMLUtil.mapToXml(params);
|
|
// 发送请求到微信支付查询订单接口
|
String url = "https://api.mch.weixin.qq.com/pay/orderquery";
|
String result = HttpUtil.post(url, xmlParams);
|
|
// 解析返回结果
|
return XMLUtil.xmlToMap(result);
|
}
|
|
|
/**
|
* 申请退款 - 使用证书
|
*/
|
public Map<String, String> refund(String orderNo, String refundNo, String totalFee, String refundFee, String refundDesc,String callbackPath) {
|
int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue();
|
int j = new BigDecimal(refundFee).multiply(new BigDecimal("100")).intValue();
|
try {
|
// 构建请求参数
|
Map<String, String> params = new HashMap<>();
|
params.put("appid", wechatPayConfig.getAppId());
|
params.put("mch_id", wechatPayConfig.getMchId());
|
params.put("nonce_str", UUID.randomUUID().toString().replaceAll("-", ""));
|
params.put("out_trade_no", orderNo);
|
params.put("out_refund_no", refundNo);
|
params.put("total_fee", String.valueOf(i));
|
params.put("refund_fee", String.valueOf(j));
|
params.put("refund_desc", refundDesc);
|
params.put("notify_url", wechatPayConfig.getCallbackPath() + callbackPath); // 退款结果
|
|
// 生成签名
|
String sign = weixinSignature(params);
|
params.put("sign", sign);
|
|
// 转换为XML
|
String xmlParams = XMLUtil.mapToXml(params);
|
|
// 使用证书发送请求
|
String result = postWithCert("https://api.mch.weixin.qq.com/secapi/pay/refund", xmlParams);
|
|
// 解析结果
|
Map<String, String> resultMap = XMLUtil.xmlToMap(result);
|
System.out.println("申请退款结果"+resultMap);
|
|
// 验证签名
|
if (!verifySign(resultMap, wechatPayConfig.getKey())) {
|
resultMap.put("return_code","FAILED");
|
resultMap.put("return_msg","申请退款结果签名验证失败");
|
return resultMap;
|
}
|
|
return resultMap;
|
} catch (Exception e) {
|
Map<String, String> resultMap=new HashMap<>();
|
resultMap.put("return_code","FAILED");
|
resultMap.put("return_msg","申请退款失败");
|
return resultMap;
|
}
|
}
|
|
/**
|
* 使用证书发送请求
|
*/
|
private String postWithCert(String url, String xmlData) throws Exception {
|
// 证书类型为PKCS12
|
KeyStore keyStore = KeyStore.getInstance("PKCS12");
|
// 获取证书路径
|
String certPath = wechatPayConfig.getCertPath();
|
|
// 如果是classpath路径,使用ClassLoader加载
|
if (certPath.startsWith("classpath:")) {
|
String path = certPath.substring("classpath:".length());
|
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path)) {
|
if (inputStream == null) {
|
throw new FileNotFoundException("证书文件不存在: " + path);
|
}
|
keyStore.load(inputStream, wechatPayConfig.getMchId().toCharArray());
|
}
|
} else {
|
// 传统文件路径
|
try (FileInputStream inputStream = new FileInputStream(new File(certPath))) {
|
keyStore.load(inputStream, wechatPayConfig.getMchId().toCharArray());
|
}
|
}
|
|
// 实例化密钥库 & 初始化密钥工厂
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
kmf.init(keyStore, wechatPayConfig.getMchId().toCharArray());
|
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
|
|
// 创建HttpsURLConnection对象
|
URL httpsUrl = new URL(url);
|
HttpsURLConnection conn = (HttpsURLConnection) httpsUrl.openConnection();
|
conn.setSSLSocketFactory(sslContext.getSocketFactory());
|
conn.setDoOutput(true);
|
conn.setDoInput(true);
|
conn.setUseCaches(false);
|
conn.setRequestMethod("POST");
|
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
|
// 发送请求
|
OutputStream outputStream = conn.getOutputStream();
|
outputStream.write(xmlData.getBytes("UTF-8"));
|
outputStream.flush();
|
outputStream.close();
|
|
// 获取响应
|
InputStream inputStream = conn.getInputStream();
|
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
|
StringBuilder result = new StringBuilder();
|
String line;
|
while ((line = bufferedReader.readLine()) != null) {
|
result.append(line);
|
}
|
bufferedReader.close();
|
inputStream.close();
|
conn.disconnect();
|
|
return result.toString();
|
}
|
|
/**
|
* 处理退款回调
|
*/
|
public RefundCallbackResult processRefundCallback(String xmlData) {
|
try {
|
// 1. 解析回调XML数据
|
if (StringUtils.isEmpty(xmlData)) {
|
return RefundCallbackResult.fail("回调数据为空");
|
}
|
|
//2.解析参数
|
System.out.println(xmlData);
|
System.out.println("----------------------------------------");
|
Map<String, String> resultMap = XMLUtil.xmlToMap(xmlData);
|
System.out.println(resultMap.get("req_info"));
|
// 3. 检查返回状态
|
String returnCode = resultMap.get("return_code");
|
|
if (!"SUCCESS".equals(returnCode)) {
|
String errMsg = resultMap.get("return_msg");
|
return RefundCallbackResult.fail("通信失败:" + errMsg);
|
}
|
|
//4 使用商户API密钥解密req_info(AES-256-CBC算法)
|
String decryptData = decrypt(resultMap.get("req_info"), wechatPayConfig.getKey());
|
Map<String, String> refundDetail = XMLUtil.xmlToMap(decryptData);
|
|
|
// 4. 提取退款信息
|
String orderNo = refundDetail.get("out_trade_no"); // 原订单号
|
String refundNo = refundDetail.get("out_refund_no"); // 退款订单号
|
String refundId = refundDetail.get("refund_id"); // 微信退款ID
|
System.err.println("退款回调成功,订单号:"+orderNo+",退款号:"+refundNo+",状态:{}"+refundId);
|
RefundCallbackResult refundCallbackResult = RefundCallbackResult.success();
|
refundCallbackResult.setOrderNo(orderNo);
|
refundCallbackResult.setRefundNo(refundId);
|
|
return refundCallbackResult;
|
|
} catch (Exception e) {
|
return RefundCallbackResult.fail("系统异常:" + e.getMessage());
|
}
|
}
|
|
private static String wxDecrypt(String req_info, String key) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException,
|
InvalidKeyException, BadPaddingException, IllegalBlockSizeException{
|
byte[] decode = Base64.getDecoder().decode(req_info);
|
System.out.println(Arrays.toString(decode));
|
String sign = MD5AndKL.MD5Encode(key, "UTF-8").toLowerCase();
|
System.out.println(sign);
|
if (Security.getProvider("BC") == null){
|
Security.addProvider(new BouncyCastleProvider());
|
}
|
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
|
SecretKeySpec secretKeySpec = new SecretKeySpec(sign.getBytes(), "AES");
|
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
|
return new String(cipher.doFinal(decode));
|
}
|
|
/**
|
* 获取RSA加密公钥
|
*/
|
public String getRsaPublicKey() {
|
int maxRetries = 3;
|
for (int retryCount = 0; retryCount < maxRetries; retryCount++) {
|
try {
|
System.out.println("尝试获取RSA公钥,第" + (retryCount + 1) + "次");
|
|
// 构建请求参数
|
Map<String, String> params = new HashMap<>();
|
params.put("mch_id", wechatPayConfig.getMchId());
|
params.put("sign_type", "MD5");
|
params.put("nonce_str", generateNonceStr());
|
|
// 生成签名
|
String sign = weixinSignature(params);
|
params.put("sign", sign);
|
|
// 转换为XML
|
String xmlParams = XMLUtil.mapToXml(params);
|
|
// 打印请求参数
|
System.out.println("请求参数: " + xmlParams);
|
|
// 使用证书发送请求(关键修改:使用postWithCert而非普通HTTP请求)
|
String result = postWithCert("https://fraud.mch.weixin.qq.com/risk/getpublickey", xmlParams);
|
|
// 打印响应结果
|
System.out.println("响应结果: " + result);
|
|
// 解析结果
|
Map<String, String> resultMap = XMLUtil.xmlToMap(result);
|
System.out.println("获取RSA公钥结果: " + resultMap);
|
|
// 检查返回状态
|
if (!"SUCCESS".equals(resultMap.get("return_code"))) {
|
throw new Exception("RSA公钥获取失败: " + resultMap.get("return_msg"));
|
}
|
|
// 保存公钥到本地文件
|
savePublicKeyToClasspath(resultMap.get("pub_key"));
|
|
return resultMap.get("pub_key");
|
} catch (Exception e) {
|
System.err.println("获取RSA公钥异常: " + e.getMessage() + ", 重试次数: " + (retryCount + 1));
|
e.printStackTrace();
|
|
// 如果是最后一次重试,抛出异常
|
if (retryCount == maxRetries - 1) {
|
return null;
|
}
|
|
// 重试前等待一段时间
|
try {
|
Thread.sleep(1000 * (retryCount + 1)); // 指数退避
|
} catch (InterruptedException ie) {
|
Thread.currentThread().interrupt();
|
return null;
|
}
|
}
|
}
|
|
return null;
|
}
|
|
/**
|
* 保存RSA公钥到文件夹
|
* @param publicKey RSA公钥内容
|
*/
|
private static void savePublicKeyToClasspath(String publicKey) throws IOException {
|
// 创建 cert 目录(如果不存在)
|
Path certDir = Paths.get(CERT_FOLDER);
|
if (!Files.exists(certDir)) {
|
Files.createDirectories(certDir); // 自动创建目录
|
}
|
|
// 写入公钥文件(UTF-8 编码)
|
Path keyFile = certDir.resolve(RSA_PUBLIC_KEY_FILENAME);
|
Files.write(keyFile, publicKey.getBytes(StandardCharsets.UTF_8));
|
}
|
/**
|
* 从文件夹加载RSA公钥
|
* @return RSA公钥内容
|
*/
|
private static String loadPublicKeyFromClasspath() throws IOException {
|
Path keyFile = Paths.get(CERT_FOLDER + RSA_PUBLIC_KEY_FILENAME);
|
byte[] bytes = Files.readAllBytes(keyFile); // 读取所有字节
|
return new String(bytes, StandardCharsets.UTF_8); // 转为UTF-8字符串
|
}
|
|
/**
|
* 加载公钥 返回PublicKey对象
|
*/
|
public static PublicKey loadPublicKey(String pemContent) throws Exception {
|
// 读取PEM文件内容
|
// String pemContent = new String(Files.readAllBytes(Paths.get(CERT_FOLDER + RSA_PUBLIC_KEY_FILENAME)), StandardCharsets.UTF_8);
|
|
// 移除PEM头尾标记
|
String publicKeyPEM = pemContent
|
.replace("-----BEGIN RSA PUBLIC KEY-----", "")
|
.replace("-----END RSA PUBLIC KEY-----", "")
|
.replaceAll("\\s", ""); // 去除换行/空格
|
|
// 解码Base64
|
byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
|
|
// 手动解析PKCS#1格式
|
DerInputStream derReader = new DerInputStream(encoded);
|
DerValue[] seq = derReader.getSequence(0);
|
BigInteger modulus = seq[0].getBigInteger();
|
BigInteger exponent = seq[1].getBigInteger();
|
|
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent);
|
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
|
}
|
|
/**
|
* 使用RSA-OAEP加密数据
|
* @param plaintext 待加密的明文
|
* @return Base64编码的密文
|
*/
|
public static String encrypt(String plaintext, PublicKey publicKey) throws Exception {
|
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING");
|
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
|
byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
|
return Base64.getEncoder().encodeToString(encryptedBytes);
|
}
|
|
|
|
/**
|
* 商户付款到银行卡(优先使用本地保存的公钥)
|
* @param partnerTradeNo 商户订单号
|
* @param bankNo 银行卡号
|
* @param trueName 银行卡真实姓名
|
* @param bankCode 银行编码
|
* @param amount 金额(分)
|
* @param desc 付款说明
|
* @return 付款结果
|
*/
|
public Map<String, String> payToBankCard(String partnerTradeNo, String bankNo, String trueName,
|
String bankCode, BigDecimal amount, String desc) throws Exception {
|
int i = amount.multiply(new BigDecimal("100")).intValue();
|
// 1. 尝试从本地加载RSA公钥
|
String pubKey = loadPublicKeyFromClasspath();
|
|
// 2. 如果本地没有公钥或公钥无效,则从微信获取新公钥
|
if (pubKey == null || pubKey.isEmpty()) {
|
pubKey = getRsaPublicKey();
|
|
}
|
//公钥对象
|
PublicKey publicKey = loadPublicKey(pubKey);
|
|
// 3. 使用RSA公钥加密银行卡号和真实姓名
|
String encryptedBankNo = encrypt(bankNo, publicKey);
|
String encryptedTrueName = encrypt(trueName, publicKey);
|
|
// 4. 构建请求参数
|
Map<String, String> params = new HashMap<>();
|
params.put("mch_id", wechatPayConfig.getMchId());
|
params.put("partner_trade_no", partnerTradeNo);
|
params.put("enc_bank_no", encryptedBankNo);
|
params.put("enc_true_name", encryptedTrueName);
|
params.put("bank_code", bankCode);
|
params.put("amount", String.valueOf(i));
|
params.put("desc", desc);
|
params.put("nonce_str", generateNonceStr());
|
|
// 生成签名
|
String sign = weixinSignature(params);
|
params.put("sign", sign);
|
|
// 将参数转换为XML
|
String xmlParams = XMLUtil.mapToXml(params);
|
|
// 5. 发送请求到微信支付付款到银行卡接口
|
String url = "https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank";
|
String result = postWithCert(url, xmlParams);
|
|
// 解析返回结果
|
Map<String, String> resultMap = XMLUtil.xmlToMap(result);
|
|
// 验证签名
|
if (!verifySign(resultMap, wechatPayConfig.getKey())) {
|
throw new Exception("付款到银行卡签名验证失败");
|
}
|
|
return resultMap;
|
}
|
|
public static void main(String[] args) throws IOException {
|
|
String info="CjlaS7RVnPn7zzP5ByZDxUN7OrXGp1/DEdO0qahpIqDH/gTNHb/U7VmrVV0S4lXrIa0N8FEREC3CdIeT4XB5P4D0E8TSURu6J/cD01hFu28f0JDRfeips3vSpTgznRGyCfnUBDPYwyrVeP29Wac7WAb3CCcJf7OZWaweOUkaKjaBRa1GzMZcguSZnQJz0cD5Jb4HbTMvM0VAebfCY9aXdfFBIbm+cPYESo3awqwkNTQeT4V+FViw8f8sjkH0TScMgWBiSKmQC837BLD27yIGklqlYkDP2IMeiNw+b12qCAGszfp2vYd3X+HpViXkQQet3PJWYlAm55R+IgvschP7Ub65XzLINfQrJKrQUXiKKO2LwoSRSwZvfDkR8G8E8X59CnU2XvWKeos5Y0q8ckbJb97yI+09nNgMjYyJoVCVjTFgFMDEQ4+e3CpYRhD6V/3RBp+TvBwszldbRav2XEuCXL2kCJyJEAqLPMNyfYBSNF8z1btjyz0+y/xQQcySKlQInZ710FxSE7KwRSBQ92j9nDdlR7UxCrPVCkEd+GrVNSqqnyjNh1J/rPJPHvvGwkPPq72TKiw6ZgaIgIDhy0/lWHTclo4sjYAWuUVfg3CJ8dqkuQwVZ7i0+NiahIl78RtcUph8NR48yUgBkN7WhCcu5wLbg2tu8Qe0SIwHF+RW1x9Yc8akEkNbMd4xzs8lY5MYEU9V16U8RyWJuwPDph3RnmV8HQ+2hfzmjCvPkBwtfR8P5VdK86OIsHfnfQxAcPM2a86tOBBzFXPrLHgd2CRcDKH+MXTw7RSH/bk1PiMUAWF8TQsNDzgUlznJnkjiQxoym/4ZUf4C6072KKQHbp6bgBYkBhJLT2lmjVMNSX5b1SXM9eTQixRfq6MKGw3P8XJnKdofktVv+KtSzWQlW0C8p504NWACiExupF5EII7FG+xCWt7urWUbc4NRI36UFrKToQCLVv6UBCXt/t9iWlvs6SfuZhpCexeMmZWeiIldzRu87U9rXR46Hu7DAL8dZ+0ItsIZYThSIABzZgaLKggXlkjyAcbcPYKO7egrCmDtFhzHuh4uA3VeBylL3/ZLZ4FUedn/8L4e2iAu22Qj46ORlu17W5R8Ez9kubydeAgC9PkWnjptaubPxE0bjPN69tec";
|
|
String key="fD0JzscfMf295SYtRK3MnPRjSCA4Gahr";
|
try {
|
String decrypted = decrypt(info, key);
|
System.out.println("解密结果: " + decrypted);
|
} catch (Exception e) {
|
throw new RuntimeException(e);
|
}
|
}
|
|
|
public static String decrypt(String encryptedStringA, String merchantKey) throws Exception {
|
// 1. 对加密串A做base64解码,得到加密串B
|
byte[] decode = Base64.getDecoder().decode(encryptedStringA);
|
|
// 2. 对商户key做md5,得到32位小写key*
|
String sign = MD5AndKL.MD5Encode(merchantKey, "UTF-8").toLowerCase();
|
|
// 3. 确保BouncyCastle提供者已添加
|
if (Security.getProvider("BC") == null) {
|
Security.addProvider(new BouncyCastleProvider());
|
}
|
|
// 4. 使用AES-256-ECB解密(PKCS7Padding)
|
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
|
|
// 注意:微信要求使用AES-256,所以密钥应为32字节(256位)
|
// 如果MD5结果是32字节(256位),直接使用
|
byte[] aesKey = sign.getBytes(StandardCharsets.UTF_8);
|
|
SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES");
|
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
|
|
// 执行解密并指定UTF-8编码
|
byte[] decryptedBytes = cipher.doFinal(decode);
|
return new String(decryptedBytes, StandardCharsets.UTF_8);
|
}
|
|
|
}
|