From 30589acd69d0aa60ef2d7e95681dc41c92fd2a18 Mon Sep 17 00:00:00 2001 From: menxipeng Date: Tue, 25 Nov 2025 23:32:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=E9=83=A8?= =?UTF-8?q?=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/back/HandleAlipayNotify.java | 83 +++++++++ .../back/WechatPayNotifyController.java | 175 +++++++++++------- .../client/ClientOrderInfoController.java | 6 + .../framework/config/SecurityConfig.java | 10 +- ruoyi-system/pom.xml | 13 ++ .../ruoyi/system/mapper/ProductMapper.java | 2 + .../system/service/IOrderInfoService.java | 4 + .../service/impl/OrderInfoServiceImpl.java | 124 +++++++++---- .../com/ruoyi/system/util/AliPayUtil.java | 60 ++++++ .../com/ruoyi/system/util/WeChatPayUtil.java | 72 +++++-- .../resources/mapper/system/ProductMapper.xml | 4 + 11 files changed, 434 insertions(+), 119 deletions(-) create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/back/HandleAlipayNotify.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/util/AliPayUtil.java diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/back/HandleAlipayNotify.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/back/HandleAlipayNotify.java new file mode 100644 index 0000000..3e219ba --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/back/HandleAlipayNotify.java @@ -0,0 +1,83 @@ +package com.ruoyi.web.controller.back; + +import com.alipay.easysdk.factory.Factory; +import com.ruoyi.system.service.IOrderInfoService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + +/** + * 描述: + * + * @author MXP by 2025/11/25 + */ +@RestController +@RequestMapping("/call/back") +public class HandleAlipayNotify { + + private static final Logger log = LoggerFactory.getLogger(HandleAlipayNotify.class); + + @Autowired + private IOrderInfoService orderService; + + @PostMapping("/alipay") + public String handleAlipayNotify(HttpServletRequest request) { + try { + // 1. 将异步通知的参数转换为Map + Map params = new HashMap<>(); + Map requestParams = request.getParameterMap(); + log.info("支付宝异步通知参数: {}", requestParams); + for (String name : requestParams.keySet()) { + String[] values = requestParams.get(name); + String valueStr = ""; + for (int i = 0; i < values.length; i++) { + valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; + } + params.put(name, valueStr); + } + log.info("支付宝异步通知处理后参数: {}", params); + // 2. 验证签名(非常重要!防止伪造通知) + // 使用SDK验证签名 + boolean signVerified = Factory + .Payment + .Common() + .verifyNotify(params); + + if (signVerified) { + // 签名验证成功,说明通知是支付宝发的 + + // 3. 验证商户APP_ID是否匹配 + String appId = params.get("app_id"); + if (!"<你的APPID>".equals(appId)) { + return "failure"; + } + + // 4. 处理业务逻辑 + String tradeStatus = params.get("trade_status"); + String outTradeNo = params.get("out_trade_no"); // 你的订单号 + String tradeNo = params.get("trade_no"); // 支付宝的交易号 + + if ("TRADE_SUCCESS".equals(tradeStatus)) { + // 支付成功! + orderService.processPaymentAliResult(outTradeNo, tradeNo); + return "success"; + } + } else { + // 签名验证失败,记录日志,可能是恶意请求 + return "failure"; + } + } catch (Exception e) { + e.printStackTrace(); + } + // 处理失败,支付宝会稍后重发通知 + return "failure"; + } + +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/back/WechatPayNotifyController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/back/WechatPayNotifyController.java index 74e9845..fa64247 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/back/WechatPayNotifyController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/back/WechatPayNotifyController.java @@ -1,16 +1,30 @@ package com.ruoyi.web.controller.back; -import com.ruoyi.common.core.domain.entity.OrderInfo; -import com.ruoyi.common.enums.OrderStatus; +import cn.hutool.json.JSONUtil; +import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.service.IOrderInfoService; +import com.ruoyi.system.util.WeChatPayUtil; +import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier; +import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; +import com.wechat.pay.contrib.apache.httpclient.auth.Verifier; +import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials; +import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; +import com.wechat.pay.java.core.notification.NotificationConfig; import com.wechat.pay.java.core.notification.NotificationParser; import com.wechat.pay.java.core.notification.RequestParam; import com.wechat.pay.java.service.payments.model.Transaction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.xml.bind.ValidationException; +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; import java.util.HashMap; import java.util.Map; @@ -18,93 +32,120 @@ import java.util.Map; @RequestMapping("/call/back") public class WechatPayNotifyController { + private static final Logger log = LoggerFactory.getLogger(WechatPayNotifyController.class); + @Autowired private IOrderInfoService orderService; - - - /** * 微信支付结果回调接口 * POST /api/wechat/pay/notify */ @PostMapping("/wechat") - public Map handleWechatPayNotify(HttpServletRequest request, - HttpServletResponse response) { + public Map handleWechatPayNotify(HttpServletRequest request, HttpServletResponse response) { Map result = new HashMap<>(); - try { - System.out.println("收到微信支付回调通知"); + // 1. 读取请求体 + String requestBody = getRequestBody(request); + log.info("微信支付回调参数: {}", requestBody); + if (StringUtils.isEmpty(requestBody)) { + result.put("code", "FAIL"); + result.put("message", "请求体为空"); + return result; + } - // 1. 构建回调参数 - RequestParam requestParam = buildRequestParam(request); + // 2. 获取请求头中的关键信息 + String wechatpaySignature = request.getHeader("Wechatpay-Signature"); + String wechatpayTimestamp = request.getHeader("Wechatpay-Timestamp"); + String wechatpayNonce = request.getHeader("Wechatpay-Nonce"); + String wechatpaySerial = request.getHeader("Wechatpay-Serial"); + String wechatpaySignatureType = request.getHeader("Wechatpay-Signature-Type"); - NotificationParser parser = new NotificationParser(); - // 2. 解析并验证通知 + log.info("回调头部信息:"); + log.info("Wechatpay-Signature: " + wechatpaySignature); + log.info("Wechatpay-Timestamp: " + wechatpayTimestamp); + log.info("Wechatpay-Nonce: " + wechatpayNonce); + log.info("Wechatpay-Serial: " + wechatpaySerial); + log.info("Wechatpay-Signature-Type: " + wechatpaySignatureType); + + // 3. 验证必要的请求头 + if (StringUtils.isEmpty(wechatpaySignature) || + StringUtils.isEmpty(wechatpayTimestamp) || + StringUtils.isEmpty(wechatpayNonce) || + StringUtils.isEmpty(wechatpaySerial)) { + result.put("code", "FAIL"); + result.put("message", "缺少必要的微信支付头部信息"); + return result; + } + + // 4. 构建 RequestParam + RequestParam requestParam = new RequestParam.Builder() + .serialNumber(wechatpaySerial) + .nonce(wechatpayNonce) + .signature(wechatpaySignature) + .timestamp(wechatpayTimestamp) + .signType(wechatpaySignatureType) + .body(requestBody) + .build(); + + // 5. 初始化验证器 - 正确的方式 + + NotificationParser parser = new NotificationParser((NotificationConfig) WeChatPayUtil.getConfig()); + + // 6. 解析并验证通知 Transaction transaction = parser.parse(requestParam, Transaction.class); - - System.out.println("解析支付通知成功:"); - System.out.println("商户订单号: " + transaction.getOutTradeNo()); - System.out.println("微信支付订单号: " + transaction.getTransactionId()); - System.out.println("交易状态: " + transaction.getTradeState()); - System.out.println("交易类型: " + transaction.getTradeType()); - System.out.println("金额: " + transaction.getAmount().getTotal()); + log.info("接收到微信支付回调:{}", JSONUtil.toJsonStr(transaction)); // 3. 处理业务逻辑 orderService.processPaymentResult(transaction); - // if (processResult) { - // // 4. 返回成功响应 - // result.put("code", "SUCCESS"); - // result.put("message", "成功"); - // System.out.println("支付回调处理成功"); - // } else { - // result.put("code", "FAIL"); - // result.put("message", "处理失败"); - // System.err.println("支付回调处理失败"); - // } + // 7. 处理业务逻辑 + //processPaymentResult(transaction, result); - } catch (Exception e) { - System.err.println("支付回调处理异常: " + e.getMessage()); + } catch (ValidationException e) { + // logger.error("签名验证失败", e); e.printStackTrace(); - - // 返回失败响应 + result.put("code", "FAIL"); + result.put("message", "签名验证失败"); + } catch (Exception e) { + e.printStackTrace(); + //logger.error("微信支付回调处理失败", e); result.put("code", "FAIL"); result.put("message", "处理异常: " + e.getMessage()); } return result; + + // Map result = new HashMap<>(); + // + // try { + // // 1. 构建回调参数 + // RequestParam requestParam = buildRequestParam(request); + // + // + // + // + // NotificationParser parser = new NotificationParser(); + // // 2. 解析并验证通知 + // Transaction transaction = parser.parse(requestParam, Transaction.class); + // + // log.info("接收到微信支付回调:{}", JSONUtil.toJsonStr( transaction)); + // + // // 3. 处理业务逻辑 + // orderService.processPaymentResult(transaction); + // + // //4. 返回成功响应 + // result.put("code", "SUCCESS"); + // result.put("message", "成功"); + // } catch (Exception e) { + // log.error("支付回调处理异常: " + e.getMessage()); + // e.printStackTrace(); + // // 返回失败响应 + // result.put("code", "FAIL"); + // result.put("message", "处理异常: " + e.getMessage()); + // } + // return result; } - /** - * 从HttpServletRequest构建RequestParam - */ - private RequestParam buildRequestParam(HttpServletRequest request) throws Exception { - // 获取微信回调的头部信息 - String wechatpaySerial = request.getHeader("Wechatpay-Serial"); - String wechatpaySignature = request.getHeader("Wechatpay-Signature"); - String wechatpayTimestamp = request.getHeader("Wechatpay-Timestamp"); - String wechatpayNonce = request.getHeader("Wechatpay-Nonce"); - String wechatpaySignatureType = request.getHeader("Wechatpay-Signature-Type"); - - System.out.println("回调头部信息:"); - System.out.println("Wechatpay-Serial: " + wechatpaySerial); - System.out.println("Wechatpay-Timestamp: " + wechatpayTimestamp); - System.out.println("Wechatpay-Nonce: " + wechatpayNonce); - System.out.println("Wechatpay-Signature-Type: " + wechatpaySignatureType); - - // 读取请求体 - String body = getRequestBody(request); - System.out.println("回调请求体: " + body); - - return new RequestParam.Builder() - .serialNumber(wechatpaySerial) - .nonce(wechatpayNonce) - .signature(wechatpaySignature) - .timestamp(wechatpayTimestamp) - .signType(wechatpaySignatureType) - .body(body) - .build(); - } /** * 读取请求体内容 @@ -127,7 +168,7 @@ public class WechatPayNotifyController { // */ // private boolean handleRefund(Order order, Transaction transaction) { // // 实现退款逻辑 - // System.out.println("处理退款: " + order.getOutTradeNo()); + // log.info("处理退款: " + order.getOutTradeNo()); // return true; // } // @@ -136,7 +177,7 @@ public class WechatPayNotifyController { // */ // private boolean handleClosedPayment(Order order, Transaction transaction) { // // 实现订单关闭逻辑 - // System.out.println("处理订单关闭: " + order.getOutTradeNo()); + // log.info("处理订单关闭: " + order.getOutTradeNo()); // return true; // } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/client/ClientOrderInfoController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/client/ClientOrderInfoController.java index 0149b0e..b4fcf1e 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/client/ClientOrderInfoController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/client/ClientOrderInfoController.java @@ -55,6 +55,12 @@ public class ClientOrderInfoController extends BaseController return orderInfoService.wechatPay(orderId); } + // 查询支付结果 + @GetMapping("/transactions") + public AjaxResult paymentCallback(@RequestParam("transactionId") String transactionId){ + return orderInfoService.findTransactions(transactionId); + } + /** * 支付订单 */ diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java index 7b7e87a..029c2be 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -135,9 +135,17 @@ public class SecurityConfig // /client/index/getCategory // /client/index/re/bind/music // /client/index/re/music + // /client/index/getCategory + // /client/index/cate/music + // /client/consult/list + // /client/index/re/music + // /client/share/list requests.antMatchers("/login", "/register","/client/shopLogin","/file/download/**", "/captchaImage", "/client/getCode","/client/file/**","/call/back/**","/client/index/getBanner", - "/client/index/getCategory","/client/index/re/bind/music","/client/index/re/music","/client/version/getLastVersion").permitAll() + "/client/index/getCategory","/client/index/re/bind/music","/client/index/re/music", + "/client/version/getLastVersion","/client/search/list","/client/music/find","/client" + + "/index/getCategory","/client/index/cate/music/**","/client/consult/list", + "/client/index/re/music/**","/client/share/list").permitAll() // 静态资源,可匿名访问 .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml index f855995..2af76a9 100644 --- a/ruoyi-system/pom.xml +++ b/ruoyi-system/pom.xml @@ -36,6 +36,19 @@ 0.2.17 + + + com.github.wechatpay-apiv3 + wechatpay-apache-httpclient + 0.4.9 + + + + com.alipay.sdk + alipay-easysdk + 2.2.3 + + diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/ProductMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/ProductMapper.java index cbadf5f..55e9dea 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/ProductMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/ProductMapper.java @@ -59,4 +59,6 @@ public interface ProductMapper * @return 结果 */ public int deleteProductByIds(String[] ids); + + Product selectProductByPageType(String packageType); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IOrderInfoService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IOrderInfoService.java index 550352a..4eb015a 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/IOrderInfoService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IOrderInfoService.java @@ -138,4 +138,8 @@ public interface IOrderInfoService AjaxResult wechatPay(String orderId); AjaxResult processPaymentResult(Transaction transaction); + + AjaxResult findTransactions(String transactionId); + + AjaxResult processPaymentAliResult(String outTradeNo, String tradeNo); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/OrderInfoServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/OrderInfoServiceImpl.java index 218ef12..263d48f 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/OrderInfoServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/OrderInfoServiceImpl.java @@ -6,6 +6,7 @@ import com.ruoyi.common.core.domain.dto.PaymentRequest; import com.ruoyi.common.core.domain.dto.RefundRequest; import com.ruoyi.common.core.domain.entity.OrderInfo; import com.ruoyi.common.core.domain.entity.PayStatusEnum; +import com.ruoyi.common.core.domain.entity.Product; import com.ruoyi.common.core.domain.entity.ShopUser; import com.ruoyi.common.core.domain.vo.OrderInfoVO; import com.ruoyi.common.core.domain.model.LoginUser; @@ -14,7 +15,9 @@ import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.mapper.OrderInfoMapper; +import com.ruoyi.system.mapper.ProductMapper; import com.ruoyi.system.service.IOrderInfoService; +import com.ruoyi.system.service.ShopUserService; import com.ruoyi.system.util.AppleyPay; import com.ruoyi.system.util.PaymentUtil; import com.ruoyi.system.util.WeChatPayUtil; @@ -51,6 +54,12 @@ public class OrderInfoServiceImpl implements IOrderInfoService @Autowired private WeChatPayUtil weChatPayUtil; + @Autowired + private ShopUserService shopUserService; + + @Autowired + private ProductMapper productMapper; + /** * 查询【请填写功能名称】 @@ -147,7 +156,8 @@ public class OrderInfoServiceImpl implements IOrderInfoService orderInfo.setOrderId(System.currentTimeMillis()); // 使用时间戳作为订单号 orderInfo.setOrderName(request.getOrderName()); orderInfo.setUserId(loginUser.getUserId()); - orderInfo.setAmount(new BigDecimal(request.getAmount())); + Product product = productMapper.selectProductByPageType(request.getPackageType()); + orderInfo.setAmount(product.getCurrentPrice()); orderInfo.setPayType(request.getPayType()); orderInfo.setPackageType(request.getPackageType()); orderInfo.setDeviceType(request.getDeviceType()); @@ -391,6 +401,7 @@ public class OrderInfoServiceImpl implements IOrderInfoService return AjaxResult.success("获取微信支付参数成功",result); } + @Override public AjaxResult processPaymentResult(Transaction transaction) { try { @@ -413,43 +424,46 @@ public class OrderInfoServiceImpl implements IOrderInfoService return AjaxResult.success("订单已支付,跳过处理"); } + if ((OrderStatus.CREATED.getCode()).equals(order.getPayStatus())) { + //order.getAmount() 元转分 + int amount = order.getAmount().multiply(new BigDecimal(100)).intValue(); - //order.getAmount() 元转分 - int amount = order.getAmount().multiply(new BigDecimal(100)).intValue(); + // 3. 验证金额是否一致 + if (amount != totalAmount) { + return AjaxResult.error("金额不一致"); + } - // 3. 验证金额是否一致 - if (amount != totalAmount) { - return AjaxResult.error("金额不一致"); + // 4. 根据交易状态处理业务 + if ("SUCCESS".equals(tradeState)) { + // 计算服务开始和结束时间 + Date startTime = new Date(); + // 支付成功 + // 设置订单信息 + order.setPayStatus(2L); // 2-已完成 + order.setPayTime(DateUtils.getNowDate()); + order.setStartTime(startTime); + order.setTradeNo(transactionId); + order.setCallbackContent(outTradeNo); // 保存交易ID + order.setCallTime(DateUtils.getNowDate()); + order.setUpdateTime(DateUtils.getNowDate()); + Date endTime = AppleyPay.calculateEndTime(startTime, order.getPackageType()); + order.setEndTime(endTime); + // 更新用户VIP时间 + shopUserService.updateShopUserVipTime(order.getUserId(), order.getPackageType()); + orderInfoMapper.updateOrderInfo(order); + return AjaxResult.success(); + } else if ("REFUND".equals(tradeState)) { + // 转入退款 + //return handleRefund(order, transaction); + } else if ("CLOSED".equals(tradeState)) { + // 已关闭 + //return handleClosedPayment(order, transaction); + } else { + // 其他状态 + log.info("未知状态: " + tradeState); + // 仍然返回成功,避免微信重复通知 + } } - - // 4. 根据交易状态处理业务 - if ("SUCCESS".equals(tradeState)) { - // 计算服务开始和结束时间 - Date startTime = new Date(); - // 支付成功 - // 设置订单信息 - order.setPayStatus(2L); // 2-已完成 - order.setPayTime(DateUtils.getNowDate()); - order.setStartTime(startTime); - order.setTradeNo(transactionId); - order.setCallbackContent(outTradeNo); // 保存交易ID - order.setCallTime(DateUtils.getNowDate()); - order.setUpdateTime(DateUtils.getNowDate()); - Date endTime = AppleyPay.calculateEndTime(startTime, order.getPackageType()); - order.setEndTime(endTime); - return AjaxResult.success(); - } else if ("REFUND".equals(tradeState)) { - // 转入退款 - //return handleRefund(order, transaction); - } else if ("CLOSED".equals(tradeState)) { - // 已关闭 - //return handleClosedPayment(order, transaction); - } else { - // 其他状态 - log.info("未知状态: " + tradeState); - // 仍然返回成功,避免微信重复通知 - } - } catch (Exception e) { log.error("处理订单异常: {}", e.getMessage()); e.printStackTrace(); @@ -458,6 +472,46 @@ public class OrderInfoServiceImpl implements IOrderInfoService return AjaxResult.error("处理订单异常"); } + @Override + public AjaxResult findTransactions(String transactionId) { + Transaction transaction = weChatPayUtil.queryOrder(transactionId); + return AjaxResult.success( transaction); + } + + @Override + public AjaxResult processPaymentAliResult(String outTradeNo, String tradeNo) { + // 1. 根据商户订单号查询本地订单 + OrderInfo order = orderInfoMapper.selectOrderByOrderId(outTradeNo); + if (order == null) { + return AjaxResult.error("未找到对应订单"); + } + + // 2. 检查订单是否已经处理过(防止重复通知) + if ((OrderStatus.PAID.getCode()).equals(order.getPayStatus())) { + return AjaxResult.success("订单已支付,跳过处理"); + } + + if ((OrderStatus.CREATED.getCode()).equals(order.getPayStatus())) { + // 计算服务开始和结束时间 + Date startTime = new Date(); + // 支付成功 + // 设置订单信息 + order.setPayStatus(2L); // 2-已完成 + order.setPayTime(DateUtils.getNowDate()); + order.setStartTime(startTime); + order.setTradeNo(tradeNo); + order.setCallbackContent(outTradeNo); // 保存交易ID + order.setCallTime(DateUtils.getNowDate()); + order.setUpdateTime(DateUtils.getNowDate()); + Date endTime = AppleyPay.calculateEndTime(startTime, order.getPackageType()); + order.setEndTime(endTime); + // 更新用户VIP时间 + shopUserService.updateShopUserVipTime(order.getUserId(), order.getPackageType()); + orderInfoMapper.updateOrderInfo(order); + return AjaxResult.success(); + } + return AjaxResult.error("处理订单异常"); + } @Override @Transactional diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/util/AliPayUtil.java b/ruoyi-system/src/main/java/com/ruoyi/system/util/AliPayUtil.java new file mode 100644 index 0000000..aa2c27a --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/util/AliPayUtil.java @@ -0,0 +1,60 @@ +package com.ruoyi.system.util; + +import com.alipay.easysdk.factory.Factory; +import com.alipay.easysdk.kernel.Config; +import com.alipay.easysdk.kernel.util.ResponseChecker; +import com.alipay.easysdk.payment.app.models.AlipayTradeAppPayResponse; +import com.alipay.easysdk.payment.facetoface.models.AlipayTradePrecreateResponse; +import com.ruoyi.common.core.domain.entity.OrderInfo; + +/** + * 描述: + * + * @author MXP by 2025/11/25 + */ +public class AliPayUtil { + + public static void main(String[] args) throws Exception { + // 1. 设置参数(全局只需设置一次) + + } + + public String prepayWithRequestPayment(OrderInfo orderInfo){ + Factory.setOptions(getOptions()); + try { + AlipayTradeAppPayResponse response = Factory + .Payment + .App() + .pay(orderInfo.getOrderName(), + String.valueOf(orderInfo.getOrderId()), + String.valueOf(orderInfo.getAmount())); + return response.getBody() ; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + + private static Config getOptions() { + Config config = new Config(); + config.protocol = "https"; + config.gatewayHost = "openapi.alipay.com"; + config.signType = "RSA2"; + config.appId = "<-- 请填写您的AppId,例如:2019091767145019 -->"; + // 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中 + config.merchantPrivateKey = "<-- 请填写您的应用私钥,例如:MIIEvQIBADANB ... ... -->"; + //注:证书文件路径支持设置为文件系统中的路径或CLASS_PATH中的路径,优先从文件系统中加载,加载失败后会继续尝试从CLASS_PATH中加载 + config.merchantCertPath = "<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->"; + config.alipayCertPath = "<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->"; + config.alipayRootCertPath = "<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt -->"; + //注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可 + // config.alipayPublicKey = "<-- 请填写您的支付宝公钥,例如:MIIBIjANBg... -->"; + //可设置异步通知接收服务地址(可选) + config.notifyUrl = "<-- 请填写您的支付类接口异步通知接收服务地址,例如:https://www.test.com/callback -->"; + //可设置AES密钥,调用AES加解密相关接口时需要(可选) + config.encryptKey = "<-- 请填写您的AES密钥,例如:aa4BtZ4tspm2wnXLb1ThQA== -->"; + return config; + } + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/util/WeChatPayUtil.java b/ruoyi-system/src/main/java/com/ruoyi/system/util/WeChatPayUtil.java index c4bb620..5ad174f 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/util/WeChatPayUtil.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/util/WeChatPayUtil.java @@ -2,6 +2,7 @@ package com.ruoyi.system.util; import com.ruoyi.common.constant.WeChatConfig; import com.ruoyi.common.core.domain.entity.OrderInfo; +import com.wechat.pay.contrib.apache.httpclient.auth.*; import com.wechat.pay.java.core.Config; import com.wechat.pay.java.core.RSAPublicKeyConfig; import com.wechat.pay.java.core.util.PemUtil; @@ -9,10 +10,17 @@ import com.wechat.pay.java.service.payments.app.AppServiceExtension; import com.wechat.pay.java.service.payments.app.model.Amount; import com.wechat.pay.java.service.payments.app.model.PrepayRequest; import com.wechat.pay.java.service.payments.app.model.PrepayWithRequestPaymentResponse; +import com.wechat.pay.java.service.payments.app.model.QueryOrderByIdRequest; +import com.wechat.pay.java.service.payments.model.Transaction; import org.springframework.stereotype.Component; +import java.io.ByteArrayInputStream; import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.List; /** * 描述: @@ -22,23 +30,9 @@ import java.security.cert.X509Certificate; @Component public class WeChatPayUtil { - public PrepayWithRequestPaymentResponse prepayWithRequestPayment(OrderInfo orderInfo){ + public PrepayWithRequestPaymentResponse prepayWithRequestPayment(OrderInfo orderInfo) { // 2. 加载商户证书并获取序列号 - X509Certificate merchantCertificate = PemUtil.loadX509FromPath(WeChatConfig.MERCHANT_CERT_PATH); - String merchantSerialNumber = merchantCertificate.getSerialNumber().toString(16).toUpperCase(); - - Config config = - new RSAPublicKeyConfig.Builder() - .merchantId(WeChatConfig.MERCHANT_ID) - .privateKeyFromPath(WeChatConfig.PRIVATE_KEY_PATH) - .publicKeyFromPath(WeChatConfig.WECHAT_PAY_CERT_PATH) - .publicKeyId(WeChatConfig.PUBLIC_KEY_ID) - .merchantSerialNumber(merchantSerialNumber) - .apiV3Key(WeChatConfig.API_V3_KEY) - .build(); - - AppServiceExtension service = new AppServiceExtension.Builder().config(config).build(); - + AppServiceExtension service = Options(); // 跟之前下单示例一样,填充预下单参数 PrepayRequest request = new PrepayRequest(); request.setAppid(WeChatConfig.APP_ID); @@ -64,4 +58,50 @@ public class WeChatPayUtil { } + // 查询支付结果 + public Transaction queryOrder(String transactionId) { + AppServiceExtension service; + service = Options(); + QueryOrderByIdRequest request = new QueryOrderByIdRequest(); + request.setTransactionId(transactionId); + request.setMchid(WeChatConfig.MERCHANT_ID); + return service.queryOrderById(request); + + } + + public static AppServiceExtension Options() { + return new AppServiceExtension.Builder().config(getConfig()).build(); + } + + public static Config getConfig() { + X509Certificate merchantCertificate = PemUtil.loadX509FromPath(WeChatConfig.MERCHANT_CERT_PATH); + String merchantSerialNumber = merchantCertificate.getSerialNumber().toString(16).toUpperCase(); + return new RSAPublicKeyConfig.Builder() + .merchantId(WeChatConfig.MERCHANT_ID) + .privateKeyFromPath(WeChatConfig.PRIVATE_KEY_PATH) + .publicKeyFromPath(WeChatConfig.WECHAT_PAY_CERT_PATH) + .publicKeyId(WeChatConfig.PUBLIC_KEY_ID) + .merchantSerialNumber(merchantSerialNumber) + .apiV3Key(WeChatConfig.API_V3_KEY) + .build(); + + } + + /** + * 获取验证器(推荐方式) + */ + public static Verifier getVerifier() { + // 加载商户私钥 + PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromPath(WeChatConfig.PRIVATE_KEY_PATH); + X509Certificate merchantCertificate = PemUtil.loadX509FromPath(WeChatConfig.MERCHANT_CERT_PATH); + String merchantSerialNumber = merchantCertificate.getSerialNumber().toString(16).toUpperCase(); + // 创建自动更新平台证书的验证器 + AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier( + new WechatPay2Credentials(WeChatConfig.MERCHANT_ID, new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)), + (WeChatConfig.API_V3_KEY).getBytes(StandardCharsets.UTF_8) + ); + + return verifier; + } + } diff --git a/ruoyi-system/src/main/resources/mapper/system/ProductMapper.xml b/ruoyi-system/src/main/resources/mapper/system/ProductMapper.xml index 1a66cb3..69743ec 100644 --- a/ruoyi-system/src/main/resources/mapper/system/ProductMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/ProductMapper.xml @@ -122,4 +122,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{id} + +