From 3b83ab28edbba5402cd95c48b89041c043968d2c Mon Sep 17 00:00:00 2001 From: Diyu0904 <1819728964@qq.com> Date: Mon, 6 Jan 2025 17:44:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=BE=AE=E4=BF=A1=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E5=B7=A5=E5=85=B7=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mcwl-admin/pom.xml | 8 + .../pay/{ => AliPay}/AliPayIntegration.java | 2 +- .../{ => AliPay}/OrderTradeController.java | 2 +- .../pay/WxPay/config/DirectConnection.java | 33 ++++ .../controller/pay/WxPay/util/AesUtil.java | 49 ++++++ .../controller/pay/WxPay/util/PayUtil.java | 144 ++++++++++++++++++ mcwl-admin/src/main/resources/application.yml | 4 +- 7 files changed, 238 insertions(+), 4 deletions(-) rename mcwl-admin/src/main/java/com/mcwl/web/controller/pay/{ => AliPay}/AliPayIntegration.java (97%) rename mcwl-admin/src/main/java/com/mcwl/web/controller/pay/{ => AliPay}/OrderTradeController.java (99%) create mode 100644 mcwl-admin/src/main/java/com/mcwl/web/controller/pay/WxPay/config/DirectConnection.java create mode 100644 mcwl-admin/src/main/java/com/mcwl/web/controller/pay/WxPay/util/AesUtil.java create mode 100644 mcwl-admin/src/main/java/com/mcwl/web/controller/pay/WxPay/util/PayUtil.java diff --git a/mcwl-admin/pom.xml b/mcwl-admin/pom.xml index 914c381..4b0db6d 100644 --- a/mcwl-admin/pom.xml +++ b/mcwl-admin/pom.xml @@ -16,6 +16,14 @@ + + + + com.github.wechatpay-apiv3 + wechatpay-apache-httpclient + 0.4.7 + + com.alipay.sdk alipay-easysdk diff --git a/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/AliPayIntegration.java b/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/AliPay/AliPayIntegration.java similarity index 97% rename from mcwl-admin/src/main/java/com/mcwl/web/controller/pay/AliPayIntegration.java rename to mcwl-admin/src/main/java/com/mcwl/web/controller/pay/AliPay/AliPayIntegration.java index dbdab88..821dfa1 100644 --- a/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/AliPayIntegration.java +++ b/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/AliPay/AliPayIntegration.java @@ -1,4 +1,4 @@ -package com.mcwl.web.controller.pay; +package com.mcwl.web.controller.pay.AliPay; import com.alibaba.fastjson.JSONObject; import com.alipay.easysdk.factory.Factory; diff --git a/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/OrderTradeController.java b/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/AliPay/OrderTradeController.java similarity index 99% rename from mcwl-admin/src/main/java/com/mcwl/web/controller/pay/OrderTradeController.java rename to mcwl-admin/src/main/java/com/mcwl/web/controller/pay/AliPay/OrderTradeController.java index 125a6af..4bcf761 100644 --- a/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/OrderTradeController.java +++ b/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/AliPay/OrderTradeController.java @@ -1,4 +1,4 @@ -package com.mcwl.web.controller.pay; +package com.mcwl.web.controller.pay.AliPay; import cn.hutool.extra.qrcode.QrCodeUtil; import com.alipay.easysdk.factory.Factory; diff --git a/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/WxPay/config/DirectConnection.java b/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/WxPay/config/DirectConnection.java new file mode 100644 index 0000000..16bdf70 --- /dev/null +++ b/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/WxPay/config/DirectConnection.java @@ -0,0 +1,33 @@ +package com.mcwl.web.controller.pay.WxPay.config; + +/** + * 微信支付配置 + * @author DaiZibo + * @date 2025/1/6 + * @apiNote + */ + +public interface DirectConnection { + + //支付成功后的回调地址 + String NOTIFY_URL = ""; + + //商户号 + String MCH_ID = ""; + + //商户证书序列号 + String MCH_SERIAL_NO = ""; + + //V3密钥 + String API_3KEY = ""; + + //小程序或者公众号的ApId + String APP_ID = ""; + + // 商户证书序列号对应的证书秘钥 + String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDOsZnx5Nh4qK7O" + + "vzbDOQu5UMtSdoZWyqOC+gFNVAB7aPAzQwgN7OAUt7G8synPRdovQ/l116dZ0ZiX" + + "XQX3Le8/o5szRH6LxpqcpFMaZg2N/HOydyTMaHI0wnZIc9BXR8aaXl7uVQnydF40" + + "FoWicge6vTCXOyjirTpS2PGKy9+hu0vx7GbX1NUDl2hNXkH54pdWn5eof1fnbh/V" + + "45q/OS7d9qnpYfs1ff+0nA=="; +} diff --git a/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/WxPay/util/AesUtil.java b/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/WxPay/util/AesUtil.java new file mode 100644 index 0000000..1e4406a --- /dev/null +++ b/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/WxPay/util/AesUtil.java @@ -0,0 +1,49 @@ +package com.mcwl.web.controller.pay.WxPay.util; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +/** + * 回调签名工具类 + * + * @author DaiZibo + * @date 2025/1/6 + * @apiNote + */ + +public class AesUtil { + + static final int KEY_LENGTH_BYTE = 32; + static final int TAG_LENGTH_BIT = 128; + private final byte[] aesKey; + + public AesUtil(byte[] key) { + if (key.length != KEY_LENGTH_BYTE) { + throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节"); + } + this.aesKey = key; + } + + public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException { + try { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + SecretKeySpec key = new SecretKeySpec(aesKey, "AES"); + GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce); + cipher.init(Cipher.DECRYPT_MODE, key, spec); + cipher.updateAAD(associatedData); + return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8"); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new IllegalStateException(e); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/WxPay/util/PayUtil.java b/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/WxPay/util/PayUtil.java new file mode 100644 index 0000000..15d5d0a --- /dev/null +++ b/mcwl-admin/src/main/java/com/mcwl/web/controller/pay/WxPay/util/PayUtil.java @@ -0,0 +1,144 @@ +package com.mcwl.web.controller.pay.WxPay.util; + +import cn.hutool.core.util.RandomUtil; +import com.alibaba.fastjson2.JSONObject; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.mcwl.web.controller.pay.WxPay.config.DirectConnection; +import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder; +import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; +import com.wechat.pay.contrib.apache.httpclient.auth.Verifier; +import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials; +import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator; +import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager; +import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException; +import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException; +import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.util.Base64; + +/** + * 微信支付工具类 + * + * @author DaiZibo + * @date 2025/1/6 + * @apiNote + */ + +public class PayUtil { + + private CloseableHttpClient httpClient; + private CertificatesManager certificatesManager; + private Verifier verifier; + private PrivateKey merchantPrivateKey; + + { + try { + merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(DirectConnection.privateKey.getBytes("utf-8"))); + // 获取证书管理器实例 + certificatesManager = CertificatesManager.getInstance(); + // 向证书管理器增加需要自动更新平台证书的商户信息 + certificatesManager.putMerchant(DirectConnection.MCH_ID, new WechatPay2Credentials(DirectConnection.MCH_ID, + new PrivateKeySigner(DirectConnection.MCH_SERIAL_NO, merchantPrivateKey)), + DirectConnection.API_3KEY.getBytes(StandardCharsets.UTF_8)); + // 从证书管理器中获取verifier + verifier = certificatesManager.getVerifier(DirectConnection.MCH_ID); + httpClient = WechatPayHttpClientBuilder.create() + .withMerchant(DirectConnection.MCH_ID, DirectConnection.MCH_SERIAL_NO, merchantPrivateKey) + .withValidator(new WechatPay2Validator(certificatesManager.getVerifier(DirectConnection.MCH_ID))) + .build(); + } catch (IOException | GeneralSecurityException | HttpCodeException | NotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * 统一下单,获取到 prepay_id,然后获取签名。 + */ + public String requestwxChatPay(String orderSn, int total, String description, String openid) throws Exception { + HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"); + httpPost.addHeader("Accept", "application/json"); + httpPost.addHeader("Content-type", "application/json; charset=utf-8"); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectMapper objectMapper = new ObjectMapper(); + //组合请求参数JSON格式 + ObjectNode rootNode = objectMapper.createObjectNode(); + rootNode.put("mchid", DirectConnection.MCH_ID) + .put("appid", DirectConnection.APP_ID) + .put("notify_url", DirectConnection.NOTIFY_URL + "returnNotify") + .put("description", description) + .put("out_trade_no", orderSn); + rootNode.putObject("amount") + // total:金额,以分为单位,假如是10块钱,那就要写 1000 + .put("total", total) + .put("currency", "CNY"); + rootNode.putObject("payer") + // openid:用户在该小程序或者公众号下的openid + .put("openid", openid); + try { + objectMapper.writeValue(bos, rootNode); + httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8")); + //获取预支付ID + CloseableHttpResponse response = httpClient.execute(httpPost); + String bodyAsString = EntityUtils.toString(response.getEntity()); + //微信成功响应 + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode == 200) { + //时间戳 + String timestamp = System.currentTimeMillis() / 1000 + ""; + //随机字符串 + String nonce = RandomUtil.randomString(32); + StringBuilder builder = new StringBuilder(); + // Appid + builder.append(DirectConnection.APP_ID).append("\n"); + // 时间戳 + builder.append(timestamp).append("\n"); + // 随机字符串 + builder.append(nonce).append("\n"); + JsonNode jsonNode = objectMapper.readTree(bodyAsString); + // 预支付会话ID + builder.append("prepay_id=").append(jsonNode.get("prepay_id").textValue()).append("\n"); + //获取签名 + String sign = this.sign(builder.toString().getBytes("utf-8"), merchantPrivateKey); + JSONObject jsonMap = new JSONObject(); + jsonMap.put("noncestr", nonce); + jsonMap.put("timestamp", timestamp); + jsonMap.put("prepayid", jsonNode.get("prepay_id").textValue()); + jsonMap.put("sign", sign); + jsonMap.put("appid", DirectConnection.APP_ID); + jsonMap.put("partnerid", DirectConnection.MCH_ID); + return jsonMap.toJSONString();//响应签名数据,前端拿着响应数据调起微信SDK + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 计算签名 + */ + private String sign(byte[] message, PrivateKey yourPrivateKey) { + try { + Signature sign = Signature.getInstance("SHA256withRSA"); + sign.initSign(yourPrivateKey); + sign.update(message); + return Base64.getEncoder().encodeToString(sign.sign()); + } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { + e.printStackTrace(); + } + return ""; + } + +} diff --git a/mcwl-admin/src/main/resources/application.yml b/mcwl-admin/src/main/resources/application.yml index 9ee7657..36eb823 100644 --- a/mcwl-admin/src/main/resources/application.yml +++ b/mcwl-admin/src/main/resources/application.yml @@ -95,7 +95,7 @@ token: # 令牌密钥 secret: abcdefghijklmnopqrstuvwxyz # 令牌有效期(默认30分钟) - expireTime: 30 + expireTime: 1440 # MyBatis Plus配置 mybatis-plus: @@ -183,5 +183,5 @@ mall: appId: 9021000140696171 privateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCIw8lnyhxyCK/2NLXeJcZUwOoU9hA6Opg5dxJqOrLB0yZpxSrqgv+PooMBzFV5Wwf0s388sC9cdTEUm8NevIAwlioCc2JLfNwgVtfOHVQr4A4bwrzg9zpDJWw5fHmxfGodPCQCF2ZfdY48n/W8eI+6WdInIg7yXUT3/xN3nMepA2J96TYBrqwURoFMzzw0Z3fV+z82QOQrIAJQeMVsTkBZOeZriykNs7sVT+DtLuGDsznEDTj04xu1Miymd81sxeGxmR8j53/nlN4UCnKn+BjwmPGn9/PolSknAzjkgnFaeOX7gG+wSYus+R6UcVEvql3uYEHp4iBi/dukV/vXw9xzAgMBAAECggEABrHOwg3apppWQq8B2AXxV2S6EJya3r7PhMCXXivRZDt5veOw3hnbeKRz1TqzVGVoVkoTCF13dAcoh+G4BoMjoz02gTg0r41FGTAnECfPXvZwulsl870mVPMYARhevJlieFvhR1jt4e5XMBCEbt1X679J0jCQmryGC72orko2W8eD2NAoEVOZy7ZGhNAsNSAisHDGy0XlDTcU1h9MlEb7qeg1N48L+GrYZB27FHxWR8DwXhK+nlznJrNOPcDhvFds+LM1YH2wV2sKWMPmjW5gZv+kLdHK7tY6oHEmdG8Co4ULSCwQl9/ZCbQJC5n1K80Cxs6GbO02pl2+J/1psNebwQKBgQDqk+O1HuasPGWbbvOZz3RBtaGqnarZWXWPa0K7xtTjyVyNtvdzaU9lY/jHvyCsq/4djmIE9QL4v2HZm6L8X1rPP0mZLGygZuoYhf2G8OaKA1XLFvplYMVNmxa5FHOlW0udxzbIjrNQY5TnMZLsmeI18+1W8n7zMBBrmwJhayioZwKBgQCVQSnSe7SZHXV30I5OVzJtnx2hrerskNtxNAsMjZGgKWk6FKK/5j33SrUCBT6BH+jPrhQqVcdv62c7Vjuj8oM6BH2kUJykXKWGDJgltB8jPvl7Cc+IGKMNcrmD1gF24xzHeAK6m3yfZHAgbsI90kGRAqTvKOR8troBIpXyqcoUFQKBgQCj/louEHlhpiO+w5WITVFQcuOf/PQpWXJnnqwbzMieqvKKnIRoahI4puiQ9JyqOt+/1//MyWGXd2y4Diem2h8JToxsinMyvGAiXuI8iYQSrnw3LExHvZySQb9kCtlCnA3EMzaohaL0q3m5G55EzNwpreuaYpO//jUslUTXyGu3NwKBgAsFN7hR6ADSOwUwwgEo0RFlCUQR2mHDRGC0bghsP49q31kpgubWckbbHkha6MWR/JexC84gbUxnPkUUVe0CEAxtnnVOenJsYNBoB6fpB8+La9JY5BjxcMCicu44E/2aQVq9Er6Cko4WVW0foHn+F1dEsbby9wP6XfuNeTLktIYNAoGAUS2pUS30KMeeVyHj0uFey+tbsDA0MHZal0ZRsNwWCIwlBaouTIVmzKlz5t2Eg4JtoZov1n9T3BU6pnfNqJxOUEirRYz+WJxH9usIy8F3RAICjLqBbIYGdgtF+YSLKPWx9RrB5JRjgOYXByhPyGqQSARI+L4wVQxeENxypfqgLbo= publicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqKWaJol63OuxavPwcqLNahXzxrDDqlqrsFDkeG3j5F+15qUJvBxmKiirAVWXeBkDFf/6cdBlI4gvkfNCE4TUq59nGfpRGEx0Gv5WhPfD1RJMPmjOhbCtuBkQDGPOSKF42zxL+B0o9lmV/z02hPrE5a8D8rarZkmEGFu96o10oMcg6o75u0JSDH+hBgA+B42zDghpLrEyMoljJDt5cQwnatStmKpHlhSZoKnzTwWN4q8YAMy/8J8yvy/RMz6jwntacbN8Rm9d1bM7BDCAU62CjhGZKLu14HrFRX/bDSlnL6Ud+J9Hhnr6sYSCG+Oqu3hu2zpeqRFVj/vKPqOsIZ8fUwIDAQAB - notifyUrl: http://susan.net.cn/notify + notifyUrl: http://zppqfa.natappfree.cc/web/pay/notify gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do