diff --git a/luoo_user/pom.xml b/luoo_user/pom.xml
index 7c413bc..dc0abb7 100644
--- a/luoo_user/pom.xml
+++ b/luoo_user/pom.xml
@@ -171,6 +171,12 @@
3.9.1
+
+ com.github.javen205
+ IJPay-WxPay
+ 2.9.6
+
+
app
diff --git a/luoo_user/src/main/java/com/luoo/user/UserApplication.java b/luoo_user/src/main/java/com/luoo/user/UserApplication.java
index 402ffdb..6c3cb35 100644
--- a/luoo_user/src/main/java/com/luoo/user/UserApplication.java
+++ b/luoo_user/src/main/java/com/luoo/user/UserApplication.java
@@ -2,6 +2,8 @@ package com.luoo.user;
import com.luoo.user.listener.JPAEntityListener;
import com.spring4all.mongodb.EnableMongoPlus;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -72,4 +74,9 @@ public class UserApplication {
return new RedisLockUtil();
}
+ @Bean
+ public CloseableHttpClient httpClient() {
+ return HttpClients.createDefault();
+ }
+
}
diff --git a/luoo_user/src/main/java/com/luoo/user/config/WxPayConfig.java b/luoo_user/src/main/java/com/luoo/user/config/WxPayConfig.java
new file mode 100644
index 0000000..c987f77
--- /dev/null
+++ b/luoo_user/src/main/java/com/luoo/user/config/WxPayConfig.java
@@ -0,0 +1,55 @@
+package com.luoo.user.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "wx.pay")
+public class WxPayConfig {
+ /**
+ * appId
+ */
+ private String appId;
+
+ /**
+ * 商户号
+ */
+ private String mchId;
+
+ /**
+ * 商户证书序列号
+ */
+ private String mchSerialNo;
+
+ /**
+ * apiv3密钥
+ */
+ private String apiV3Key;
+
+ /**
+ * 支付回调地址
+ */
+ private String notifyUrl;
+
+ /**
+ * 微信支付请求url
+ */
+// private String wxJsapiUrl;
+
+ /**
+ * 私钥路径
+ */
+ private String privateKeyPath;
+
+ /**
+ * 商户证书路径
+ */
+ private String privateCertPath;
+
+ /**
+ * 微信平台证书路径
+ */
+ private String platformCertPath;
+}
\ No newline at end of file
diff --git a/luoo_user/src/main/java/com/luoo/user/controller/WxPayController.java b/luoo_user/src/main/java/com/luoo/user/controller/WxPayController.java
new file mode 100644
index 0000000..b7f628f
--- /dev/null
+++ b/luoo_user/src/main/java/com/luoo/user/controller/WxPayController.java
@@ -0,0 +1,143 @@
+package com.luoo.user.controller;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.ContentType;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.ijpay.core.IJPayHttpResponse;
+import com.ijpay.core.enums.RequestMethodEnum;
+import com.ijpay.core.kit.HttpKit;
+import com.ijpay.core.kit.PayKit;
+import com.ijpay.core.kit.WxPayKit;
+import com.ijpay.core.utils.DateTimeZoneUtil;
+import com.ijpay.wxpay.WxPayApi;
+import com.ijpay.wxpay.enums.WxDomainEnum;
+import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
+import com.ijpay.wxpay.model.UnifiedOrderModel;
+import com.luoo.user.config.WxPayConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @program: luoo_parent
+ * @description:
+ * @author: yawei.huang
+ * @create: 2024-11-12 11:59
+ **/
+@RestController
+@CrossOrigin
+@RequestMapping("/wx/pay")
+@Slf4j
+public class WxPayController {
+
+ @Resource
+ private WxPayConfig wxPayConfig;
+
+ //支付
+ @RequestMapping("/app")
+ @ResponseBody
+ public String jsApiPay() {
+ try {
+ String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
+ UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
+ .setAppid(wxPayConfig.getAppId())
+ .setMch_id(wxPayConfig.getMchId())
+ .setOut_trade_no(PayKit.generateStr())
+ .setTime_expire(timeExpire)
+ .setAttach("微信系开发脚手架 https://gitee.com/javen205/TNWX")
+ .setNotify_url(wxPayConfig.getNotifyUrl())
+ .setTotal_fee("1");
+
+ Map params = new HashMap<>();
+ params.put("appid", wxPayConfig.getAppId());
+ params.put("mchid", wxPayConfig.getMchId());
+ params.put("description", "雀乐会员充值");
+ params.put("out_trade_no", PayKit.generateStr());
+ params.put("time_expire", timeExpire);
+ params.put("notify_url", "https://www.baidu.com");
+ params.put("amount", new HashMap() {{
+ put("total", 1);
+ put("currency", "CNY");
+ }});
+
+ log.info("统一下单参数 {}", JSONUtil.toJsonStr(params));
+ IJPayHttpResponse response = WxPayApi.v3(
+ RequestMethodEnum.POST,
+ WxDomainEnum.CHINA.toString(),
+ BasePayApiEnum.APP_PAY.toString(),
+ wxPayConfig.getMchId(),
+ wxPayConfig.getMchSerialNo(),
+ null,
+ wxPayConfig.getPrivateKeyPath(),
+ JSONUtil.toJsonStr(params)
+ );
+
+ log.info("统一下单响应 {}", response);
+ // 根据证书序列号查询对应的证书来验证签名结果
+ boolean verifySignature = WxPayKit.verifySignature(response, wxPayConfig.getPlatformCertPath());
+ log.info("verifySignature: {}", verifySignature);
+ if (response.getStatus() == 200 && verifySignature) {
+ String body = response.getBody();
+ JSONObject jsonObject = JSONUtil.parseObj(body);
+ String prepayId = jsonObject.getStr("prepay_id");
+ Map map = WxPayKit.jsApiCreateSign(wxPayConfig.getAppId(), prepayId, wxPayConfig.getPrivateKeyPath());
+ log.info("唤起支付参数:{}", map);
+ return JSONUtil.toJsonStr(map);
+ }
+ return JSONUtil.toJsonStr(response);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return e.getMessage();
+ }
+ }
+
+ @RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
+ @ResponseBody
+ public void payNotify(HttpServletRequest request, HttpServletResponse response) {
+ Map map = new HashMap<>(12);
+ try {
+ String timestamp = request.getHeader("Wechatpay-Timestamp");
+ String nonce = request.getHeader("Wechatpay-Nonce");
+ String serialNo = request.getHeader("Wechatpay-Serial");
+ String signature = request.getHeader("Wechatpay-Signature");
+
+ log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
+ String result = HttpKit.readData(request);
+ log.info("支付通知密文 {}", result);
+
+ // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
+ String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
+ wxPayConfig.getApiV3Key(), wxPayConfig.getPlatformCertPath());
+
+ log.info("支付通知明文 {}", plainText);
+
+ if (StrUtil.isNotEmpty(plainText)) {
+ response.setStatus(200);
+ map.put("code", "SUCCESS");
+ map.put("message", "SUCCESS");
+ } else {
+ response.setStatus(500);
+ map.put("code", "ERROR");
+ map.put("message", "签名错误");
+ }
+ response.setHeader("Content-type", ContentType.JSON.toString());
+ response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
+ response.flushBuffer();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+
+}
diff --git a/luoo_user/src/main/java/com/luoo/user/service/WxPayService.java b/luoo_user/src/main/java/com/luoo/user/service/WxPayService.java
new file mode 100644
index 0000000..e2eb74f
--- /dev/null
+++ b/luoo_user/src/main/java/com/luoo/user/service/WxPayService.java
@@ -0,0 +1,105 @@
+package com.luoo.user.service;
+
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.ijpay.core.IJPayHttpResponse;
+import com.ijpay.core.enums.RequestMethodEnum;
+import com.ijpay.core.kit.AesUtil;
+import com.ijpay.core.kit.PayKit;
+import com.ijpay.core.kit.WxPayKit;
+import com.ijpay.wxpay.WxPayApi;
+import com.ijpay.wxpay.enums.WxDomainEnum;
+import com.ijpay.wxpay.enums.v3.OtherApiEnum;
+import com.luoo.user.config.WxPayConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.ByteArrayInputStream;
+import java.io.FileWriter;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.X509Certificate;
+
+/**
+ * @program: luoo_parent
+ * @description:
+ * @author: yawei.huang
+ * @create: 2024-11-12 12:00
+ **/
+@Slf4j
+@Service
+public class WxPayService {
+
+ @Resource
+ private WxPayConfig wxPayConfig;
+
+ public void v3Get() {
+ // 获取平台证书列表
+ try {
+ IJPayHttpResponse response = WxPayApi.v3(
+ RequestMethodEnum.GET,
+ WxDomainEnum.CHINA.toString(),
+ OtherApiEnum.GET_CERTIFICATES.toString(),
+ wxPayConfig.getMchId(),
+ wxPayConfig.getMchSerialNo(),
+ null,
+ wxPayConfig.getPrivateKeyPath(),
+ ""
+ );
+
+ String timestamp = response.getHeader("Wechatpay-Timestamp");
+ String nonceStr = response.getHeader("Wechatpay-Nonce");
+ String serialNumber = response.getHeader("Wechatpay-Serial");
+ String signature = response.getHeader("Wechatpay-Signature");
+
+ String body = response.getBody();
+ int status = response.getStatus();
+
+ log.info("serialNumber: {}", serialNumber);
+ log.info("status: {}", status);
+ log.info("body: {}", body);
+ if (status == 200) {
+ JSONObject jsonObject = JSONUtil.parseObj(body);
+ JSONArray dataArray = jsonObject.getJSONArray("data");
+ // 默认认为只有一个平台证书
+ JSONObject encryptObject = dataArray.getJSONObject(0);
+ JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
+ String associatedData = encryptCertificate.getStr("associated_data");
+ String cipherText = encryptCertificate.getStr("ciphertext");
+ String nonce = encryptCertificate.getStr("nonce");
+ String serialNo = encryptObject.getStr("serial_no");
+ final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, wxPayConfig.getPlatformCertPath());
+ log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
+ }
+ // 根据证书序列号查询对应的证书来验证签名结果
+ boolean verifySignature = WxPayKit.verifySignature(response, wxPayConfig.getPlatformCertPath());
+ System.out.println("verifySignature:" + verifySignature);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private String savePlatformCert(String associatedData, String nonce, String cipherText, String certPath) {
+ try {
+ AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
+ // 平台证书密文解密
+ // encrypt_certificate 中的 associated_data nonce ciphertext
+ String publicKey = aesUtil.decryptToString(
+ associatedData.getBytes(StandardCharsets.UTF_8),
+ nonce.getBytes(StandardCharsets.UTF_8),
+ cipherText
+ );
+ // 保存证书
+ FileWriter writer = new FileWriter(certPath);
+ writer.write(publicKey);
+ writer.close();
+ // 获取平台证书序列号
+ X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
+ return certificate.getSerialNumber().toString(16).toUpperCase();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return e.getMessage();
+ }
+ }
+}
diff --git a/luoo_user/src/main/resources/bootstrap.yml b/luoo_user/src/main/resources/bootstrap.yml
index a6cd465..3888948 100644
--- a/luoo_user/src/main/resources/bootstrap.yml
+++ b/luoo_user/src/main/resources/bootstrap.yml
@@ -35,4 +35,16 @@ mybatis-plus:
pagehelper:
helperDialect: mysql
supportMethodsArguments: true
- params: count=countSql
\ No newline at end of file
+ params: count=countSql
+
+wx:
+ pay:
+ appId: wxae6fb76efa147314
+ mchId: 1676389229
+ mchSerialNo: 541E42E46254E026DE1E1D13602865CE049866C2
+ apiV3Key: Indiecn2024181189288661529541569
+ notifyUri: https://beta.indie.cn/user/wx/pay/payNotify
+ privateKeyPath: D:\apiclient_key.pem
+ privateCertPath: D:\apiclient_cert.pem
+ platformCertPath: D:\apiclient_cert.pem
+