From a92790886a139b8b141f15a840def9632b5e81a1 Mon Sep 17 00:00:00 2001 From: huangyw <1207046171@qq.com> Date: Tue, 12 Nov 2024 12:47:22 +0800 Subject: [PATCH] =?UTF-8?q?release:=20=E4=BD=BF=E7=94=A8IJPay=E9=9B=86?= =?UTF-8?q?=E6=88=90=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- luoo_user/pom.xml | 6 + .../java/com/luoo/user/UserApplication.java | 7 + .../com/luoo/user/config/WxPayConfig.java | 55 +++++++ .../luoo/user/controller/WxPayController.java | 143 ++++++++++++++++++ .../com/luoo/user/service/WxPayService.java | 105 +++++++++++++ luoo_user/src/main/resources/bootstrap.yml | 14 +- 6 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 luoo_user/src/main/java/com/luoo/user/config/WxPayConfig.java create mode 100644 luoo_user/src/main/java/com/luoo/user/controller/WxPayController.java create mode 100644 luoo_user/src/main/java/com/luoo/user/service/WxPayService.java 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 +