diff --git a/ruoyi-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/config/AliPayConfig.java b/ruoyi-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/config/AliPayConfig.java index 28d5422..f8c30b3 100644 --- a/ruoyi-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/config/AliPayConfig.java +++ b/ruoyi-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/config/AliPayConfig.java @@ -58,7 +58,7 @@ public class AliPayConfig { } @Bean - protected Config config() throws Exception { + protected Config alipayBaseConfig() throws Exception { // 设置参数(全局只需设置一次) Config config = new Config(); config.protocol = "https"; diff --git a/ruoyi-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/controller/AliPayController.java b/ruoyi-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/controller/AliPayController.java index 2339c27..e28a00d 100644 --- a/ruoyi-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/controller/AliPayController.java +++ b/ruoyi-pay/ruoyi-pay-alipay/src/main/java/com/ruoyi/pay/alipay/controller/AliPayController.java @@ -64,7 +64,6 @@ public class AliPayController { Map requestParams = request.getParameterMap(); for (String name : requestParams.keySet()) { params.put(name, request.getParameter(name)); - // System.out.println(name + " = " + request.getParameter(name)); } String orderNumber = params.get("out_trade_no"); diff --git a/ruoyi-pay/ruoyi-pay-sqb/src/main/java/com/ruoyi/pay/sqb/utils/HttpUtil.java b/ruoyi-pay/ruoyi-pay-sqb/src/main/java/com/ruoyi/pay/sqb/utils/HttpUtil.java index d5e580c..cbf4485 100644 --- a/ruoyi-pay/ruoyi-pay-sqb/src/main/java/com/ruoyi/pay/sqb/utils/HttpUtil.java +++ b/ruoyi-pay/ruoyi-pay-sqb/src/main/java/com/ruoyi/pay/sqb/utils/HttpUtil.java @@ -25,10 +25,10 @@ import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.util.EntityUtils; public class HttpUtil { - public static String httpPostWithoutException(String url, String string,String sign,String sn) { + public static String httpPostWithoutException(String url, String string, String sign, String sn) { String xmlRes = "{}"; try { - xmlRes = httpPost(url, string,sign,sn); + xmlRes = httpPost(url, string, sign, sn); } catch (UnrecoverableKeyException e) { } catch (NoSuchAlgorithmException e) { @@ -40,36 +40,33 @@ public class HttpUtil { } return xmlRes; } + /** * http POST 请求 - * @param url:请求地址 - * @param body: body实体字符串 - * @param sign:签名 - * @param sn: 序列号 + * + * @param url:请求地址 + * @param body: body实体字符串 + * @param sign:签名 + * @param sn: 序列号 * @return */ - public static String httpPost(String url, String body,String sign,String sn) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + public static String httpPost(String url, String body, String sign, String sn) + throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { String xmlRes = "{}"; HttpClient client = createSSLClientDefault(); HttpPost httpost = new HttpPost(url); try { - System.out.println("Request string: " + body); - //所有请求的body都需采用UTF-8编码 - StringEntity entity = new StringEntity(body,"UTF-8");// + // 所有请求的body都需采用UTF-8编码 + StringEntity entity = new StringEntity(body, "UTF-8");// entity.setContentType("application/json"); httpost.setEntity(entity); - - //支付平台所有的API仅支持JSON格式的请求调用,HTTP请求头Content-Type设为application/json - httpost.addHeader("Content-Type","application/json"); - - //支付平台所有的API调用都需要签名验证,签名首部: Authorization: sn + " " + sign - httpost.addHeader("Authorization",sn + " " + sign); - System.out.println("Authorization" + sn + " " + sign); + // 支付平台所有的API仅支持JSON格式的请求调用,HTTP请求头Content-Type设为application/json + httpost.addHeader("Content-Type", "application/json"); + // 支付平台所有的API调用都需要签名验证,签名首部: Authorization: sn + " " + sign + httpost.addHeader("Authorization", sn + " " + sign); HttpResponse response = client.execute(httpost); - - //所有响应也采用UTF-8编码 + // 所有响应也采用UTF-8编码 xmlRes = EntityUtils.toString(response.getEntity(), "UTF-8"); - System.out.println("Response string: " + xmlRes); } catch (ClientProtocolException e) { } catch (IOException e) { @@ -81,9 +78,9 @@ public class HttpUtil { public static CloseableHttpClient createSSLClientDefault() { try { SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { - //信任所有 + // 信任所有 public boolean isTrusted(X509Certificate[] chain, - String authType) throws CertificateException { + String authType) throws CertificateException { return true; } }).build(); @@ -99,23 +96,20 @@ public class HttpUtil { return HttpClients.createDefault(); } - public static String doGet(String url,String parameter) - { - String uriAPI =url+"?"+parameter ; //"http://XX?str=I+am+get+String"; - String result= ""; + public static String doGet(String url, String parameter) { + String uriAPI = url + "?" + parameter; // "http://XX?str=I+am+get+String"; + String result = ""; HttpClient client = createSSLClientDefault(); HttpGet httpRequst = new HttpGet(uriAPI); try { - HttpResponse httpResponse = client.execute(httpRequst);//其中HttpGet是HttpUriRequst的子类 - if(httpResponse.getStatusLine().getStatusCode() == 200) - { + HttpResponse httpResponse = client.execute(httpRequst);// 其中HttpGet是HttpUriRequst的子类 + if (httpResponse.getStatusLine().getStatusCode() == 200) { HttpEntity httpEntity = httpResponse.getEntity(); - result = EntityUtils.toString(httpEntity);//取出应答字符串 + result = EntityUtils.toString(httpEntity);// 取出应答字符串 // 一般来说都要删除多余的字符 - result.replaceAll("\r", "");//去掉返回结果中的"\r"字符,否则会在结果字符串后面显示一个小方格 - } - else + result.replaceAll("\r", "");// 去掉返回结果中的"\r"字符,否则会在结果字符串后面显示一个小方格 + } else httpRequst.abort(); } catch (ClientProtocolException e) { e.printStackTrace(); diff --git a/ruoyi-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/config/WxPayAppConfig.java b/ruoyi-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/config/WxPayAppConfig.java index 21829b6..fd19973 100644 --- a/ruoyi-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/config/WxPayAppConfig.java +++ b/ruoyi-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/config/WxPayAppConfig.java @@ -1,25 +1,36 @@ package com.ruoyi.pay.wx.config; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; -import lombok.Data; +import com.wechat.pay.java.core.RSAAutoCertificateConfig; +import com.wechat.pay.java.core.notification.NotificationParser; +import com.wechat.pay.java.service.payments.nativepay.NativePayService; /** * 配置我们自己的信息 * * @author ZlH */ -@Data @Configuration @ConfigurationProperties(prefix = "wechat") public class WxPayAppConfig { @Value("${pay.wechat.merchantId}") - private String wxchantId; + private String wxchantId; @Value("${pay.wechat.merchantSerialNumber}") private String wxchantSerialNumber; + @Value("${pay.wechat.apiV3Key}") private String wxapiV3Key; @Value("${pay.wechat.privateKeyPath}") @@ -29,4 +40,81 @@ public class WxPayAppConfig { @Value("${pay.wechat.notifyUrl}") private String notifyUrl; + @Bean + public RSAAutoCertificateConfig wxpayBaseConfig() throws Exception { + return new RSAAutoCertificateConfig.Builder() + .merchantId(getWxchantId()) + .privateKeyFromPath(getWxcertPath()) + .merchantSerialNumber(getWxchantSerialNumber()) + .apiV3Key(getWxapiV3Key()) + .build(); + } + + @Bean + public NativePayService nativePayService() throws Exception { + return new NativePayService.Builder().config(wxpayBaseConfig()).build(); + } + + @Bean + public NotificationParser notificationParser() throws Exception { + return new NotificationParser(wxpayBaseConfig()); + } + + @Autowired + private ApplicationContext applicationContext; + + public String getWxcertPath() throws Exception { + if (wxcertPath.startsWith("classpath:")) { + Resource resource = applicationContext.getResource(wxcertPath); + String tempFilePath = System.getProperty("java.io.tmpdir") + "/temp_wxcert.pem"; + try (InputStream inputStream = resource.getInputStream()) { + Files.copy(inputStream, Paths.get(tempFilePath), StandardCopyOption.REPLACE_EXISTING); + wxcertPath = tempFilePath; + } catch (Exception e) { + Files.deleteIfExists(Paths.get(tempFilePath)); + throw new RuntimeException("微信支付证书文件读取失败", e); + } + } + return wxcertPath; + } + + public String getWxchantId() { + return wxchantId; + } + + public void setWxchantId(String wxchantId) { + this.wxchantId = wxchantId; + } + + public String getWxchantSerialNumber() { + return wxchantSerialNumber; + } + + public void setWxchantSerialNumber(String wxchantSerialNumber) { + this.wxchantSerialNumber = wxchantSerialNumber; + } + + public String getWxapiV3Key() { + return wxapiV3Key; + } + + public void setWxapiV3Key(String wxapiV3Key) { + this.wxapiV3Key = wxapiV3Key; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getNotifyUrl() { + return notifyUrl; + } + + public void setNotifyUrl(String notifyUrl) { + this.notifyUrl = notifyUrl; + } } diff --git a/ruoyi-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/controller/WxAppPayController.java b/ruoyi-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/controller/WxAppPayController.java index efc553a..cc66df0 100644 --- a/ruoyi-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/controller/WxAppPayController.java +++ b/ruoyi-pay/ruoyi-pay-wx/src/main/java/com/ruoyi/pay/wx/controller/WxAppPayController.java @@ -1,8 +1,13 @@ package com.ruoyi.pay.wx.controller; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StreamUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -12,18 +17,26 @@ import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.pay.domain.PayOrder; import com.ruoyi.pay.service.IPayOrderService; import com.ruoyi.pay.wx.config.WxPayAppConfig; -import com.wechat.pay.java.core.Config; -import com.wechat.pay.java.core.RSAAutoCertificateConfig; +import com.wechat.pay.java.core.exception.ValidationException; +import com.wechat.pay.java.core.notification.NotificationParser; +import com.wechat.pay.java.core.notification.RequestParam; import com.wechat.pay.java.service.payments.nativepay.NativePayService; import com.wechat.pay.java.service.payments.nativepay.model.Amount; import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest; import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse; +import com.wechat.pay.java.service.wexinpayscoreparking.model.Transaction; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; /** * @author zlh */ +@Slf4j @RestController @RequestMapping("/wxPay") public class WxAppPayController extends BaseController { @@ -31,22 +44,18 @@ public class WxAppPayController extends BaseController { private WxPayAppConfig wxPayAppConfig; @Autowired private IPayOrderService payOrderService; - @Anonymous + + @Autowired + private NativePayService nativePayService; + @Autowired + private NotificationParser notificationParser; + @Operation(summary = "微信支付") + @Parameters({ + @Parameter(name = "orderNumber", description = "订单号", required = true) + }) @GetMapping("/pay/{orderNumber}") - public AjaxResult pay(@PathVariable String orderNumber) { - // 使用自动更新平台证书的RSA配置 - // 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错 - Config config = - new RSAAutoCertificateConfig.Builder() - .merchantId(wxPayAppConfig.getWxchantId()) - .privateKeyFromPath(wxPayAppConfig.getWxcertPath()) - .merchantSerialNumber(wxPayAppConfig.getWxchantSerialNumber()) - .apiV3Key(wxPayAppConfig.getWxapiV3Key()) - .build(); - // 构建service - NativePayService service = new NativePayService.Builder().config(config).build(); - // request.setXxx(val)设置所需参数,具体参数可见Request定义 + public AjaxResult pay(@PathVariable String orderNumber) throws Exception { PayOrder aliPay = payOrderService.selectPayOrderByOrderNumber(orderNumber); String amountStr = aliPay.getTotalAmount(); double amountDouble = Double.parseDouble(amountStr); @@ -60,48 +69,44 @@ public class WxAppPayController extends BaseController { request.setDescription(aliPay.getOrderContent()); request.setNotifyUrl(wxPayAppConfig.getNotifyUrl()); request.setOutTradeNo(aliPay.getOrderNumber()); - // 调用下单方法,得到应答 - PrepayResponse response = service.prepay(request); - // 使用微信扫描 code_url 对应的二维码,即可体验Native支付 + PrepayResponse response = nativePayService.prepay(request); return AjaxResult.success(response.getCodeUrl()); } -// @Anonymous -// @Operation(summary = "微信支付查询订单") -// @GetMapping("/notify") -// public AjaxResult WxPayList() { -// System.out.println("=========支付宝异步回调========"); -// // 构造 RequestParam -// RequestParam requestParam = new RequestParam.Builder() -// .serialNumber("wechatPaySerial") -// .nonce("wechatpayNonce") -// .signature("wechatSignature") -// .timestamp("wechatTimestamp") -// .body("requestBody") -// .build(); -// -//// 如果已经初始化了 RSAAutoCertificateConfig,可直接使用 -//// 没有的话,则构造一个 -// NotificationConfig config = new RSAAutoCertificateConfig.Builder() -// .merchantId(merchantId) -// .privateKeyFromPath(privateKeyPath) -// .merchantSerialNumber(merchantSerialNumber) -// .apiV3Key(apiV3Key) -// .build(); -// -//// 初始化 NotificationParser -// NotificationParser parser = new NotificationParser(config); -// -// try { -// // 以支付通知回调为例,验签、解密并转换成 Transaction -// Transaction transaction = parser.parse(requestParam, Transaction.class); -// } catch (ValidationException e) { -// // 签名验证失败,返回 401 UNAUTHORIZED 状态码 -// logger.error("sign verification failed", e); -// return AjaxResult.success(HttpStatus.UNAUTHORIZED); -// } -//// 处理成功,返回 200 OK 状态码 -// return AjaxResult.success(HttpStatus.OK); -// } + @Anonymous + @Operation(summary = "微信支付查询订单") + @PostMapping("/notify") + public AjaxResult WxPayList(HttpServletRequest servletRequest, HttpServletResponse response) + throws Exception { + log.info("=========微信异步回调========"); + + String timeStamp = servletRequest.getHeader("Wechatpay-Timestamp"); + String nonce = servletRequest.getHeader("Wechatpay-Nonce"); + String signature = servletRequest.getHeader("Wechatpay-Signature"); + String certSn = servletRequest.getHeader("Wechatpay-Serial"); + + try { + String requestBody = StreamUtils.copyToString(servletRequest.getInputStream(), StandardCharsets.UTF_8); + RequestParam requestParam = new RequestParam.Builder() + .serialNumber(certSn) + .nonce(nonce) + .signature(signature) + .timestamp(timeStamp) + .body(requestBody) + .build(); + + try { + Transaction transaction = notificationParser.parse(requestParam, Transaction.class); + String orderNumber = transaction.getOutTradeNo(); + payOrderService.updateStatus(orderNumber, "已支付"); + return success(); + } catch (ValidationException e) { + return error(); + } + } catch (IOException e) { + log.error("Error reading request body", e); + throw new RuntimeException("Error reading request body", e); + } + } } \ No newline at end of file