| | |
| | | import com.alibaba.fastjson2.JSON; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.other.util.payment.MD5AndKL; |
| | | import com.ruoyi.other.util.payment.wx.WechatPayConfig; |
| | | import com.ruoyi.other.util.payment.wx.XMLUtil; |
| | | 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.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.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.security.spec.RSAPublicKeySpec; |
| | | import java.security.spec.X509EncodedKeySpec; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * 微信支付服务类 |
| | |
| | | @Autowired |
| | | private WechatPayConfig wechatPayConfig; |
| | | private static final String RSA_PUBLIC_KEY_FILENAME = "wechat_rsa_public_key.pem"; |
| | | private static final String CERT_FOLDER = "C:\\cert\\"; |
| | | private static final String CERT_FOLDER = "cert/"; |
| | | /** |
| | | * 统一下单 |
| | | * native统一下单 |
| | | * @param orderNumber 订单号 |
| | | * @param totalFee 总金额(分) |
| | | * @param body 商品描述 |
| | | * @param openid 用户openid |
| | | * @return 预支付订单信息 |
| | | */ |
| | | public R unifiedOrder(String orderNumber, String totalFee, String body, String openid, String callbackPath) throws Exception { |
| | | public Map<String, String> unifiedOrder(String orderNumber, String totalFee, |
| | | String body, |
| | | String callbackPath) throws Exception { |
| | | int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue(); |
| | | String hostAddress = null; |
| | | try { |
| | |
| | | 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); |
| | | |
| | | params.put("notify_url", "http://221.182.45.100:8084"+callbackPath); |
| | | params.put("trade_type", "NATIVE"); |
| | | // 生成签名 |
| | | 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("微信支付签名验证失败"); |
| | | throw new Exception("微信支付签名验证失败"); |
| | | } |
| | | if (!resultMap.get("return_code").equals("SUCCESS")) { |
| | | throw new Exception("拉取支付失败"); |
| | | |
| | | // 构建小程序支付所需参数 |
| | | 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"); |
| | | return R.ok(JSON.toJSONString(payParams)); |
| | | } |
| | | resultMap.put("code",orderNumber); |
| | | return resultMap; |
| | | } |
| | | /** |
| | | * 微信下单的签名算法 |
| | |
| | | try { |
| | | // 1. 解析回调XML数据 |
| | | if (StringUtils.isEmpty(xmlData)) { |
| | | // logger.error("退款回调数据为空"); |
| | | return RefundCallbackResult.fail("回调数据为空"); |
| | | } |
| | | |
| | | //2.解析参数 |
| | | System.err.println(xmlData); |
| | | System.out.println(xmlData); |
| | | System.out.println("----------------------------------------"); |
| | | Map<String, String> resultMap = XMLUtil.xmlToMap(xmlData); |
| | | System.out.println(resultMap.get("req_info")); |
| | |
| | | } |
| | | |
| | | //4 使用商户API密钥解密req_info(AES-256-CBC算法) |
| | | String decryptData = wxDecrypt(resultMap.get("req_info"), wechatPayConfig.getKey()); |
| | | String decryptData = decrypt(resultMap.get("req_info"), wechatPayConfig.getKey()); |
| | | Map<String, String> refundDetail = XMLUtil.xmlToMap(decryptData); |
| | | |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 加载公钥 返回PublicKey |
| | | * 加载公钥 返回PublicKey对象 |
| | | */ |
| | | public static PublicKey loadPublicKey(String pemContent) throws Exception { |
| | | // 读取PEM文件内容 |
| | |
| | | } |
| | | |
| | | public static void main(String[] args) throws IOException { |
| | | /* |
| | | try { |
| | | // 1. 加载公钥 |
| | | PublicKey publicKey = loadPublicKey(); |
| | | |
| | | // 2. 加密数据 |
| | | String sensitiveData = "用户名"; |
| | | String encryptedData = encrypt(sensitiveData, publicKey); |
| | | 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"; |
| | | |
| | | System.out.println("加密结果(Base64):\n" + encryptedData); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | }*/ |
| | | |
| | | String info="CjlaS7RVnPn7zzP5ByZDxUN7OrXGp1/DEdO0qahpIqDH/gTNHb/U7VmrVV0S4lXrIa0N8FEREC3CdIeT4XB5P4D0E8TSURu6J/cD01hFu2/uJOvcE6EeQH2xiRg/Wir4qcW7c6uTiLoqyirCQXcGzQb3CCcJf7OZWaweOUkaKjaBRa1GzMZcguSZnQJz0cD5jTMx+Tch5+b7jBq5PrTFxtMSH/DAG+kgkRazDFnEzkMeT4V+FViw8f8sjkH0TScMgWBiSKmQC837BLD27yIGklqlYkDP2IMeiNw+b12qCAGszfp2vYd3X+HpViXkQQet3PJWYlAm55R+IgvschP7Ub65XzLINfQrJKrQUXiKKO2LwoSRSwZvfDkR8G8E8X59CnU2XvWKeos5Y0q8ckbJb97yI+09nNgMjYyJoVCVjTGc7ghcYvWKbqanJ8bSFqiBCIqLSXsRR2DmJIxHq9fGE72kCJyJEAqLPMNyfYBSNF8z1btjyz0+y/xQQcySKlQInZ710FxSE7KwRSBQ92j9nDdlR7UxCrPVCkEd+GrVNSqqnyjNh1J/rPJPHvvGwkPPq72TKiw6ZgaIgIDhy0/lWHTclo4sjYAWuUVfg3CJ8dqkuQwVZ7i0+NiahIl78RtcUph8NR48yUgBkN7WhCcu5wLbg2tu8Qe0SIwHF+RW1x9Yc8akEkNbMd4xzs8lY5MYEU9V16U8RyWJuwPDph3RnmV8HQ+2hfzmjCvPkBwtfR8P5VdK86OIsHfnfQxAcPM2a86tOBBzFXPrLHgd2CRcDKH+MXTw7RSH/bk1PiMUAWF8TQsNDzgUlznJnkjiQxoym/4ZUf4C6072KKQHbp6bgBYkBhJLT2lmjVMNSX5b1SXM9eTQixRfq6MKGw3P8XJnKdofktVv+KtSzWQlW0C8p504NWACiExupF5EII7FG+xbTa/s7vxXCP7R98tpcQTGoQCLVv6UBCXt/t9iWlvs6SfuZhpCexeMmZWeiIldzRu87U9rXR46Hu7DAL8dZ+0ItsIZYThSIABzZgaLKggXlkjyAcbcPYKO7egrCmDtFhwN50V7hoXEQB8G5kf/lMuT5+xNE2FRmv7H2a0ttZiv4u17W5R8Ez9kubydeAgC9PkWnjptaubPxE0bjPN69tec"; |
| | | String key="fD0JzscfMf295SYtRK3MnPRjSCA4Gahr"; |
| | | try { |
| | | String decrypted = decrypt(info, key); |
| | |
| | | |
| | | |
| | | public static String decrypt(String encryptedStringA, String merchantKey) throws Exception { |
| | | try { |
| | | byte[] decode = Base64.getDecoder().decode(encryptedStringA); |
| | | String sign = MD5AndKL.MD5Encode(merchantKey, "UTF-8").toLowerCase(); |
| | | System.out.println("MD5 Key: " + sign); // 调试输出 |
| | | // 1. 对加密串A做base64解码,得到加密串B |
| | | byte[] decode = Base64.getDecoder().decode(encryptedStringA); |
| | | |
| | | if (Security.getProvider("BC") == null) { |
| | | Security.addProvider(new BouncyCastleProvider()); |
| | | } |
| | | // 2. 对商户key做md5,得到32位小写key* |
| | | String sign = MD5AndKL.MD5Encode(merchantKey, "UTF-8").toLowerCase(); |
| | | |
| | | Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC"); |
| | | byte[] aesKey = Arrays.copyOf(sign.getBytes("UTF-8"), 16); // 明确指定 UTF-8 |
| | | SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES"); |
| | | cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); |
| | | |
| | | byte[] decryptedBytes = cipher.doFinal(decode); |
| | | return new String(decryptedBytes, "UTF-8"); // 明确指定 UTF-8 |
| | | } catch (Exception e) { |
| | | System.err.println("解密失败: " + e.getMessage()); |
| | | throw e; |
| | | // 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); |
| | | } |
| | | |
| | | |
| | | } |