From d2715834cec72d9e365ddb8a1e38b69b81168905 Mon Sep 17 00:00:00 2001 From: DongZeLiang <2746733890@qq.com> Date: Tue, 29 Aug 2023 13:18:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/wechat/clients/WeChatAppClient.java | 34 +++++++ .../wechat/clients/WeChatNativeClient.java | 6 +- .../interceptor/WeChatAuthInterceptor.java | 10 +- .../pay/wechat/config/WechatPayConfigure.java | 42 ++++++++- .../wechat/domain/model/WeChatSignModel.java | 26 +++++ .../wechat/domain/req/WeChatPayAppReq.java | 48 ++++++++++ ...WeChatPay.java => WeChatPayNativeReq.java} | 2 +- .../pay/wechat/service/WeChatSignService.java | 94 +++++++++---------- src/main/resources/wechat/wxpaykey.pem | 0 .../com/muyu/wechat/pay/AppClientTest.java | 41 ++++++++ .../com/muyu/wechat/pay/NativeClientTest.java | 4 +- 11 files changed, 240 insertions(+), 67 deletions(-) create mode 100644 src/main/java/com/muyu/pay/wechat/clients/WeChatAppClient.java create mode 100644 src/main/java/com/muyu/pay/wechat/domain/model/WeChatSignModel.java create mode 100644 src/main/java/com/muyu/pay/wechat/domain/req/WeChatPayAppReq.java rename src/main/java/com/muyu/pay/wechat/domain/req/{WeChatPayReqWeChatPay.java => WeChatPayNativeReq.java} (93%) create mode 100644 src/main/resources/wechat/wxpaykey.pem create mode 100644 src/test/java/com/muyu/wechat/pay/AppClientTest.java diff --git a/src/main/java/com/muyu/pay/wechat/clients/WeChatAppClient.java b/src/main/java/com/muyu/pay/wechat/clients/WeChatAppClient.java new file mode 100644 index 0000000..1cc779b --- /dev/null +++ b/src/main/java/com/muyu/pay/wechat/clients/WeChatAppClient.java @@ -0,0 +1,34 @@ +package com.muyu.pay.wechat.clients; + +import com.dtflys.forest.annotation.BaseRequest; +import com.dtflys.forest.annotation.JSONBody; +import com.dtflys.forest.annotation.Post; +import com.muyu.pay.wechat.clients.interceptor.WeChatAuthInterceptor; +import com.muyu.pay.wechat.domain.req.WeChatPayAppReq; + +/** + * @author DongZl + * @description: 微信App支付 + * @Date 2023/8/29 12:44 + */ + +@BaseRequest( + baseURL = "https://api.mch.weixin.qq.com/v3/pay/transactions", + contentType = "application/json", + headers = { + "Accept: application/json" + }, + interceptor = WeChatAuthInterceptor.class +) +public interface WeChatAppClient { + + /** + * App 支付下单 + * @param weChatPayReq 请求参数 + * @return 响应结果 + */ + @Post( + url = "/app" + ) + public String order(@JSONBody WeChatPayAppReq weChatPayReq); +} diff --git a/src/main/java/com/muyu/pay/wechat/clients/WeChatNativeClient.java b/src/main/java/com/muyu/pay/wechat/clients/WeChatNativeClient.java index 2721c53..3325baa 100644 --- a/src/main/java/com/muyu/pay/wechat/clients/WeChatNativeClient.java +++ b/src/main/java/com/muyu/pay/wechat/clients/WeChatNativeClient.java @@ -4,7 +4,7 @@ import com.dtflys.forest.annotation.BaseRequest; import com.dtflys.forest.annotation.JSONBody; import com.dtflys.forest.annotation.Post; import com.muyu.pay.wechat.clients.interceptor.WeChatAuthInterceptor; -import com.muyu.pay.wechat.domain.req.WeChatPayReqWeChatPay; +import com.muyu.pay.wechat.domain.req.WeChatPayNativeReq; /** * @author DongZl @@ -23,11 +23,11 @@ public interface WeChatNativeClient { /** * Native 支付下单 - * @param jsonObject 请求参数 + * @param weChatPayReq 请求参数 * @return 响应结果 */ @Post( url = "/native" ) - public String order(@JSONBody WeChatPayReqWeChatPay weChatPayReq); + public String order(@JSONBody WeChatPayNativeReq weChatPayReq); } diff --git a/src/main/java/com/muyu/pay/wechat/clients/interceptor/WeChatAuthInterceptor.java b/src/main/java/com/muyu/pay/wechat/clients/interceptor/WeChatAuthInterceptor.java index cee02f7..400aaca 100644 --- a/src/main/java/com/muyu/pay/wechat/clients/interceptor/WeChatAuthInterceptor.java +++ b/src/main/java/com/muyu/pay/wechat/clients/interceptor/WeChatAuthInterceptor.java @@ -6,6 +6,7 @@ import com.dtflys.forest.interceptor.Interceptor; import com.dtflys.forest.reflection.ForestMethod; import com.muyu.pay.wechat.config.WechatPayConfig; import com.muyu.pay.wechat.domain.WeChatPayBaseReq; +import com.muyu.pay.wechat.domain.model.WeChatSignModel; import com.muyu.pay.wechat.service.WeChatSignService; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; @@ -57,17 +58,16 @@ public class WeChatAuthInterceptor implements Interceptor { log.debug("微信请求前拦截 - 添加Authorization授权请求头"); ForestBody forestBody = req.getBody(); String body = forestBody.encodeToString(); - ForestMethod forestMethod = req.getMethod(); String methodName = req.getType().getName(); String url = req.getUrl(); - String authorization = null; + WeChatSignModel weChatSignModel = null; if (HttpMethod.GET.matches(methodName)){ - authorization = weChatSignService.tokenByGet(url); + weChatSignModel = weChatSignService.tokenByGet(url); }else if (HttpMethod.POST.matches(methodName)){ - authorization = weChatSignService.tokenByPost(url, body); + weChatSignModel = weChatSignService.tokenByPost(url, body); } // 添加Header - req.addHeader("Authorization", authorization); + req.addHeader("Authorization", weChatSignModel.getSign()); // 继续执行请求返回true return true; } diff --git a/src/main/java/com/muyu/pay/wechat/config/WechatPayConfigure.java b/src/main/java/com/muyu/pay/wechat/config/WechatPayConfigure.java index a692d75..cca0849 100644 --- a/src/main/java/com/muyu/pay/wechat/config/WechatPayConfigure.java +++ b/src/main/java/com/muyu/pay/wechat/config/WechatPayConfigure.java @@ -2,6 +2,18 @@ package com.muyu.pay.wechat.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; + import static com.muyu.pay.wechat.constant.WechatPayConstant.*; /** @@ -12,6 +24,26 @@ import static com.muyu.pay.wechat.constant.WechatPayConstant.*; @Configuration public class WechatPayConfigure { + public PrivateKey getPrivateKey(String filename) { + + try { + String content = new String(Files.readAllBytes(Paths.get(filename)), StandardCharsets.UTF_8); + String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s+", ""); + + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePrivate( + new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("当前Java环境不支持RSA", e); + } catch (InvalidKeySpecException e) { + throw new RuntimeException("无效的密钥格式"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + /** * 初始化微信支付配置 * @return 微信支付配置 @@ -19,15 +51,15 @@ public class WechatPayConfigure { @Bean public WechatPayConfig initWechatPayConfig(){ return WechatPayConfig.builder() - .appid("wx1a60b1d281c88896") - .merchantId("1645775803") - .payKey("wentaoyun1234567wentaoyun1234567") + .appid("*****") + .merchantId("*****") + .payKey("*****") .algorithmType(ALGORITHM_TYPE) .schema(SCHEMA) // 自己填我不知道 - .serialNo(null) + .serialNo("******") // 自己填我不知道 - .privateKey(null) + .privateKey(getPrivateKey("D:\\work\\demo\\wechat-pay\\src\\main\\resources\\wechat\\wxpaykey.pem")) .build(); } } diff --git a/src/main/java/com/muyu/pay/wechat/domain/model/WeChatSignModel.java b/src/main/java/com/muyu/pay/wechat/domain/model/WeChatSignModel.java new file mode 100644 index 0000000..6108a6c --- /dev/null +++ b/src/main/java/com/muyu/pay/wechat/domain/model/WeChatSignModel.java @@ -0,0 +1,26 @@ +package com.muyu.pay.wechat.domain.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author DongZl + * @description: 微信支付Sign签名对象 + * @Date 2023/8/29 12:54 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WeChatSignModel { + + private String sign; + + + private long timestamp; + + + private String nonceStr; +} diff --git a/src/main/java/com/muyu/pay/wechat/domain/req/WeChatPayAppReq.java b/src/main/java/com/muyu/pay/wechat/domain/req/WeChatPayAppReq.java new file mode 100644 index 0000000..e0b1d62 --- /dev/null +++ b/src/main/java/com/muyu/pay/wechat/domain/req/WeChatPayAppReq.java @@ -0,0 +1,48 @@ +package com.muyu.pay.wechat.domain.req; + +import com.alibaba.fastjson.annotation.JSONField; +import com.muyu.pay.wechat.domain.WeChatPayBaseReq; +import com.muyu.pay.wechat.domain.model.WeChatPayAmountModel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + + +/** + * @author DongZl + * @description: 微信支付模型 + * @Date 2023-8-21 下午 03:17 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class WeChatPayAppReq extends WeChatPayBaseReq { + + /** + * 商品描述 + */ + private String description; + + /** + * 商户订单号 + */ + @JSONField(name = "out_trade_no") + private String outTradeNo; + + /** + * 通知地址 + */ + @JSONField(name = "notify_url") + private String notifyUrl; + + + /** + * 订单金额 + */ + @JSONField(name = "amount") + private WeChatPayAmountModel payAmountModel; +} diff --git a/src/main/java/com/muyu/pay/wechat/domain/req/WeChatPayReqWeChatPay.java b/src/main/java/com/muyu/pay/wechat/domain/req/WeChatPayNativeReq.java similarity index 93% rename from src/main/java/com/muyu/pay/wechat/domain/req/WeChatPayReqWeChatPay.java rename to src/main/java/com/muyu/pay/wechat/domain/req/WeChatPayNativeReq.java index 5e86faa..d7e2081 100644 --- a/src/main/java/com/muyu/pay/wechat/domain/req/WeChatPayReqWeChatPay.java +++ b/src/main/java/com/muyu/pay/wechat/domain/req/WeChatPayNativeReq.java @@ -17,7 +17,7 @@ import lombok.experimental.SuperBuilder; @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(callSuper = true) -public class WeChatPayReqWeChatPay extends WeChatPayBaseReq { +public class WeChatPayNativeReq extends WeChatPayBaseReq { /** * 商品描述 diff --git a/src/main/java/com/muyu/pay/wechat/service/WeChatSignService.java b/src/main/java/com/muyu/pay/wechat/service/WeChatSignService.java index cf7d8f1..349a28b 100644 --- a/src/main/java/com/muyu/pay/wechat/service/WeChatSignService.java +++ b/src/main/java/com/muyu/pay/wechat/service/WeChatSignService.java @@ -1,18 +1,25 @@ package com.muyu.pay.wechat.service; import com.muyu.pay.wechat.config.WechatPayConfig; +import com.muyu.pay.wechat.domain.model.WeChatSignModel; import com.muyu.utils.IdUtils; import lombok.AllArgsConstructor; import lombok.extern.log4j.Log4j2; import okhttp3.HttpUrl; import org.springframework.stereotype.Service; +import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.Signature; -import java.security.SignatureException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import static com.muyu.pay.wechat.constant.WechatPayConstant.ALGORITHM_TYPE; /** * @author DongZl @@ -28,72 +35,57 @@ public class WeChatSignService { */ private final WechatPayConfig wechatPayConfig; - - public String tokenByPost(String url, String bodyJson){ - return wechatPayConfig.getSchema() + " " + getToken("POST", HttpUrl.parse(url), bodyJson); + public WeChatSignModel tokenByPost(String url, String bodyJson){ + return getToken("POST", HttpUrl.parse(url), bodyJson); } - public String tokenByGet(String url){ - return wechatPayConfig.getSchema() + " " + getToken("GET", HttpUrl.parse(url), ""); + public WeChatSignModel tokenByGet(String url){ + return getToken("POST", HttpUrl.parse(url), ""); } - /** - * 获取token - * @param method 请求方式 - * @param url url - * @param body 请求参数 - * @return 签名 token - */ - public String getToken(String method, HttpUrl url, String body) { - String nonceStr = IdUtils.simpleUUID().toUpperCase(); + public WeChatSignModel getToken(String method, HttpUrl url, String body) { + String nonceStr = IdUtils.simpleUUID(); long timestamp = System.currentTimeMillis() / 1000; String message = buildMessage(method, url, timestamp, nonceStr, body); - String signature = sign(message.getBytes(StandardCharsets.UTF_8)); - return "mchid=\"" + wechatPayConfig.getPayKey() + "\"," - + "nonce_str=\"" + nonceStr + "\"," - + "timestamp=\"" + timestamp + "\"," - + "serial_no=\"" + wechatPayConfig.getSerialNo() + "\"," - + "signature=\"" + signature + "\""; - } - - /** - * 进行算法签名 - * @param message 信息 - * @return 签名信息 - */ - private String sign(byte[] message) { - Signature sign = null; + String signature = null; try { - sign = Signature.getInstance(wechatPayConfig.getAlgorithmType()); - sign.initSign(wechatPayConfig.getPrivateKey()); - sign.update(message); - return Base64.getEncoder().encodeToString(sign.sign()); - } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { - log.info("微信支付获取签名失败:{}", e.getMessage(), e); - throw new RuntimeException(e.getMessage()); + signature = sign(message.getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException | IOException | InvalidKeyException | SignatureException e) { + throw new RuntimeException(e); } + return WeChatSignModel.builder() + .sign( + wechatPayConfig.getSchema()+" mchid=\""+wechatPayConfig.getMerchantId()+"\"," + + "nonce_str=\""+nonceStr+"\"," + + "timestamp=\""+timestamp+"\"," + + "serial_no=\""+wechatPayConfig.getSerialNo()+"\"," + + "signature=\""+signature+"\"" + ) + .timestamp(timestamp) + .nonceStr(nonceStr) + .build(); } - /** - * 构建签名明细 - * @param method 请求方式 - * @param url 请求rul - * @param timestamp 请求时间戳 - * @param nonceStr 随机字符串 - * @param body 请求体 - * @return 请求明细 - */ - private String buildMessage(String method, HttpUrl url, long timestamp, - String nonceStr, String body) { + private String sign(byte[] message) throws NoSuchAlgorithmException, IOException, InvalidKeyException, SignatureException { + Signature sign = Signature.getInstance(ALGORITHM_TYPE); + sign.initSign(wechatPayConfig.getPrivateKey()); + sign.update(message); + + return Base64.getEncoder().encodeToString(sign.sign()); + } + + private String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) { String canonicalUrl = url.encodedPath(); if (url.encodedQuery() != null) { canonicalUrl += "?" + url.encodedQuery(); } + return method + "\n" + canonicalUrl + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n"; } + } diff --git a/src/main/resources/wechat/wxpaykey.pem b/src/main/resources/wechat/wxpaykey.pem new file mode 100644 index 0000000..e69de29 diff --git a/src/test/java/com/muyu/wechat/pay/AppClientTest.java b/src/test/java/com/muyu/wechat/pay/AppClientTest.java new file mode 100644 index 0000000..552dd31 --- /dev/null +++ b/src/test/java/com/muyu/wechat/pay/AppClientTest.java @@ -0,0 +1,41 @@ +package com.muyu.wechat.pay; + +import com.muyu.WechatPayApplication; +import com.muyu.pay.wechat.clients.WeChatAppClient; +import com.muyu.pay.wechat.domain.model.WeChatPayAmountModel; +import com.muyu.pay.wechat.domain.req.WeChatPayAppReq; +import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author DongZl + * @description: 微信支付下单测试类 + * @Date 2023-8-21 下午 02:55 + */ +@Log4j2 +@SpringBootTest(classes = WechatPayApplication.class) +public class AppClientTest { + + /** + * 微信 native支付 + */ + @Autowired + private WeChatAppClient weChatAppClient; + + @Test + public void order(){ + String orderResult = weChatAppClient.order( + WeChatPayAppReq.builder() + .description("测试商品") + .outTradeNo("12341684615") + .notifyUrl("http://doc.qinmian.online") + .payAmountModel( + WeChatPayAmountModel.totalBuild(1) + ) + .build() + ); + log.info(orderResult); + } +} diff --git a/src/test/java/com/muyu/wechat/pay/NativeClientTest.java b/src/test/java/com/muyu/wechat/pay/NativeClientTest.java index a1475e6..389ea21 100644 --- a/src/test/java/com/muyu/wechat/pay/NativeClientTest.java +++ b/src/test/java/com/muyu/wechat/pay/NativeClientTest.java @@ -3,7 +3,7 @@ package com.muyu.wechat.pay; import com.muyu.WechatPayApplication; import com.muyu.pay.wechat.clients.WeChatNativeClient; import com.muyu.pay.wechat.domain.model.WeChatPayAmountModel; -import com.muyu.pay.wechat.domain.req.WeChatPayReqWeChatPay; +import com.muyu.pay.wechat.domain.req.WeChatPayNativeReq; import lombok.extern.log4j.Log4j2; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -27,7 +27,7 @@ public class NativeClientTest { @Test public void order(){ String orderResult = weChatNativeClient.order( - WeChatPayReqWeChatPay.builder() + WeChatPayNativeReq.builder() .description("测试商品") .outTradeNo("12341684615") .notifyUrl("http://doc.qinmian.online")