From 43fa1e70c16f62615b415cb0f0313517c2672bc9 Mon Sep 17 00:00:00 2001
From: Cool <747682928@qq.com>
Date: Tue, 3 Sep 2024 23:13:29 +0800
Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=8A=9F=E8=83=BD=EF=BC=88?=
=?UTF-8?q?=E6=9C=AA=E5=AE=9E=E7=8E=B0=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 18 +++
.../wxappserver/config/MyWxPayConfig.java | 45 ++++++
.../controller/TestController.java | 52 +++++++
.../properties/WeChatProperties.java | 4 +-
.../bigdata/wxappserver/utils/SignV3Util.java | 138 ++++++++++++++++++
.../wxappserver/utils/WeChatPayUtil.java | 18 +--
src/main/resources/application.yml | 10 +-
.../WxappServerApplicationTests.java | 46 ++++++
8 files changed, 314 insertions(+), 17 deletions(-)
create mode 100644 src/main/java/com/bigdata/wxappserver/config/MyWxPayConfig.java
create mode 100644 src/main/java/com/bigdata/wxappserver/controller/TestController.java
create mode 100644 src/main/java/com/bigdata/wxappserver/utils/SignV3Util.java
diff --git a/pom.xml b/pom.xml
index 4dc1cdb..ca57eb3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -68,6 +68,24 @@
wechatpay-apache-httpclient
0.4.8
+
+
+ com.squareup.okhttp3
+ okhttp
+ 3.5.0
+
+
+ com.github.wechatpay-apiv3
+ wechatpay-java
+ 0.2.14
+
+
+
+ com.github.binarywang
+ weixin-java-pay
+ 4.1.0
+
+
commons-lang
commons-lang
diff --git a/src/main/java/com/bigdata/wxappserver/config/MyWxPayConfig.java b/src/main/java/com/bigdata/wxappserver/config/MyWxPayConfig.java
new file mode 100644
index 0000000..e7ee012
--- /dev/null
+++ b/src/main/java/com/bigdata/wxappserver/config/MyWxPayConfig.java
@@ -0,0 +1,45 @@
+package com.bigdata.wxappserver.config;
+
+import com.bigdata.wxappserver.properties.WeChatProperties;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Created with IntelliJ IDEA.
+ *
+ * @Author: Cool
+ * @Date: 2024/09/03/22:19
+ * @Description:
+ */
+
+@Configuration
+@ConditionalOnClass(WxPayService.class)
+public class MyWxPayConfig {
+
+ @Autowired
+ private WeChatProperties properties;
+
+ @Bean
+ @ConditionalOnMissingBean
+ public WxPayService wxService() {
+ WxPayConfig payConfig = new WxPayConfig();
+ payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
+ payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
+ payConfig.setMchKey(StringUtils.trimToNull(this.properties.getSecret()));
+ payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getPrivateKeyFilePath()));
+
+ // 可以指定是否使用沙箱环境
+ payConfig.setUseSandboxEnv(false);
+
+ WxPayService wxPayService = new WxPayServiceImpl();
+ wxPayService.setConfig(payConfig);
+ return wxPayService;
+ }
+}
diff --git a/src/main/java/com/bigdata/wxappserver/controller/TestController.java b/src/main/java/com/bigdata/wxappserver/controller/TestController.java
new file mode 100644
index 0000000..21372a9
--- /dev/null
+++ b/src/main/java/com/bigdata/wxappserver/controller/TestController.java
@@ -0,0 +1,52 @@
+package com.bigdata.wxappserver.controller;
+
+import com.bigdata.wxappserver.result.Result;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
+import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryResult;
+import com.github.binarywang.wxpay.service.WxPayService;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Created with IntelliJ IDEA.
+ *
+ * @Author: Cool
+ * @Date: 2024/09/03/22:25
+ * @Description:
+ */
+@RestController
+@Slf4j
+public class TestController {
+ @Autowired
+ private WxPayService wxService;
+
+
+ @PostMapping("/unifiedOrder")
+ public Result unifiedOrder(String outTradeNo) throws Exception
+ {
+ WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest = new WxPayUnifiedOrderRequest();
+ wxPayUnifiedOrderRequest.setBody("测试");
+ wxPayUnifiedOrderRequest.setOutTradeNo(outTradeNo);
+ wxPayUnifiedOrderRequest.setTotalFee(50);
+ wxPayUnifiedOrderRequest.setSpbillCreateIp("127.0.0.1");
+ wxPayUnifiedOrderRequest.setNotifyUrl("payCallBack");
+ wxPayUnifiedOrderRequest.setTradeType("JSAPI");
+ wxPayUnifiedOrderRequest.setOpenid("oU5Ta5f9Vx6f-***********");
+ wxPayUnifiedOrderRequest.setSignType("MD5");
+
+ return Result.success(wxService.unifiedOrder(wxPayUnifiedOrderRequest));
+ }
+
+ @PostMapping("/queryOrder")
+ public Result queryOrder(String outTradeNo) throws Exception
+ {
+ WxPayOrderQueryResult wxPayOrderQueryResult = wxService.queryOrder(null, outTradeNo);
+ log.info(wxPayOrderQueryResult.toString());
+ return Result.success(wxPayOrderQueryResult);
+ }
+
+
+}
diff --git a/src/main/java/com/bigdata/wxappserver/properties/WeChatProperties.java b/src/main/java/com/bigdata/wxappserver/properties/WeChatProperties.java
index 482ee59..9256bf6 100644
--- a/src/main/java/com/bigdata/wxappserver/properties/WeChatProperties.java
+++ b/src/main/java/com/bigdata/wxappserver/properties/WeChatProperties.java
@@ -9,9 +9,9 @@ import org.springframework.stereotype.Component;
@Data
public class WeChatProperties {
- private String appid; //小程序的appid
+ private String appId; //小程序的appid
private String secret; //小程序的秘钥
- private String mchid; //商户号
+ private String mchId; //商户号
private String mchSerialNo; //商户API证书的证书序列号
private String privateKeyFilePath; //商户私钥文件
private String apiV3Key; //证书解密的密钥
diff --git a/src/main/java/com/bigdata/wxappserver/utils/SignV3Util.java b/src/main/java/com/bigdata/wxappserver/utils/SignV3Util.java
new file mode 100644
index 0000000..c3d921a
--- /dev/null
+++ b/src/main/java/com/bigdata/wxappserver/utils/SignV3Util.java
@@ -0,0 +1,138 @@
+package com.bigdata.wxappserver.utils;
+
+import okhttp3.HttpUrl;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import java.io.UnsupportedEncodingException;
+import java.security.*;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Random;
+
+/**
+ * Created with IntelliJ IDEA.
+ *
+ * @Author: Cool
+ * @Date: 2024/09/03/20:34
+ * @Description:
+ */
+@Component
+public class SignV3Util{
+ //V3主商户ID
+ private static String merchantId;
+ //微信商户平台APIv3证书序列号
+ private static String certificateSerialNo;
+ //私钥(不要把私钥文件暴露在公共场合,如上传到Github,写在客户端代码等。)
+ private static String privateKey;
+
+ //配置文件配置好主商户号
+ @Value("${dx.wechat.mchid}")
+ public void setMerchantId(String merchantId) {
+ SignV3Util.merchantId = merchantId;
+ }
+
+ //配置文件配置好序列号
+ @Value("${dx.wechat.mchSerialNo}")
+ public void setCertificateSerialNo(String certificateSerialNo) {
+ SignV3Util.certificateSerialNo = certificateSerialNo;
+ }
+
+ //配置文件配置好私钥
+ @Value("${dx.wechat.apiV3Key}")
+ public void setPrivateKey(String privateKey) {
+ SignV3Util.privateKey = privateKey;
+ }
+
+ /**
+ * 使用方法
+ *
+ * @param method 请求方法
+ * @param url 请求url
+ * @param body 请求内容
+ * @return
+ */
+ public static HashMap getSignMap(String method, String url, String body) throws InvalidKeySpecException, NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException, SignatureException {
+ String authorization = getSign(method, url, body);
+
+ HashMap headsMap = new HashMap<>();
+ headsMap.put("Authorization", authorization);
+ headsMap.put("Content-Type", "application/json");
+ headsMap.put("Accept", "application/json");
+
+ return headsMap;
+ }
+
+ public static String getSign(String method, String url, String body) throws NoSuchAlgorithmException, SignatureException, InvalidKeySpecException, InvalidKeyException, UnsupportedEncodingException {
+ return "WECHATPAY2-SHA256-RSA2048 " + getToken(method, HttpUrl.parse(url), body);
+ }
+
+ public static String getToken(String method, HttpUrl url, String body) throws UnsupportedEncodingException, SignatureException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
+ String nonceStr = nonceString();
+ long timestamp = System.currentTimeMillis() / 1000;
+ String message = buildMessage(method, url, timestamp, nonceStr, body);
+ String signature = sign(message.getBytes("utf-8"));
+ return "mchid=\"" + merchantId + "\","
+ + "nonce_str=\"" + nonceStr + "\","
+ + "timestamp=\"" + timestamp + "\","
+ + "serial_no=\"" + certificateSerialNo + "\","
+ + "signature=\"" + signature + "\"";
+ }
+
+ public static String sign(byte[] message) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
+ Signature sign = Signature.getInstance("SHA256withRSA");
+ sign.initSign(getPKCS8PrivateKey(privateKey));
+ sign.update(message);
+
+ return Base64.getEncoder().encodeToString(sign.sign());
+ }
+
+ public static 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";
+ }
+
+
+ private static PrivateKey getPKCS8PrivateKey(String strPk) throws NoSuchAlgorithmException, InvalidKeySpecException {
+ String realPK = strPk.replaceAll("-----END PRIVATE KEY-----", "")
+ .replaceAll("-----BEGIN PRIVATE KEY-----", "")
+ .replaceAll("\n", "");
+
+ byte[] b1 = Base64.getDecoder().decode(realPK);
+
+ PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(b1);
+
+ KeyFactory kf = KeyFactory.getInstance("RSA");
+
+ return kf.generatePrivate(spec);
+ }
+
+ public static String nonceString() {
+
+ String currTime = String.format("%d", (long) System.currentTimeMillis() / 1000);
+
+ String strTime = currTime.substring(8, currTime.length());
+
+ Random random = new Random();
+ int num = (int) (random.nextDouble() * (1000000 - 100000) + 100000);
+ String code = String.format("%06d", num);
+
+ String nonce_str = currTime.substring(2) + code;
+ return nonce_str;
+
+ }
+}
+
+
diff --git a/src/main/java/com/bigdata/wxappserver/utils/WeChatPayUtil.java b/src/main/java/com/bigdata/wxappserver/utils/WeChatPayUtil.java
index 9e90ce5..c1d4991 100644
--- a/src/main/java/com/bigdata/wxappserver/utils/WeChatPayUtil.java
+++ b/src/main/java/com/bigdata/wxappserver/utils/WeChatPayUtil.java
@@ -24,10 +24,7 @@ import java.math.BigDecimal;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.List;
+import java.util.*;
/**
* 微信支付工具类
@@ -36,7 +33,7 @@ import java.util.List;
public class WeChatPayUtil {
//微信支付下单接口地址
- public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
+ public static final String PAY_API = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//申请退款接口地址
public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
@@ -60,7 +57,7 @@ public class WeChatPayUtil {
List wechatPayCertificates = Arrays.asList(x509Certificate);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
- .withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey)
+ .withMerchant(weChatProperties.getMchId(), weChatProperties.getMchSerialNo(), merchantPrivateKey)
.withWechatPay(wechatPayCertificates);
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
@@ -133,10 +130,11 @@ public class WeChatPayUtil {
*/
private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
JSONObject jsonObject = new JSONObject();
- jsonObject.put("appid", weChatProperties.getAppid());
- jsonObject.put("mchid", weChatProperties.getMchid());
+ jsonObject.put("appid", weChatProperties.getAppId());
+ jsonObject.put("mchid", weChatProperties.getMchId());
jsonObject.put("description", description);
jsonObject.put("out_trade_no", orderNum);
+ jsonObject.put("nonce_str", UUID.randomUUID().toString());
jsonObject.put("notify_url", weChatProperties.getNotifyUrl());
JSONObject amount = new JSONObject();
@@ -151,7 +149,7 @@ public class WeChatPayUtil {
jsonObject.put("payer", payer);
String body = jsonObject.toJSONString();
- return post(JSAPI, body);
+ return post(PAY_API, body);
}
/**
@@ -175,7 +173,7 @@ public class WeChatPayUtil {
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = RandomStringUtils.randomNumeric(32);
ArrayList