ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/domain/ShopWithdraw.java
@@ -106,7 +106,7 @@ @TableField("receiverAccountType") private Integer receiverAccountType; @ApiModelProperty("收款账户联行号") @ApiModelProperty("收款方开户行编号") @TableField("receiverBankChannelNo") private String receiverBankChannelNo; ruoyi-service/pom.xml
@@ -32,7 +32,7 @@ <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.0.3</version> <version>5.8.25</version> </dependency> <!--log4j--> <dependency> ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/AppUserServiceImpl.java
@@ -136,6 +136,10 @@ appUser.setDelFlag(false); appUser.setCreateTime(LocalDateTime.now()); this.save(appUser); }else { //从订单导入的,将openid导入 appUser.setWxOpenid(openid); this.updateById(appUser); } //账户被冻结,给出提示 if(2 == appUser.getStatus()){ @@ -175,7 +179,7 @@ //查询用户是否注册,没有注册则跳转到注册页面 AppUser appUser = this.getOne(new LambdaQueryWrapper<AppUser>().eq(AppUser::getPhone, mobileLogin.getPhone()) .ne(AppUser::getStatus, 3).eq(AppUser::getDelFlag, 0)); if(null == appUser){ if(null == appUser||null ==appUser.getWxOpenid()){ LoginVo loginVo = new LoginVo(); loginVo.setSkipPage(2); loginVo.setPhone(mobileLogin.getPhone()); @@ -304,8 +308,8 @@ if(null != appUser1 && StringUtils.isNotEmpty(appUser1.getWxOpenid())){ return R.fail("手机号已注册,请直接登录!"); } if(null != appUser1 && appUser1.getStatus() == 1){ return R.fail("手机号已注册,请直接登录!"); if(null != appUser1 && appUser1.getStatus() == 2){ return R.fail("该手机号已被冻结!"); } String avatar = registerAccount.getAvatar(); ruoyi-service/ruoyi-order/pom.xml
@@ -15,6 +15,12 @@ </description> <dependencies> <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.8.10.ALL</version> </dependency> <!--网易邮件--> <dependency> <groupId>javax.mail</groupId> @@ -170,6 +176,21 @@ <artifactId>geodesy</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-http</artifactId> <version>5.8.25</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> </dependencies> <build> ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/OrderController.java
@@ -24,6 +24,9 @@ import com.ruoyi.order.service.OrderService; import com.ruoyi.order.util.payment.model.RefundCallbackResult; import com.ruoyi.order.util.payment.model.UniPayCallbackResult; import com.ruoyi.order.util.payment.wechat.PayMoneyUtil; import com.ruoyi.order.util.payment.wx.WechatPayService; import com.ruoyi.order.util.payment.wx.vo.PayResult; import com.ruoyi.order.util.vo.MapTrackKD100Vo; import com.ruoyi.order.util.vo.ShopAnalysisVO; import com.ruoyi.order.vo.*; @@ -37,10 +40,12 @@ import com.ruoyi.system.api.model.LoginUser; import io.swagger.annotations.*; import org.apache.ibatis.annotations.Param; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @@ -82,7 +87,10 @@ @Resource private ShopClient shopClient; @Resource private PayMoneyUtil payMoneyUtil; @Resource private WechatPayService wechatPayService; @ResponseBody @@ -104,12 +112,19 @@ * 订单支付回调通知 */ @ResponseBody @GetMapping("/orderPaymentCallback") public void orderPaymentCallback(UniPayCallbackResult uniPayCallbackResult, HttpServletResponse response){ String jsonString = JSONObject.toJSONString(uniPayCallbackResult); @PostMapping("/orderPaymentCallback") public void orderPaymentCallback(HttpServletRequest request, HttpServletResponse response){ // String jsonString = JSONObject.toJSONString(uniPayCallbackResult); System.err.println("1111111111111"); PayResult payResult= null; try { payResult = wechatPayService.processNotify(request); } catch (Exception e) { throw new RuntimeException(e); } System.out.println("1111111111111111111111"); System.out.println(jsonString); R callback = orderService.orderPaymentCallback(uniPayCallbackResult); // System.out.println(jsonString); R callback = orderService.orderPaymentCallback(payResult); if(callback.getCode() == 200){ response.setStatus(200); PrintWriter out = null; @@ -406,26 +421,16 @@ */ /** * 订单取消支付回退 * * @param refundCallbackResult * @param response * @return */ @ResponseBody @GetMapping("/refundPayMoneyCallback") public void refundPayMoneyCallback(RefundCallbackResult refundCallbackResult, HttpServletResponse response) { R callback = orderService.refundPayMoneyCallback(refundCallbackResult); @PostMapping("/refundPayMoneyCallback") public String refundPayMoneyCallback( @RequestBody(required = false) String xmlData) { R callback = orderService.refundPayMoneyCallback(xmlData); if (callback.getCode() == 200) { response.setStatus(200); PrintWriter out = null; try { out = response.getWriter(); } catch (IOException e) { throw new RuntimeException(e); } out.println("success"); out.flush(); out.close(); return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; }else { System.err.println("支付回退错误:"+callback.getMsg()); return "<xml><return_code><![CDATA[FAIL]]></return_code></xml>"; } } @@ -778,5 +783,15 @@ return R.ok(orderService.getMap(queryWrapper)); } /** * 获取商户RSA加密公钥 */ @GetMapping("/getRsaPublicKey") public R<Void> getRsaPublicKey(){ wechatPayService.getRsaPublicKey(); return R.ok(); } } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/OrderService.java
@@ -7,12 +7,16 @@ import com.ruoyi.order.model.Order; import com.ruoyi.order.util.payment.model.RefundCallbackResult; import com.ruoyi.order.util.payment.model.UniPayCallbackResult; import com.ruoyi.order.util.payment.wx.vo.PayResult; import com.ruoyi.order.vo.*; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Map; /** * <p> @@ -72,7 +76,7 @@ * 取消订单后回调处理 * @return */ R refundPayMoneyCallback(RefundCallbackResult refundCallbackResult); R refundPayMoneyCallback(String xmlData); /** @@ -121,7 +125,7 @@ /** * 订单支付回调通知 */ R orderPaymentCallback(UniPayCallbackResult uniPayCallbackResult); R orderPaymentCallback(PayResult payResult); /** * 定时任务关闭订单 */ ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/OrderServiceImpl.java
@@ -33,6 +33,11 @@ import com.ruoyi.order.util.payment.PaymentUtil; import com.ruoyi.order.util.payment.model.*; import com.ruoyi.order.util.payment.wechat.PayMoneyUtil; import com.ruoyi.order.util.payment.wx.WechatPayConfig; import com.ruoyi.order.util.payment.wx.WechatPayService; import com.ruoyi.order.util.payment.wx.vo.PayResult; import com.ruoyi.order.util.payment.wx.vo.RefundCallbackResult; import com.ruoyi.order.vo.*; import com.ruoyi.other.api.domain.*; import com.ruoyi.other.api.feignClient.*; @@ -52,6 +57,8 @@ import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.math.BigDecimal; import java.math.RoundingMode; @@ -122,6 +129,13 @@ private RegionClient regionClient; @Resource private ApplicationEventPublisher applicationEventPublisher; @Resource private PayMoneyUtil payMoneyUtil; @Resource private WechatPayService wechatPayService; @Override @@ -433,10 +447,16 @@ BigDecimal paymentAmount = order.getPaymentAmount(); if (BigDecimal.ZERO.compareTo(order.getPaymentAmount()) < 0) {//支付的金额是否大于0 //微信退款 RefundResult refund = PaymentUtil.refund(order.getOrderNumber(), "R" + order.getOrderNumber(), paymentAmount.doubleValue(), "/order/order/refundPayMoneyCallback"); if (!"100".equals(refund.getRa_Status())) { return R.fail(refund.getRc_CodeMsg());//退款失败 Map<String,String> map = wechatPayService.refund(order.getOrderNumber(), order.getOrderNumber(), order.getPaymentAmount().toString(), order.getPaymentAmount().toString(), "退款", "/order/order/refundPayMoneyCallback"); // RefundResult refund = PaymentUtil.refund(order.getOrderNumber(), "R" + order.getOrderNumber(), paymentAmount.doubleValue(), "/order/order/refundPayMoneyCallback"); if (!"SUCCESS".equals(map.get("return_code"))) { return R.fail(map.get("return_msg"));//退款失败 } //这里申请成功后先返回,等待微信退款成功再返回积分这些 order.setRefundStatus(1); this.updateById(order); return R.ok(); } //退款成功再回退积分 AppUser appUser = appUserClient.getAppUserById(order.getAppUserId()); @@ -516,16 +536,90 @@ * @return */ @Override public R refundPayMoneyCallback(RefundCallbackResult refundCallbackResult) { String code = refundCallbackResult.getR3_RefundOrderNo().substring(1); Order order = this.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNumber, code)); if (null == order || order.getPayStatus() == 1 || order.getOrderStatus() == 6) { public R refundPayMoneyCallback(String xmlData) { RefundCallbackResult result = wechatPayService.processRefundCallback(xmlData); if (!result.isSuccess()) { return R.fail(result.getMsg()); } Order order = this.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNumber, result.getOrderNo())); if (null == order || order.getPayStatus() == 1 || order.getRefundStatus() == 2) { return R.ok(); } order.setRefundCode(refundCallbackResult.getR5_RefundTrxNo()); // order.setRefundCode(refundCallbackResult.getR5_RefundTrxNo()); order.setRefundCode(result.getRefundNo()); order.setRefundStatus(2); order.setRefundTime(LocalDateTime.now()); this.updateById(order); //退款成功再回退积分 AppUser appUser = appUserClient.getAppUserById(order.getAppUserId()); if (order.getPoint()>0) { if(null==appUser.getCancelPoint()){ appUser.setCancelPoint(0); } //返回订单抵扣积分 Integer historicalPoint = appUser.getAvailablePoint(); Integer availablePoint = appUser.getAvailablePoint() + order.getPoint();//可用积分 Integer cancelPoint = appUser.getCancelPoint() + order.getPoint();//取消订单积分 appUser.setAvailablePoint(availablePoint); appUser.setCancelPoint(cancelPoint); appUser.setTotalPoint(appUser.getTotalPoint() + order.getPoint()); appUserClient.editAppUserById(appUser); //构建积分流水 UserPoint userPoint = new UserPoint(); userPoint.setType(16);//取消订单 userPoint.setHistoricalPoint(historicalPoint); userPoint.setVariablePoint(order.getPoint()); userPoint.setBalance(availablePoint); userPoint.setCreateTime(LocalDateTime.now()); userPoint.setAppUserId(order.getAppUserId()); userPoint.setObjectId(order.getId()); userPointClient.saveUserPoint(userPoint); } order.setRefundStatus(2); order.setRefundTime(LocalDateTime.now()); //商品销售数量 OrderGood orderGood = orderGoodService.getOne(new LambdaQueryWrapper<OrderGood>().eq(OrderGood::getOrderId, order.getId())); goodsClient.editGoodsNum(orderGood.getGoodsId(), -1); //获取商品json Goods good = JSON.parseObject(orderGood.getGoodJson(), Goods.class); GoodsSeckill goodsSeckill = JSON.parseObject(orderGood.getSeckillJson(), GoodsSeckill.class); //门店减少冻结资金 即减少余额, 冻结资金=余额-可用资金 Shop shop = shopClient.getShopById(order.getShopId()).getData(); BigDecimal historicalBalance=shop.getBalance();//历史余额 BigDecimal variableAmount=BigDecimal.ZERO;//变动金额 if (null != goodsSeckill) { variableAmount=goodsSeckill.getSellingPrice(); }else { variableAmount=good.getSellingPrice(); } BigDecimal balance=shop.getBalance().subtract(variableAmount);//变动后余额 shop.setBalance(balance); shopClient.updateShop(shop); //门店余额流水记录 ShopBalanceStatement shopBalanceStatement = new ShopBalanceStatement(); shopBalanceStatement.setShopId(shop.getId()); shopBalanceStatement.setShopName(shop.getName()); shopBalanceStatement.setShopManagerName(shop.getShopManager()); shopBalanceStatement.setPhone(shop.getPhone()); shopBalanceStatement.setType(6);//变更类型,订单退款 shopBalanceStatement.setHistoricalBalance(historicalBalance); shopBalanceStatement.setVariableAmount(variableAmount); shopBalanceStatement.setCreateTime(LocalDateTime.now()); shopBalanceStatement.setBalance(balance); shopBalanceStatement.setCreateUserId(appUser.getId()); shopBalanceStatement.setObjectId(order.getId()); shopBalanceStatementClient.saveShopBalanceStatement(shopBalanceStatement); return R.ok(); } @@ -913,49 +1007,54 @@ if ( BigDecimal.ZERO.compareTo(paymentMoney) < 0){ //调起微信支付 String goodsNames = goods.getName(); UniPayResult uniPayResult = PaymentUtil.uniPay(order.getOrderNumber(), paymentMoney.doubleValue(), "购买单品商品", goodsNames, "", "/order/order/orderPaymentCallback", appUser.getWxOpenid(), null); if(null == uniPayResult || !"100".equals(uniPayResult.getRa_Code())){ //支付失败,积分回退 ,删除订单 //检查是否先有过积分抵扣了,是的话要返回订单已抵扣的积分,以及用户积分流水的删除 if (order.getPoint()>0) { //返回订单抵扣积分 AppUser appUser2 = appUserClient.getAppUserById(order.getAppUserId()); Integer availablePoint = appUser2.getAvailablePoint();//可用积分 Integer variablePoint = order.getPoint();//变动积分 Integer balance = appUser2.getAvailablePoint() + order.getPoint();//变动后积分 Integer cancelPoint = appUser2.getCancelPoint() + order.getPoint();//取消订单积分 appUser2.setAvailablePoint(availablePoint); appUser2.setCancelPoint(cancelPoint); appUser2.setTotalPoint(appUser2.getTotalPoint() + order.getPoint()); try { R r = wechatPayService.unifiedOrder(order.getId().toString(), order.getOrderNumber(), paymentMoney.toString(), "购买单品商品",appUser.getWxOpenid(),"/order/order/orderPaymentCallback"); if (null == r || 200 != r.getCode()){ //支付失败,积分回退 ,删除订单 //检查是否先有过积分抵扣了,是的话要返回订单已抵扣的积分,以及用户积分流水的删除 if (order.getPoint()>0) { //返回订单抵扣积分 AppUser appUser2 = appUserClient.getAppUserById(order.getAppUserId()); Integer availablePoint = appUser2.getAvailablePoint();//可用积分 Integer variablePoint = order.getPoint();//变动积分 Integer balance = appUser2.getAvailablePoint() + order.getPoint();//变动后积分 Integer cancelPoint = appUser2.getCancelPoint() + order.getPoint();//取消订单积分 appUser2.setAvailablePoint(availablePoint); appUser2.setCancelPoint(cancelPoint); appUser2.setTotalPoint(appUser2.getTotalPoint() + order.getPoint()); //构建积分流水记录 UserPoint userPoint = new UserPoint(); userPoint.setType(16);//取消订单 userPoint.setHistoricalPoint(availablePoint); userPoint.setVariablePoint(variablePoint); userPoint.setBalance(balance); userPoint.setCreateTime(LocalDateTime.now()); userPoint.setAppUserId(appUser2.getId()); userPoint.setObjectId(order.getId()); userPointClient.saveUserPoint(userPoint); //构建积分流水记录 UserPoint userPoint = new UserPoint(); userPoint.setType(16);//取消订单 userPoint.setHistoricalPoint(availablePoint); userPoint.setVariablePoint(variablePoint); userPoint.setBalance(balance); userPoint.setCreateTime(LocalDateTime.now()); userPoint.setAppUserId(appUser2.getId()); userPoint.setObjectId(order.getId()); userPointClient.saveUserPoint(userPoint); appUserClient.editAppUserById(appUser2); appUserClient.editAppUserById(appUser2); } //删除订单 order.setDelFlag(1); orderMapper.updateById(order); //返回报错信息 return R.fail(null == r ? "支付失败" : r.getMsg()); } //返回报错信息 return R.fail(null == uniPayResult ? "支付失败" : uniPayResult.getRb_CodeMsg()); /*if(null == uniPayResult || !"100".equals(uniPayResult.getRa_Code())){ }*/ //将支付数据添加到redis队列中,便于定时任务去校验是否完成支付,没有完成支付支付,15分钟后关闭订单。 long second = LocalDateTime.now().plusMinutes(15).toEpochSecond(ZoneOffset.UTC); redisTemplate.opsForZSet().add("OrderPayment", order.getId(), second); return r; }catch (Exception e){ e.printStackTrace(); } String rc_result = uniPayResult.getRc_Result(); JSONObject jsonObject = JSON.parseObject(rc_result); jsonObject.put("orderId", order.getId().toString()); //将支付数据添加到redis队列中,便于定时任务去校验是否完成支付,没有完成支付支付,15分钟后关闭订单。 long second = LocalDateTime.now().plusMinutes(15).toEpochSecond(ZoneOffset.UTC); redisTemplate.opsForZSet().add("OrderPayment", order.getOrderNumber(), second); return R.ok(jsonObject.toJSONString()); } @@ -993,15 +1092,18 @@ shopBalanceStatement.setCreateUserId(appUser.getId()); shopBalanceStatement.setObjectId(order.getId()); shopBalanceStatementClient.saveShopBalanceStatement(shopBalanceStatement); return R.ok(order.getId().toString()); Map<String, String> payParams = new HashMap<>(); payParams.put("payMethod","3");//给前端标识 3-不需要调微信支付 payParams.put("orderId",order.getId().toString()); return R.ok(JSON.toJSONString(payParams)); } /** * 订单支付回调通知 * 订单支付回调 处理业务逻辑 */ @Override public R orderPaymentCallback(UniPayCallbackResult uniPayCallbackResult) { Order order = orderMapper.selectOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNumber, uniPayCallbackResult.getR2_OrderNo())); public R orderPaymentCallback(PayResult payResult) { Order order = orderMapper.selectOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNumber, payResult.getOrderNumber())); if(null == order || order.getPayStatus() == 2){ return R.ok(); } @@ -1017,7 +1119,7 @@ order.setPayStatus(2); //待使用 order.setOrderStatus(3); String r7TrxNo = uniPayCallbackResult.getR9_BankTrxNo(); String r7TrxNo = payResult.getTransactionId(); order.setSerialNumber(r7TrxNo); orderMapper.updateById(order); @@ -1068,18 +1170,17 @@ long second = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC); Set<String> orderPayment = redisTemplate.opsForZSet().rangeByScore("OrderPayment", 0, second); if(orderPayment.size() > 0){ List<Order> list = orderMapper.selectList(new LambdaQueryWrapper<Order>().in(Order::getOrderNumber, orderPayment)); List<Order> list = orderMapper.selectList(new LambdaQueryWrapper<Order>().in(Order::getId, orderPayment)); for (Order order : list) { if(null == order || order.getPayStatus() != 1){ redisTemplate.opsForZSet().remove("OrderPayment", order.getOrderNumber()); continue; } //开始执行关闭订单操作 CloseOrderResult closeOrderResult = PaymentUtil.closeOrder(order.getOrderNumber()); if((null == closeOrderResult || !closeOrderResult.getRa_Status().equals("100")) && Arrays.asList("0", "4", "101", "10080000", "10080002", "10083004", "10083005").contains(closeOrderResult.getRb_Code())){ Map<String, String> map = wechatPayService.closeOrder(order.getOrderNumber()); if((null == map || !map.get("return_code").equals("SUCCESS"))){ redisTemplate.opsForZSet().add("OrderPayment", order.getOrderNumber(), 0); log.error("关闭订单失败:{}---->{}", order.getOrderNumber(), JSON.toJSONString(closeOrderResult)); log.error("关闭订单失败:{}---->{}", order.getOrderNumber(), map.get("return_msg")); } redisTemplate.opsForZSet().remove("OrderPayment", order.getOrderNumber()); //关闭订单后,检查是否先有过积分抵扣了,是的话要返回订单已抵扣的积分,以及用户积分流水的删除, 删除订单 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/PaymentUtil.java
@@ -22,7 +22,7 @@ /** * 商户密钥 */ private static final String key = "a2369875124965782f148539657823152"; private static final String key = "ss369875124965782f148539657826321"; /** * 商户号 */ @@ -280,10 +280,10 @@ public static void main(String[] args) { // UniPayResult uniPayResult = PaymentUtil.uniPay("852963742", 0.01D, "测试商品", "这是用于对接支付测试的商品描述", // "", "/order/shopping-cart/shoppingCartPaymentCallback", "ooOrs64zHLuInkZ_GF0LpIN9_Rxc", "777168500885852"); // PaymentUtil.queryOrder("852963742"); // PaymentUtil.closeOrder("852963742"); UniPayResult uniPayResult = PaymentUtil.uniPay("852963742", 0.01D, "测试商品", "这是用于对接支付测试的商品描述", "", "/order/shopping-cart/shoppingCartPaymentCallback", "ooOrs64zHLuInkZ_GF0LpIN9_Rxc", "777168500885852"); PaymentUtil.queryOrder("852963742"); PaymentUtil.closeOrder("852963742"); } } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wechat/PayMoneyUtil.java
New file @@ -0,0 +1,1295 @@ package com.ruoyi.order.util.payment.wechat; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayClient; import com.alipay.api.CertAlipayRequest; import com.alipay.api.DefaultAlipayClient; import com.alipay.api.domain.AlipayTradeAppPayModel; import com.alipay.api.domain.AlipayTradeRefundModel; import com.alipay.api.request.*; import com.alipay.api.response.*; import com.ruoyi.common.core.domain.R; import com.ruoyi.order.util.payment.MD5AndKL; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.servlet.http.HttpServletRequest; import java.io.*; import java.math.BigDecimal; import java.net.InetAddress; import java.net.UnknownHostException; import java.security.*; import java.util.*; /** * 第三方支付工具类 */ @Component public class PayMoneyUtil { @Value("${alipay.appid}") private String aliAppid;//支付宝appid @Value("${alipay.appPrivateKey}") private String appPrivateKey;//支付宝开发者应用私钥 @Value("${alipay.alipayPublicKey}") private String alipayPublicKey;//支付宝应用公钥 @Value("${alipay.alipay_public_key}") private String alipay_public_key;//支付宝支付公钥 @Value("${wx.appid}") private String appid;//微信appid @Value("${wx.appletsAppid}") private String appletsAppid;//微信小程序appid @Value("${wx.mchId}") private String mchId;//微信商户号 @Value("${wx.key}") private String key;//微信商户号 @Value("${wx.callbackPath}") private String callbackPath;//支付回调网关地址 @Value(("${wx.certPath}")) private String certPath;//微信证书路径 private String app_cert_path = "C:/cert/alipay/driver/app_cert_path.crt";//应用公钥证书路径 private String alipay_cert_path = "C:/cert/alipay/driver/alipay_cert_path.crt";//支付宝公钥证书文件路径 private String alipay_root_cert_path = "C:/cert/alipay/driver/alipay_root_cert_path.crt";//支付宝CA根证书文件路径 private Map<String, JSONObject> order = new HashMap<>();//存储支付订单用于主动查询支付结果 /** * 支付宝支付 */ public R alipay(String body, String subject, String passbackParams, String outTradeNo, String amount, String notifyUrl){ //构造client CertAlipayRequest certAlipayRequest = new CertAlipayRequest (); //设置网关地址 certAlipayRequest.setServerUrl("https://openapi.alipay.com/gateway.do"); //设置应用Id certAlipayRequest.setAppId(aliAppid); //设置应用私钥 certAlipayRequest.setPrivateKey(appPrivateKey); //设置请求格式,固定值json certAlipayRequest.setFormat("json"); //设置字符集 certAlipayRequest.setCharset("UTF-8"); //设置签名类型 certAlipayRequest.setSignType("RSA2"); //设置应用公钥证书路径 certAlipayRequest.setCertPath(app_cert_path); //设置支付宝公钥证书路径 certAlipayRequest.setAlipayPublicCertPath(alipay_cert_path); //设置支付宝根证书路径 certAlipayRequest.setRootCertPath(alipay_root_cert_path); //构造client AlipayClient alipayClient = null; try { alipayClient = new DefaultAlipayClient(certAlipayRequest); } catch (AlipayApiException e) { e.printStackTrace(); } //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest (); //SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。 AlipayTradeAppPayModel model = new AlipayTradeAppPayModel (); model.setBody(body); model.setSubject (subject); model.setOutTradeNo (outTradeNo); model.setTimeoutExpress ("30m" ); model.setTotalAmount (amount); model.setProductCode ( "QUICK_MSECURITY_PAY" ); model.setPassbackParams(passbackParams);//自定义参数 request.setBizModel ( model ); request.setNotifyUrl (callbackPath + notifyUrl); try { //这里和普通的接口调用不同,使用的是sdkExecute AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request); System.out.println(response.getBody());//就是orderString 可以直接给客户端请求,无需再做处理。 return R.ok(response.getBody()); } catch (AlipayApiException e ) { e.printStackTrace(); } // //实例化客户端 // AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", aliAppid, appPrivateKey, "json", "UTF-8", alipay_public_key, "RSA2"); // //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay // AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest(); // //SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。 // AlipayTradeAppPayModel model = new AlipayTradeAppPayModel(); // model.setBody(body);//对一笔交易的具体描述信息。如果是多种商品,请将商品描述字符串累加传给body。 // model.setSubject(subject);//商品的标题/交易标题/订单标题/订单关键字等。 // model.setOutTradeNo(outTradeNo);//商户网站唯一订单号 // model.setTimeoutExpress("30m"); // model.setTotalAmount(amount);//付款金额 // model.setProductCode("QUICK_MSECURITY_PAY"); // model.setPassbackParams(passbackParams);//自定义参数 // request.setBizModel(model); // request.setNotifyUrl(callbackPath + notifyUrl); // try { // //这里和普通的接口调用不同,使用的是sdkExecute // AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request); // Map<String, String> map = new HashMap<>(); // map.put("orderString", response.getBody()); // System.out.println(map);//就是orderString 可以直接给客户端请求,无需再做处理。 // return ResultUtil.success(map); // } catch (AlipayApiException e) { // e.printStackTrace(); // } return null; } /** * 支付宝扫码支付下单 * @param body * @param subject * @param outTradeNo * @param amount * @param notifyUrl * @return */ public R aliScanCodePay(String body, String subject, String outTradeNo, String amount, String notifyUrl){ AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", aliAppid, appPrivateKey, "json", "UTF-8", alipay_public_key, "RSA2"); //获得初始化的AlipayClient AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();//创建API对应的request类 request.setBizContent("{" + " \"out_trade_no\":\"" + outTradeNo + "\"," +//商户订单号 " \"total_amount\":\"" + 1 + "\"," + " \"subject\":\"" + subject + "\"," + " \"notify_url\":\"" + callbackPath + notifyUrl + "\"," + " \"body\":\"" + body + "\"," + " \"store_id\":\"NJ_001\"," + " \"timeout_express\":\"90m\"}");//订单允许的最晚付款时间 AlipayTradePrecreateResponse response = null; try { response = alipayClient.execute(request); } catch (AlipayApiException e) { e.printStackTrace(); } JSONObject alipay_trade_precreate_response = JSON.parseObject(response.getBody()).getJSONObject("alipay_trade_precreate_response"); System.err.print(alipay_trade_precreate_response.getString("qr_code")); return R.ok(alipay_trade_precreate_response.getString("qr_code")); } /** * 支付成功后的回调处理逻辑 * @param request */ public Map<String, String> alipayCallback(HttpServletRequest request){ //获取支付宝POST过来反馈信息 Map<String,String> params = new HashMap<String,String>(); Map requestParams = request.getParameterMap(); for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) { String name = (String) iter.next(); String[] values = (String[]) requestParams.get(name); String valueStr = ""; for (int i = 0; i < values.length; i++) { valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + "_"; } //乱码解决,这段代码在出现乱码时使用。 //valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8"); params.put(name, valueStr); } //切记alipaypublickey是支付宝的公钥,请去open.alipay.com对应应用下查看。 //boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type) // try { // boolean flag = AlipaySignature.rsaCheckV1(params, alipay_public_key, "UTF-8","RSA2"); // if(flag){ // Map<String, String> map = new HashMap<>(); // String out_trade_no = params.get("out_trade_no"); // String subject = params.get("subject"); // String total_amount = params.get("total_amount"); // String trade_no = params.get("trade_no"); // String passback_params = params.get("passback_params"); // map.put("out_trade_no", out_trade_no);//商家订单号 // map.put("subject", subject); // map.put("total_amount", total_amount); // map.put("trade_no", trade_no);//支付宝交易号 // map.put("passback_params", passback_params);//回传参数 // return map; // }else{ // System.err.println("验签失败"); // } // // } catch (AlipayApiException e) { // e.printStackTrace(); // } // return null; Map<String, String> map = new HashMap<>(); String out_trade_no = params.get("out_trade_no"); String subject = params.get("subject"); String total_amount = params.get("total_amount"); String trade_no = params.get("trade_no"); String passback_params = params.get("passback_params"); map.put("out_trade_no", out_trade_no);//商家订单号 map.put("subject", subject); map.put("total_amount", total_amount); map.put("trade_no", trade_no);//支付宝交易号 map.put("passback_params", passback_params);//回传参数 return map; } /** * 支付宝查询订单支付状态 * @param out_trade_no * @return * @throws Exception */ public R queryALIOrder(String out_trade_no) throws Exception{ AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do",aliAppid, appPrivateKey,"json","UTF-8", alipay_public_key,"RSA2"); AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); request.setBizContent("{" + "\"out_trade_no\":" + out_trade_no + " }"); AlipayTradeQueryResponse response = alipayClient.execute(request); if(response.isSuccess()){ String tradeStatus = response.getTradeStatus();//交易状态:WAIT_BUYER_PAY(交易创建,等待买家付款)、TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、TRADE_SUCCESS(交易支付成功)、TRADE_FINISHED(交易结束,不可退款) return R.ok(tradeStatus); } else { return R.fail(response.getMsg()); } } /** * 微信统一下单 * @param body 商品描述 * @param attach 附加数据 * @param out_trade_no 商户订单号 * @param total_fee 标价金额 * @param notify_url 通知地址 * @param tradeType 交易类型 * @return */ public R weixinpay(String body, String attach, String out_trade_no, String total_fee, String notify_url, String tradeType, String openId) throws Exception{ int i = new BigDecimal(total_fee).multiply(new BigDecimal("100")).intValue(); String hostAddress = null; try { hostAddress = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); } String nonce_str = UUID.randomUUID().toString(); Map<String, Object> map = new HashMap<>(); map.put("appid", "APP".equals(tradeType) ? appid : appletsAppid); map.put("mch_id", mchId); map.put("nonce_str", nonce_str); map.put("body", body); map.put("attach", attach);//存储订单id map.put("out_trade_no", out_trade_no);//存储的订单code map.put("total_fee", i); map.put("spbill_create_ip", hostAddress); map.put("notify_url", callbackPath + notify_url); map.put("trade_type", tradeType); if("JSAPI".equals(tradeType)){ map.put("openid", openId); } String s = this.weixinSignature(map); map.put("sign", s); String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //设置请求头 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_XML); StringBuffer xmlString = new StringBuffer(); Set<String> strings = map.keySet(); String[] keys = {}; keys = strings.toArray(keys); Arrays.sort(keys); xmlString.append("<xml>"); for(int l = 0; l < keys.length; l++){ xmlString.append("<" + keys[l] + ">" + map.get(keys[l]) + "</" + keys[l] + ">"); } xmlString.append("</xml>"); Map<String, String> map1 = null; // String body1 = httpClientUtil.pushHttpRequsetXml(url, xmlString.toString(), new HashMap<>()); String body1 = HttpRequest.post(url) .body(xmlString.toString()) .contentType("application/xml; charset=UTF-8") .timeout(5000) // 设置超时时间 .execute() .body(); //将结果xml解析成map body1 = body1.replaceAll("<!\\[CDATA\\[",""); body1 = body1.replaceAll("]]>", ""); try { map1 = this.xmlToMap(body1, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } String return_code = map1.get("return_code"); if("SUCCESS".equals(return_code)){ String result_code = map1.get("result_code"); if("SUCCESS".equals(result_code)){ String type = map1.get("trade_type"); String prepay_id = map1.get("prepay_id"); switch (type){ case "JSAPI": //重新进行签名后返回给前端 Map<String, Object> map2 = new HashMap<>(); map2.put("appId", map1.get("appid")); map2.put("nonceStr", map1.get("nonce_str")); map2.put("package", "prepay_id=" + prepay_id); map2.put("signType", "MD5"); map2.put("timeStamp", new Date().getTime() + ""); String s2 = this.weixinSignature(map2); map2.put("prepay_id", prepay_id); map2.put("mch_id", map1.get("mch_id")); map2.put("trade_type", map1.get("trade_type")); map2.put("sign", s2); return R.ok(map2); case "NATIVE": String code_url = map1.get("code_url"); return R.ok(code_url); case "APP": //重新进行签名后返回给前端 Map<String, Object> map3 = new HashMap<>(); map3.put("appid", appid); map3.put("noncestr", nonce_str); map3.put("package", "Sign=WXPay"); map3.put("partnerid", mchId); map3.put("prepayid", prepay_id); map3.put("timestamp", new Date().getTime() / 1000); String s1 = this.weixinSignature(map3); map3.put("sign", s1); System.err.println(map3); return R.ok(map3); } return null; }else{ System.err.println(map1.get("err_code_des")); return R.fail(map1.get("err_code_des")); } }else{ System.err.println(map1.get("return_msg") + appid + "----" + mchId); return R.fail(map1.get("return_msg")); } } /** * 微信支付成功后的回调处理 * @param request */ public Map<String, String> weixinpayCallback(HttpServletRequest request){ try { String param = this.getParam(request); param = param.replaceAll("<!\\[CDATA\\[",""); param = param.replaceAll("]]>", ""); Map<String, String> map = this.xmlToMap(param, "UTF-8"); String return_code = map.get("return_code"); if("SUCCESS".equals(return_code)){ String result_code = map.get("result_code"); if("SUCCESS".equals(result_code)){ Map<String, String> map1 = new HashMap<>(); map1.put("nonce_str", map.get("nonce_str")); map1.put("out_trade_no", map.get("out_trade_no"));//存储的订单code map1.put("attach", map.get("attach"));//存储订单id map1.put("total_fee", map.get("total_fee")); map1.put("transaction_id", map.get("transaction_id"));//微信支付订单号 String result = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; map1.put("result", result); return map1; }else{ System.err.println(map.get("err_code_des")); } }else{ System.err.println(map.get("return_msg")); } } catch (IOException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } return null; } /** * 微信扫码收款 * @param body 商品描述 * @param attach 附加数据 * @param nonce_str 随机字符串 * @param out_trade_no 商户订单号 * @param total_fee 订单金额 * @param auth_code 授权码 扫码支付授权码,设备读取用户微信中的条码或者二维码信息(注:用户付款码条形码规则:18位纯数字,以10、11、12、13、14、15开头) * @return */ public R wxScanQRCodePay(String body, String attach, String nonce_str, String out_trade_no, String total_fee, String auth_code){ int i = new BigDecimal(total_fee).multiply(new BigDecimal("100")).intValue(); String hostAddress = null; try { InetAddress address = InetAddress.getLocalHost(); hostAddress = address.getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); } String randomCode = null; try { randomCode = UUID.randomUUID().toString(); } catch (Exception e) { e.printStackTrace(); } Map<String, Object> map = new HashMap<>(); map.put("appid", appid); map.put("mch_id", mchId); map.put("nonce_str", nonce_str);//存储的支付人员id,员工扫描二维码支付的时候存储的是收款员工id map.put("body", body); map.put("attach", attach);//存储的费用月份数据,员工扫描二维码支付的时候存储的是收费项id map.put("out_trade_no", randomCode + "_" + out_trade_no);//存储的房间id map.put("total_fee", i); map.put("spbill_create_ip", hostAddress); map.put("auth_code", auth_code); String s = this.weixinSignature(map); map.put("sign", s); String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //设置请求头 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_XML); StringBuffer xmlString = new StringBuffer(); Set<String> strings = map.keySet(); String[] keys = {}; keys = strings.toArray(keys); Arrays.sort(keys); xmlString.append("<xml>"); for(int l = 0; l < keys.length; l++){ xmlString.append("<" + keys[l] + ">" + map.get(keys[l]) + "</" + keys[l] + ">"); } xmlString.append("</xml>"); Map<String, String> map1 = null; // String body1 = httpClientUtil.pushHttpRequsetXml(url, xmlString.toString(), new HashMap<>()); String body1 = HttpRequest.post(url) .body(xmlString.toString()) .contentType("application/xml; charset=UTF-8") .timeout(5000) // 设置超时时间 .execute() .body(); //将结果xml解析成map body1 = body1.replaceAll("<!\\[CDATA\\[",""); body1 = body1.replaceAll("]]>", ""); try { map1 = this.xmlToMap(body1, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } String return_code = map1.get("return_code"); if("SUCCESS".equals(return_code)){ String result_code = map1.get("result_code"); if("SUCCESS".equals(result_code)){ String type = map1.get("trade_type"); switch (type){ case "JSAPI": break; case "NATIVE": String code_url = map1.get("code_url"); return R.ok(code_url); case "APP": String prepay_id = map1.get("prepay_id"); //重新进行签名后返回给前端 Map<String, Object> map2 = new HashMap<>(); map2.put("appid", appid); map2.put("noncestr", nonce_str); map2.put("package", "Sign=WXPay"); map2.put("partnerid", mchId); map2.put("prepayid", prepay_id); map2.put("timestamp", new Date().getTime() + ""); String s1 = this.weixinSignature(map2); map2.put("pac", "Sign=WXPay"); map2.put("sign", s1); // System.err.println(map2); return R.ok(map2); } return null; }else{ // System.err.println(map1.get("err_code_des")); return R.fail(map1.get("err_code_des")); } }else{ // System.err.println(map1.get("return_msg") + appid + "----" + mchId); return R.fail(map1.get("return_msg")); } } /** * 支付宝扫码收款 * @param data * @return */ public Object aliScanQRCodePay(String data){ return null; } /** * 微信退款申请 * @param transaction_id 微信订单号。微信生成的订单号,在支付通知中有返回 * @param out_refund_no 商户退款单号。商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。 * @param total_fee 订单金额。订单总金额,单位为分,只能为整数 * @param refund_fee 退款金额。退款总金额,订单总金额,单位为分,只能为整数 * @param notify_url 退款结果通知url。异步接收微信支付退款结果通知的回调地址,通知URL必须为外网可访问的url,不允许带参数 如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效。 * @return */ public Map<String, String> wxRefund(String transaction_id, String out_refund_no, String total_fee, String refund_fee, String notify_url){ int tf = new BigDecimal(total_fee).multiply(new BigDecimal("100")).intValue(); int rf = new BigDecimal(refund_fee).multiply(new BigDecimal("100")).intValue(); String nonce_str = UUID.randomUUID().toString(); Map<String, Object> map = new HashMap<>(); map.put("appid", appid); map.put("mch_id", mchId); map.put("nonce_str", nonce_str); map.put("transaction_id", transaction_id); map.put("out_refund_no", out_refund_no); map.put("total_fee", tf); map.put("refund_fee", rf); map.put("notify_url", callbackPath + notify_url); String s = this.weixinSignature(map, key); map.put("sign", s); String url = "https://api.mch.weixin.qq.com/secapi/pay/refund"; //设置请求头 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_XML); StringBuffer xmlString = new StringBuffer(); Set<String> strings = map.keySet(); String[] keys = {}; keys = strings.toArray(keys); Arrays.sort(keys); xmlString.append("<xml>"); for(int l = 0; l < keys.length; l++){ xmlString.append("<" + keys[l] + ">" + map.get(keys[l]) + "</" + keys[l] + ">"); } xmlString.append("</xml>"); Map<String, String> map1 = null; String body1 = null; try { body1 = sendHttpsRequestWithCert(url, xmlString.toString()); // String certPath = "E:\\cert\\1523106371_20211206_cert\\apiclient_cert.p12"; // // body1 = httpClientUtil.pushHttpsRequsetXml(url, xmlString.toString(), new HashMap<>(), "1717539630", certPath, "PKCS12"); } catch (Exception e) { System.err.println("微信退款请求失败:"+ e.getMessage()); e.printStackTrace(); } System.err.println(body1); //将结果xml解析成map body1 = body1.replaceAll("<!\\[CDATA\\[",""); body1 = body1.replaceAll("]]>", ""); try { map1 = this.xmlToMap(body1, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } String return_code = map1.get("return_code"); Map<String, String> map2 = new HashMap<>(); if("SUCCESS".equals(return_code)){ String result_code = map1.get("result_code"); if("SUCCESS".equals(result_code)){ map2.put("return_code", result_code); map2.put("refund_id", String.valueOf(map1.get("refund_id")));//微信退款订单号 map2.put("refund_fee", String.valueOf(map1.get("refund_fee")));//退款金额 return map2; }else{ map2.put("return_code", result_code); map2.put("return_msg", map1.get("err_code_des")); return map2; } }else{ map2.put("return_code", return_code); map2.put("return_msg", map1.get("return_msg")); return map2; } } /** * 发送带商户证书的 HTTPS 请求 */ private String sendHttpsRequestWithCert(String url, String xmlBody) throws Exception { KeyStore keyStore = KeyStore.getInstance("PKCS12"); try (FileInputStream fis = new FileInputStream(certPath)) { keyStore.load(fis, mchId.toCharArray()); // 证书密码为商户号 } // 初始化SSL上下文 String keyManagerAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keyManagerAlgorithm); keyManagerFactory.init(keyStore, mchId.toCharArray()); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init( keyManagerFactory.getKeyManagers(), // 客户端密钥管理器(包含商户证书和私钥) null, // 信任管理器(使用默认信任库) new SecureRandom() // 安全随机数生成器 ); // 获取SSLSocketFactory SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); // 发送请求(此处假设使用Hutool的HttpRequest,但需调整SSLSocketFactory设置) // 注意:Hutool的HttpRequest仍可使用,但需传入原生的SSLSocketFactory HttpResponse response = HttpRequest.post(url) .setSSLSocketFactory(sslSocketFactory) // 传入原生SSLSocketFactory .body(xmlBody) .contentType("application/xml; charset=UTF-8") .timeout(10000) .execute(); if (response.getStatus() != 200) { throw new RuntimeException("HTTP 请求失败,状态码:" + response.getStatus()); } return response.body(); } /** * 微信退款成功后的回调处理 * @param request * @return */ public Map<String, String> wxRefundCallback(HttpServletRequest request){ try { String param = this.getParam(request); param = param.replaceAll("<!\\[CDATA\\[",""); param = param.replaceAll("]]>", ""); Map<String, String> map = this.xmlToMap(param, "UTF-8"); String return_code = map.get("return_code"); if("SUCCESS".equals(return_code)){ String req_info = map.get("req_info");//加密信息请用商户秘钥进行解密 String s = this.wxDecrypt(req_info); s = s.replaceAll("<!\\[CDATA\\[",""); s = s.replaceAll("]]>", ""); map = this.xmlToMap(s, "UTF-8"); Map<String, String> map1 = new HashMap<>(); map1.put("refund_id", map.get("refund_id")); map1.put("out_refund_no", map.get("out_refund_no")); String result = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; map1.put("result", result); return map1; }else{ // System.err.println(map.get("return_msg")); } } catch (IOException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } return null; } /** * 支付宝退款 * @param trade_no 支付宝交易号 * @param refund_amount 退款金额 * @return * @throws AlipayApiException */ public Map<String, String> aliRefund(String trade_no, String refund_amount) throws AlipayApiException { // AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", aliAppid, appPrivateKey,"json","UTF-8", alipay_public_key,"RSA2"); // AlipayTradeRefundRequest request = new AlipayTradeRefundRequest(); // JSONObject jsonObject = new JSONObject(); // jsonObject.put("trade_no", trade_no); // jsonObject.put("refund_amount", refund_amount); // request.setBizContent(jsonObject.toJSONString()); // AlipayTradeRefundResponse response = alipayClient.execute(request); // Map<String, String> map = new HashMap<>(); // if(response.isSuccess()){ // System.out.println("调用成功"); // String outTradeNo = response.getOutTradeNo(); // map.put("code", response.getCode());//10000 // map.put("trade_no", response.getTradeNo());//支付宝交易号 // map.put("out_trade_no", outTradeNo);//商户订单号 // } else { // System.out.println("调用失败"); // map.put("code", response.getCode()); // map.put("msg", response.getSubMsg()); // } // return map; //构造client CertAlipayRequest certAlipayRequest = new CertAlipayRequest (); //设置网关地址 certAlipayRequest.setServerUrl("https://openapi.alipay.com/gateway.do"); //设置应用Id certAlipayRequest.setAppId(aliAppid); //设置应用私钥 certAlipayRequest.setPrivateKey(appPrivateKey); //设置请求格式,固定值json certAlipayRequest.setFormat("json"); //设置字符集 certAlipayRequest.setCharset("UTF-8"); //设置签名类型 certAlipayRequest.setSignType("RSA2"); //设置应用公钥证书路径 certAlipayRequest.setCertPath(app_cert_path); //设置支付宝公钥证书路径 certAlipayRequest.setAlipayPublicCertPath(alipay_cert_path); //设置支付宝根证书路径 certAlipayRequest.setRootCertPath(alipay_root_cert_path); //构造client AlipayClient alipayClient = null; try { alipayClient = new DefaultAlipayClient(certAlipayRequest); } catch (AlipayApiException e) { e.printStackTrace(); } //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay AlipayTradeRefundRequest request = new AlipayTradeRefundRequest (); //SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。 AlipayTradeRefundModel model = new AlipayTradeRefundModel (); model.setTradeNo(trade_no); model.setRefundAmount(refund_amount); request.setBizModel ( model ); try { //这里和普通的接口调用不同,使用的是sdkExecute AlipayTradeRefundResponse response = alipayClient.certificateExecute(request); Map<String, String> map = new HashMap<>(); if(response.isSuccess()){ System.out.println("调用成功"); String outTradeNo = response.getOutTradeNo(); map.put("code", response.getCode());//10000 map.put("trade_no", response.getTradeNo());//支付宝交易号 map.put("out_trade_no", outTradeNo);//商户订单号 } else { System.out.println("调用失败"); map.put("code", response.getCode()); map.put("msg", response.getSubMsg()); } return map; } catch (AlipayApiException e ) { e.printStackTrace(); } return null; } /** * 查询微信支付订单 * @return * @throws Exception */ public R queryWXOrder() throws Exception{ String url = "https://api.mch.weixin.qq.com/pay/orderquery"; String nonce_str = UUID.randomUUID().toString(); Map<String, Object> map = new HashMap<>(); map.put("appid", appid); map.put("mch_id", mchId); map.put("transaction_id", nonce_str);//微信订单号 map.put("nonce_str", nonce_str);//随机字符串 String s = this.weixinSignature(map); map.put("sign", s); //设置请求头 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_XML); StringBuffer xmlString = new StringBuffer(); Set<String> strings = map.keySet(); String[] keys = {}; keys = strings.toArray(keys); Arrays.sort(keys); xmlString.append("<xml>"); for(int l = 0; l < keys.length; l++){ xmlString.append("<" + keys[l] + ">" + map.get(keys[l]) + "</" + keys[l] + ">"); } xmlString.append("</xml>"); Map<String, String> map1 = null; // String body1 = httpClientUtil.pushHttpRequsetXml(url, xmlString.toString(), new HashMap<>()); String body1 = HttpRequest.post(url) .body(xmlString.toString()) .contentType("application/xml; charset=UTF-8") .timeout(5000) // 设置超时时间 .execute() .body(); //将结果xml解析成map body1 = body1.replaceAll("<!\\[CDATA\\[",""); body1 = body1.replaceAll("]]>", ""); try { map1 = this.xmlToMap(body1, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } String return_code = map1.get("return_code"); if("SUCCESS".equals(return_code)){ String result_code = map1.get("result_code"); if("SUCCESS".equals(result_code)){ String type = map1.get("trade_type"); switch (type){ case "JSAPI": break; case "NATIVE": String code_url = map1.get("code_url"); return R.ok(code_url); case "APP": String trade_state = map1.get("trade_state"); String time_end = map1.get("time_end"); Map<String, Object> map2 = new HashMap<>(); map2.put("trade_state", trade_state);//订单状态SUCCESS—支付成功,REFUND—转入退款,NOTPAY—未支付,CLOSED—已关闭,REVOKED—已撤销(刷卡支付),USERPAYING--用户支付中,PAYERROR--支付失败(其他原因,如银行返回失败) map2.put("time_end", time_end);//订单支付时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。 return R.ok(map2); } return null; }else{ System.err.println(map1.get("err_code_des")); return R.fail(map1.get("err_code_des")); } }else{ System.err.println(map1.get("return_msg") + appid + "----" + mchId); return R.fail(map1.get("return_msg")); } } /** * 微信转账功能(企业付款到零钱) * @param openid 商户appid下,某用户的openid * @param desc 企业付款备注,必填。 * @param total_fee 企业付款金额 * @param partner_trade_no 商户订单号,需保持唯一性 * @return */ public Map<String, String> wxTransfers(String openid, String desc, String total_fee, String partner_trade_no) throws Exception{ int amount = new BigDecimal(total_fee).multiply(new BigDecimal("100")).intValue(); String nonce_str = UUID.randomUUID().toString(); Map<String, Object> map = new HashMap<>(); map.put("mch_appid", appid);//申请商户号的appid或商户号绑定的appid map.put("mchid", mchId);//微信支付分配的商户号 map.put("nonce_str", nonce_str);//随机字符串,不长于32位 map.put("partner_trade_no", partner_trade_no);//商户订单号,需保持唯一性 map.put("openid", openid);//商户appid下,某用户的openid map.put("check_name", "NO_CHECK");//NO_CHECK:不校验真实姓名 FORCE_CHECK:强校验真实姓名 map.put("amount", amount);//企业付款金额,单位为分 map.put("desc", desc);//企业付款备注,必填。 String s = this.weixinSignature(map, key); map.put("sign", s); String url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"; //设置请求头 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_XML); StringBuffer xmlString = new StringBuffer(); Set<String> strings = map.keySet(); String[] keys = {}; keys = strings.toArray(keys); Arrays.sort(keys); xmlString.append("<xml>"); for(int l = 0; l < keys.length; l++){ xmlString.append("<" + keys[l] + ">" + map.get(keys[l]) + "</" + keys[l] + ">"); } xmlString.append("</xml>"); Map<String, String> map1 = null; String certPath = "C:\\cert\\1523106371_20211206_cert\\apiclient_cert.p12";//证书地址 String body1 = sendHttpsRequestWithCert(url, xmlString.toString()); // String body1 = httpClientUtil.pushHttpsRequsetXml(url, xmlString.toString(), new HashMap<>(), "1523106371", certPath, "PKCS12"); //将结果xml解析成map body1 = body1.replaceAll("<!\\[CDATA\\[",""); body1 = body1.replaceAll("]]>", ""); try { map1 = this.xmlToMap(body1, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } String return_code = map1.get("return_code"); Map<String, String> map2 = new HashMap<>(); if("SUCCESS".equals(return_code)){ String result_code = map1.get("result_code"); if("SUCCESS".equals(result_code)){ map2.put("return_code", result_code); map2.put("payment_no", String.valueOf(map1.get("payment_no")));//付款订单号 map2.put("payment_time", String.valueOf(map1.get("payment_time")));//付款时间 return map2; }else{ map2.put("return_code", result_code); map2.put("err_code", map1.get("err_code")); map2.put("err_code_des", map1.get("err_code_des")); return map2; } }else{ map2.put("return_code", return_code); map2.put("return_msg", map1.get("return_msg")); return map2; } } /** * 微信转账功能(企业付款到银行卡) * @param desc 备注信息 * @param total_fee 转账金额 * @param partner_trade_no 订单号 * @param enc_bank_no 银行卡号 * @param enc_true_name 收款方用户名 * @param bankName 银行名称 * @return * @throws Exception */ public Map<String, String> wxPayBank(String desc, String total_fee, String partner_trade_no, String enc_bank_no, String enc_true_name, String bankName) throws Exception{ int amount = new BigDecimal(total_fee).multiply(new BigDecimal("100")).intValue(); String nonce_str = UUID.randomUUID().toString(); Map<String, Object> map = new HashMap<>(); map.put("mch_id", mchId);//微信支付分配的商户号 map.put("nonce_str", nonce_str);//随机字符串,不长于32位 map.put("partner_trade_no", partner_trade_no);//商户订单号,需保持唯一性 map.put("enc_bank_no", enc_bank_no);//收款方银行卡号(采用标准RSA算法,公钥由微信侧提供) map.put("enc_true_name", enc_true_name);//收款方用户名(采用标准RSA算法,公钥由微信侧提供) map.put("bank_code", findBankCode(bankName));// map.put("amount", amount);//企业付款金额,单位为分 map.put("desc", desc);//企业付款备注,必填。 String s = this.weixinSignature(map, key); map.put("sign", s); String url = "https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank"; //设置请求头 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_XML); StringBuffer xmlString = new StringBuffer(); Set<String> strings = map.keySet(); String[] keys = {}; keys = strings.toArray(keys); Arrays.sort(keys); xmlString.append("<xml>"); for(int l = 0; l < keys.length; l++){ xmlString.append("<" + keys[l] + ">" + map.get(keys[l]) + "</" + keys[l] + ">"); } xmlString.append("</xml>"); Map<String, String> map1 = null; // String certPath = "C:\\cert\\1523106371_20211206_cert\\apiclient_cert.p12";//证书地址 // String body1 = httpClientUtil.pushHttpsRequsetXml(url, xmlString.toString(), new HashMap<>(), "1523106371", certPath, "PKCS12"); String body1 = sendHttpsRequestWithCert(url, xmlString.toString()); //将结果xml解析成map body1 = body1.replaceAll("<!\\[CDATA\\[",""); body1 = body1.replaceAll("]]>", ""); try { map1 = this.xmlToMap(body1, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } String return_code = map1.get("return_code"); Map<String, String> map2 = new HashMap<>(); if("SUCCESS".equals(return_code)){ String result_code = map1.get("result_code"); if("SUCCESS".equals(result_code)){ map2.put("return_code", result_code); map2.put("payment_no", String.valueOf(map1.get("payment_no")));//付款订单号 map2.put("cmms_amt", String.valueOf(map1.get("cmms_amt")));//手续费金额 RMB:分 return map2; }else{ map2.put("return_code", result_code); map2.put("err_code", map1.get("err_code")); map2.put("err_code_des", map1.get("err_code_des")); return map2; } }else{ map2.put("return_code", return_code); map2.put("return_msg", map1.get("return_msg")); return map2; } } /** * 微信转账到银行卡不编号 * @param bankName * @return */ public String findBankCode(String bankName){ String json = "{\"工商银行 \":1002,\"农业银行\":1005,\"建设银行\":1003,\"中国银行\":1026,\"交通银行 \":1020,\"招商银行 \":1001,\"邮储银行\":1066,\"民生银行 \":1006,\"平安银行 \":1010,\"中信银行\":1021,\"浦发银行 \":1004,\"兴业银行 \":1009,\"光大银行 \":1022,\"广发银行\":1027,\"华夏银行\":1025,\"宁波银行\":1056,\"北京银行\":4836,\"上海银行\":1024,\"南京银行\":1054,\"长子县融汇村镇银行\":4755,\"长沙银行\":4216,\"浙江泰隆商业银行\":4051,\"中原银行 \":4753,\"企业银行(中国)\":4761,\"顺德农商银行 \":4036,\"衡水银行\":4752,\"长治银行\":4756,\"大同银行\":4767,\"河南省农村信用社\":4115,\"宁夏黄河农村商业银行\":4150,\"山西省农村信用社\":4156,\"安徽省农村信用社\":4166,\"甘肃省农村信用社\":4157,\"天津农村商业银行\":4153,\"广西壮族自治区农村信用社\":4113,\"陕西省农村信用社\":4108,\"深圳农村商业银行\":4076,\"宁波鄞州农村商业银行\":4052,\"浙江省农村信用社联合社\":4764,\"江苏省农村信用社联合社\":4217,\"江苏紫金农村商业银行股份有限公司 \":4072,\"北京中关村银行股份有限公司 \":4769,\"星展银行( 中国) 有限公司 \":4778,\"枣庄银行股份有限公司 \":4766,\"海口联合农村商业银行股份有限公司 \":4758,\"南洋商业银行( 中国) 有限公司 \":4763}"; JSONObject jsonObject = JSON.parseObject(json); Set<String> strings = jsonObject.keySet(); for(String key : strings){ if(key.indexOf(bankName) >= 0){ return jsonObject.getString(key); } } return ""; } /** * 支付宝转账 * @param out_biz_no 商家侧唯一订单号,由商家自定义。对于不同转账请求,商家需保证该订单号在自身系统唯一。 * @param trans_amount 订单总金额,单位为元,精确到小数点后两位 * @param order_title 转账业务的标题,用于在支付宝用户的账单里显示 * @param identity 参与方的唯一标识(收款方支付宝账号) * @param name 参与方真实姓名,如果非空,将校验收款支付宝账号姓名一致性。 * @param remark 业务备注 * @return * @throws Exception */ public Map<String, Object> aliTransfer(String out_biz_no, Double trans_amount, String order_title, String identity, String name, String remark) throws Exception{ CertAlipayRequest certAlipayRequest = new CertAlipayRequest(); certAlipayRequest.setServerUrl("https://openapi.alipay.com/gateway.do"); //gateway:支付宝网关(固定)https://openapi.alipay.com/gateway.do certAlipayRequest.setAppId(aliAppid); //APPID 即创建应用后生成,详情见创建应用并获取 APPID certAlipayRequest.setPrivateKey(appPrivateKey); //开发者应用私钥,由开发者自己生成 certAlipayRequest.setFormat("json"); //参数返回格式,只支持 json 格式 certAlipayRequest.setCharset("UTF-8"); //请求和签名使用的字符编码格式,支持 GBK和 UTF-8 certAlipayRequest.setSignType("RSA2"); //商户生成签名字符串所使用的签名算法类型,目前支持 RSA2 和 RSA,推荐商家使用 RSA2。 certAlipayRequest.setCertPath(app_cert_path); //应用公钥证书路径(app_cert_path 文件绝对路径) certAlipayRequest.setAlipayPublicCertPath(alipay_cert_path); //支付宝公钥证书文件路径(alipay_cert_path 文件绝对路径) certAlipayRequest.setRootCertPath(alipay_root_cert_path); //支付宝CA根证书文件路径(alipay_root_cert_path 文件绝对路径) AlipayClient alipayClient = new DefaultAlipayClient(certAlipayRequest); AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest(); request.setBizContent("{" + "\"out_biz_no\":\"" + out_biz_no + "\"," + "\"trans_amount\":" + trans_amount + "," + "\"product_code\":\"TRANS_ACCOUNT_NO_PWD\"," + "\"biz_scene\":\"DIRECT_TRANSFER\"," + "\"order_title\":\"" + order_title + "\"," + "\"payee_info\":{" + "\"identity\":\"" + identity + "\"," + "\"identity_type\":\"ALIPAY_USER_ID\"," + "\"name\":\"" + name + "\"," + "}," + "\"remark\":\"" + remark + "\"" + "}"); AlipayFundTransUniTransferResponse response = alipayClient.certificateExecute(request); Map<String, Object> map = new HashMap<>(); if(response.isSuccess()){ String status = response.getStatus(); if(status.equals("SUCCESS")){//成功 map.put("code", response.getCode()); map.put("order_id", response.getOrderId());//支付宝订单号 map.put("pay_fund_order_id", response.getPayFundOrderId());//支付宝流水号 }else{ map.put("code", response.getCode()); map.put("sub_msg", response.getSubMsg()); } } else { map.put("code", response.getSubCode()); map.put("sub_msg", response.getSubMsg()); } return map; } /** * 获取请求内容 * @param request * @return * @throws IOException */ private String getParam(HttpServletRequest request) throws IOException { // 读取参数 InputStream inputStream; StringBuilder sb = new StringBuilder(); inputStream = request.getInputStream(); String s; BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); while ((s = in.readLine()) != null) { sb.append(s); } in.close(); inputStream.close(); return sb.toString(); } /** * 微信下单的签名算法 * @param map * @return */ private String weixinSignature(Map<String, Object> map){ try { Set<Map.Entry<String, Object>> entries = map.entrySet(); List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(entries); // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序) Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() { public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) { return (o1.getKey()).toString().compareTo(o2.getKey()); } }); // 构造签名键值对的格式 StringBuilder sb = new StringBuilder(); for (Map.Entry<String, Object> item : infoIds) { if (item.getKey() != null || item.getKey() != "") { String key = item.getKey(); Object val = item.getValue(); if (!(val == "" || val == null)) { sb.append(key + "=" + val + "&"); } } } sb.append("key=" + key); String sign = MD5AndKL.MD5Encode(sb.toString(), "UTF-8").toUpperCase(); //注:MD5签名方式 return sign; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 微信下单的签名算法 * @param map * @return */ private String weixinSignature(Map<String, Object> map, String key_){ try { Set<Map.Entry<String, Object>> entries = map.entrySet(); List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(entries); // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序) Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() { public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) { return (o1.getKey()).toString().compareTo(o2.getKey()); } }); // 构造签名键值对的格式 StringBuilder sb = new StringBuilder(); for (Map.Entry<String, Object> item : infoIds) { if (item.getKey() != null || item.getKey() != "") { String key = item.getKey(); Object val = item.getValue(); if (!(val == "" || val == null)) { sb.append(key + "=" + val + "&"); } } } sb.append("key=" + key_); String sign = MD5AndKL.MD5Encode(sb.toString(), "UTF-8").toUpperCase(); //注:MD5签名方式 return sign; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 微信退款成功后的解密 * @param req_info * @return */ private String wxDecrypt(String req_info) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { byte[] decode = Base64.getDecoder().decode(req_info); String sign = MD5AndKL.MD5Encode(key, "UTF-8").toLowerCase(); if (Security.getProvider("BC") == null){ Security.addProvider(new BouncyCastleProvider()); } Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC"); SecretKeySpec secretKeySpec = new SecretKeySpec(sign.getBytes(), "AES"); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); return new String(cipher.doFinal(decode)); } public static void main(String[] ages){ // PayMoneyUtil payMoneyUtil = new PayMoneyUtil(); // payMoneyUtil.weixinpay("测试", "123", "12.5", ""); } /** * xml转map * @param xml * @param charset * @return * @throws UnsupportedEncodingException * @throws DocumentException */ public static Map<String, String> xmlToMap(String xml, String charset) throws UnsupportedEncodingException, DocumentException { Map<String, String> respMap = new HashMap<String, String>(); SAXReader reader = new SAXReader(); Document doc = reader.read(new ByteArrayInputStream(xml.getBytes(charset))); Element root = doc.getRootElement(); xmlToMap(root, respMap); return respMap; } public static Map<String, String> xmlToMap(Element tmpElement, Map<String, String> respMap){ if (tmpElement.isTextOnly()) { respMap.put(tmpElement.getName(), tmpElement.getText()); return respMap; } @SuppressWarnings("unchecked") Iterator<Element> eItor = tmpElement.elementIterator(); while (eItor.hasNext()) { Element element = eItor.next(); xmlToMap(element, respMap); } return respMap; } } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wx/HttpUtil.java
New file @@ -0,0 +1,98 @@ package com.ruoyi.order.util.payment.wx; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import javax.net.ssl.*; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; /** * HTTP工具类 */ public class HttpUtil { /** * 发送POST请求 */ public static String post(String urlStr, String data) throws Exception { // 设置超时时间(单位:毫秒) int timeout = 5000; // 5秒 // 发送 POST 请求 try (HttpResponse response = HttpRequest.post(urlStr) .body(data, "application/xml") // 设置 XML 请求体 .timeout(timeout) .execute()) { // 检查 HTTP 状态码 if (!response.isOk()) { throw new RuntimeException("HTTP请求失败,状态码: " + response.getStatus() + ", 响应: " + response.body()); } return response.body(); } } /** * 发送HTTPS请求 */ public static String postHttps(String urlStr, String data, String certPath, String certPassword) throws Exception { // 创建SSL上下文 SSLContext sslContext = SSLContext.getInstance("SSL"); TrustManager[] trustManagers = {new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } }}; sslContext.init(null, trustManagers, new java.security.SecureRandom()); // 创建HTTPS连接 URL url = new URL(urlStr); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setSSLSocketFactory(sslContext.getSocketFactory()); conn.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }); conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); conn.setRequestProperty("Content-Type", "application/xml"); conn.setRequestProperty("Connection", "Keep-Alive"); conn.setRequestProperty("Charset", "UTF-8"); // 发送请求 OutputStream os = conn.getOutputStream(); os.write(data.getBytes("UTF-8")); os.flush(); os.close(); // 获取响应 StringBuilder result = new StringBuilder(); BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); String line; while ((line = br.readLine()) != null) { result.append(line); } br.close(); return result.toString(); } } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wx/WechatPayConfig.java
New file @@ -0,0 +1,26 @@ package com.ruoyi.order.util.payment.wx; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * 微信支付配置类 */ @Data @Component @ConfigurationProperties(prefix = "wx") public class WechatPayConfig { // 小程序APPID private String appId; // 商户号 private String mchId; // 商户API密钥 private String key; // 支付结果通知地址 private String callbackPath; // 证书路径 private String certPath; // 商户RAS加密公钥路径 private String RASPath; } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wx/WechatPayService.java
New file @@ -0,0 +1,742 @@ package com.ruoyi.order.util.payment.wx; import com.alibaba.fastjson2.JSON; import com.ruoyi.common.core.domain.R; import com.ruoyi.order.util.payment.MD5AndKL; import com.ruoyi.order.util.payment.wx.vo.PayResult; import com.ruoyi.order.util.payment.wx.vo.RefundCallbackResult; import org.apache.commons.codec.digest.DigestUtils; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.paddings.PKCS7Padding; import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.dom4j.DocumentException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import sun.misc.BASE64Decoder; import sun.security.util.DerInputStream; import sun.security.util.DerValue; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; import java.net.URL; import java.net.URLEncoder; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.*; import java.security.spec.RSAPublicKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.*; import java.util.stream.Collectors; /** * 微信支付服务类 */ @Service public class WechatPayService { @Autowired private WechatPayConfig wechatPayConfig; private static final String RSA_PUBLIC_KEY_FILENAME = "wechat_rsa_public_key.pem"; private static final String CERT_FOLDER = "cert/"; /** * 统一下单 * @param orderNumber 订单号 * @param totalFee 总金额(分) * @param body 商品描述 * @param openid 用户openid * @return 预支付订单信息 */ public R unifiedOrder(String orderId,String orderNumber, String totalFee, String body, String openid, String callbackPath) throws Exception { int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue(); String hostAddress = null; try { hostAddress = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); } // 构建请求参数 Map<String, String> params = new HashMap<>(); params.put("appid", wechatPayConfig.getAppId()); params.put("mch_id", wechatPayConfig.getMchId()); params.put("nonce_str", generateNonceStr()); params.put("body", body); params.put("out_trade_no", orderNumber); params.put("total_fee", String.valueOf(i) ); params.put("spbill_create_ip", "221.182.45.100"); // 实际应用中应获取客户端IP params.put("notify_url", wechatPayConfig.getCallbackPath()+callbackPath); params.put("trade_type", "JSAPI"); params.put("openid", openid); // 生成签名 String sign = weixinSignature(params); params.put("sign", sign); // 将参数转换为XML String xmlParams = XMLUtil.mapToXml(params).replaceFirst("^<\\?xml.+?\\?>\\s*", ""); // 发送请求到微信支付统一下单接口 String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; String result = HttpUtil.post(url, xmlParams); // 解析返回结果 Map<String, String> resultMap = XMLUtil.xmlToMap(result); // 验证签名 if (!verifySign(resultMap, wechatPayConfig.getKey())) { return R.fail("微信支付签名验证失败"); // throw new Exception("微信支付签名验证失败"); } if (!resultMap.get("return_code").equals("SUCCESS")) { return R.fail(resultMap.get("return_msg")); } // 构建小程序支付所需参数 Map<String, String> payParams = new HashMap<>(); payParams.put("appId", wechatPayConfig.getAppId()); payParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); payParams.put("nonceStr", generateNonceStr()); payParams.put("package", "prepay_id=" + resultMap.get("prepay_id")); payParams.put("signType", "MD5"); // 生成支付签名 String paySign = weixinSignature(payParams); payParams.put("paySign", paySign); //给前端标识 payParams.put("payMethod","1"); payParams.put("orderId", orderId); return R.ok(JSON.toJSONString(payParams)); } /** * 微信下单的签名算法 * @param map * @return */ private String weixinSignature(Map<String, String> map){ try { Set<Map.Entry<String, String>> entries = map.entrySet(); List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(entries); // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序) Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() { public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) { return (o1.getKey()).toString().compareTo(o2.getKey()); } }); // 构造签名键值对的格式 StringBuilder sb = new StringBuilder(); for (Map.Entry<String, String> item : infoIds) { if (item.getKey() != null || item.getKey() != "") { String key = item.getKey(); Object val = item.getValue(); if (!(val == "" || val == null)) { sb.append(key + "=" + val + "&"); } } } sb.append("key=" + wechatPayConfig.getKey()); String sign = MD5AndKL.MD5Encode(sb.toString(), "UTF-8").toUpperCase(); //注:MD5签名方式 return sign; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 处理支付结果通知 * @param request HTTP请求 * @return 处理结果 */ public PayResult processNotify(HttpServletRequest request) throws Exception { // 读取请求内容 BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)); String line; StringBuilder xml = new StringBuilder(); while ((line = br.readLine()) != null) { xml.append(line); } br.close(); // 解析XML Map<String, String> resultMap = XMLUtil.xmlToMap(xml.toString()); // 验证签名 if (!verifySign(resultMap, wechatPayConfig.getKey())) { throw new Exception("微信支付签名验证失败"); } // 验证支付结果 if (!"SUCCESS".equals(resultMap.get("return_code")) || !"SUCCESS".equals(resultMap.get("result_code"))) { throw new Exception("微信支付结果异常"); } // 处理业务逻辑 PayResult payResult = new PayResult(); payResult.setOrderNumber(resultMap.get("out_trade_no")); payResult.setTransactionId(resultMap.get("transaction_id")); payResult.setTotalFee(resultMap.get("total_fee")); return payResult; } /** * 生成随机字符串 */ private String generateNonceStr() { return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32); } /** * 微信支付API V2签名算法 * @param params 请求参数 * @param apiKey 商户API密钥 * @return 签名结果 */ public static String generateSign(Map<String, String> params, String apiKey) throws Exception { // 1. 过滤空值参数 Map<String, String> filteredParams = new HashMap<>(); for (Map.Entry<String, String> entry : params.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); // 排除sign字段和空值字段 if (!"sign".equals(key) && value != null && !value.isEmpty()) { filteredParams.put(key, value); } } // 2. 按照ASCII码排序参数名 List<String> keys = new ArrayList<>(filteredParams.keySet()); Collections.sort(keys); // 3. 构建签名原始字符串 StringBuilder sb = new StringBuilder(); for (String key : keys) { String value = filteredParams.get(key); sb.append(key).append("=").append(value).append("&"); } // 4. 添加API密钥 sb.append("key=").append(apiKey); // 5. MD5加密并转为大写 MessageDigest md = MessageDigest.getInstance("MD5"); byte[] digest = md.digest(sb.toString().getBytes("UTF-8")); // 6. 转换为十六进制字符串 StringBuilder sign = new StringBuilder(); for (byte b : digest) { sign.append(String.format("%02x", b & 0xff)); } return sign.toString().toUpperCase(); } /** * 验证签名 */ private boolean verifySign(Map<String, String> params, String key) throws Exception { String sign = params.get("sign"); if (StringUtils.isEmpty(sign)) { return false; } // 移除sign字段 Map<String, String> newParams = new HashMap<>(params); newParams.remove("sign"); // 生成新签名 String newSign = generateSign(newParams, key); return sign.equals(newSign); } /** * 关闭订单 */ public Map<String, String> closeOrder(String orderId){ // 构建请求参数 Map<String, String> params = new HashMap<>(); params.put("appid", wechatPayConfig.getAppId()); params.put("mch_id", wechatPayConfig.getMchId()); params.put("nonce_str", generateNonceStr()); params.put("out_trade_no", orderId); // 生成签名 String sign = weixinSignature(params); params.put("sign", sign); // 将参数转换为XML String xmlParams = XMLUtil.mapToXml(params); // 发送请求到微信支付关闭订单接口 String url = "https://api.mch.weixin.qq.com/pay/closeorder"; String result = null; try { result = HttpUtil.post(url, xmlParams); } catch (Exception e) { throw new RuntimeException(e); } // 解析返回结果 try { return XMLUtil.xmlToMap(result); } catch (DocumentException e) { throw new RuntimeException(e); } } /** * 查询订单 */ public Map<String, String> queryOrder(String orderId) throws Exception { // 构建请求参数 Map<String, String> params = new HashMap<>(); params.put("appid", wechatPayConfig.getAppId()); params.put("mch_id", wechatPayConfig.getMchId()); params.put("nonce_str", generateNonceStr()); params.put("out_trade_no", orderId); // 生成签名 String sign = generateSign(params, wechatPayConfig.getKey()); params.put("sign", sign); // 将参数转换为XML String xmlParams = XMLUtil.mapToXml(params); // 发送请求到微信支付查询订单接口 String url = "https://api.mch.weixin.qq.com/pay/orderquery"; String result = HttpUtil.post(url, xmlParams); // 解析返回结果 return XMLUtil.xmlToMap(result); } /** * 申请退款 - 使用证书 */ public Map<String, String> refund(String orderNo, String refundNo, String totalFee, String refundFee, String refundDesc,String callbackPath) { int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue(); int j = new BigDecimal(refundFee).multiply(new BigDecimal("100")).intValue(); try { // 构建请求参数 Map<String, String> params = new HashMap<>(); params.put("appid", wechatPayConfig.getAppId()); params.put("mch_id", wechatPayConfig.getMchId()); params.put("nonce_str", UUID.randomUUID().toString().replaceAll("-", "")); params.put("out_trade_no", orderNo); params.put("out_refund_no", refundNo); params.put("total_fee", String.valueOf(i)); params.put("refund_fee", String.valueOf(j)); params.put("refund_desc", refundDesc); params.put("notify_url", wechatPayConfig.getCallbackPath() + callbackPath); // 退款结果 // 生成签名 String sign = weixinSignature(params); params.put("sign", sign); // 转换为XML String xmlParams = XMLUtil.mapToXml(params); // 使用证书发送请求 String result = postWithCert("https://api.mch.weixin.qq.com/secapi/pay/refund", xmlParams); // 解析结果 Map<String, String> resultMap = XMLUtil.xmlToMap(result); System.out.println("申请退款结果"+resultMap); // 验证签名 if (!verifySign(resultMap, wechatPayConfig.getKey())) { resultMap.put("return_code","FAILED"); resultMap.put("return_msg","申请退款结果签名验证失败"); return resultMap; } return resultMap; } catch (Exception e) { Map<String, String> resultMap=new HashMap<>(); resultMap.put("return_code","FAILED"); resultMap.put("return_msg","申请退款失败"); return resultMap; } } /** * 使用证书发送请求 */ private String postWithCert(String url, String xmlData) throws Exception { // 证书类型为PKCS12 KeyStore keyStore = KeyStore.getInstance("PKCS12"); // 获取证书路径 String certPath = wechatPayConfig.getCertPath(); // 如果是classpath路径,使用ClassLoader加载 if (certPath.startsWith("classpath:")) { String path = certPath.substring("classpath:".length()); try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path)) { if (inputStream == null) { throw new FileNotFoundException("证书文件不存在: " + path); } keyStore.load(inputStream, wechatPayConfig.getMchId().toCharArray()); } } else { // 传统文件路径 try (FileInputStream inputStream = new FileInputStream(new File(certPath))) { keyStore.load(inputStream, wechatPayConfig.getMchId().toCharArray()); } } // 实例化密钥库 & 初始化密钥工厂 SSLContext sslContext = SSLContext.getInstance("TLS"); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, wechatPayConfig.getMchId().toCharArray()); sslContext.init(kmf.getKeyManagers(), null, new SecureRandom()); // 创建HttpsURLConnection对象 URL httpsUrl = new URL(url); HttpsURLConnection conn = (HttpsURLConnection) httpsUrl.openConnection(); conn.setSSLSocketFactory(sslContext.getSocketFactory()); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // 发送请求 OutputStream outputStream = conn.getOutputStream(); outputStream.write(xmlData.getBytes("UTF-8")); outputStream.flush(); outputStream.close(); // 获取响应 InputStream inputStream = conn.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); StringBuilder result = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { result.append(line); } bufferedReader.close(); inputStream.close(); conn.disconnect(); return result.toString(); } /** * 处理退款回调 */ public RefundCallbackResult processRefundCallback(String xmlData) { try { // 1. 解析回调XML数据 if (StringUtils.isEmpty(xmlData)) { return RefundCallbackResult.fail("回调数据为空"); } //2.解析参数 System.out.println(xmlData); System.out.println("----------------------------------------"); Map<String, String> resultMap = XMLUtil.xmlToMap(xmlData); System.out.println(resultMap.get("req_info")); // 3. 检查返回状态 String returnCode = resultMap.get("return_code"); if (!"SUCCESS".equals(returnCode)) { String errMsg = resultMap.get("return_msg"); return RefundCallbackResult.fail("通信失败:" + errMsg); } //4 使用商户API密钥解密req_info(AES-256-CBC算法) String decryptData = decrypt(resultMap.get("req_info"), wechatPayConfig.getKey()); Map<String, String> refundDetail = XMLUtil.xmlToMap(decryptData); // 4. 提取退款信息 String orderNo = refundDetail.get("out_trade_no"); // 原订单号 String refundNo = refundDetail.get("out_refund_no"); // 退款订单号 String refundId = refundDetail.get("refund_id"); // 微信退款ID System.err.println("退款回调成功,订单号:"+orderNo+",退款号:"+refundNo+",状态:{}"+refundId); RefundCallbackResult refundCallbackResult = RefundCallbackResult.success(); refundCallbackResult.setOrderNo(orderNo); refundCallbackResult.setRefundNo(refundId); return refundCallbackResult; } catch (Exception e) { return RefundCallbackResult.fail("系统异常:" + e.getMessage()); } } private static String wxDecrypt(String req_info, String key) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException{ byte[] decode = Base64.getDecoder().decode(req_info); System.out.println(Arrays.toString(decode)); String sign = MD5AndKL.MD5Encode(key, "UTF-8").toLowerCase(); System.out.println(sign); if (Security.getProvider("BC") == null){ Security.addProvider(new BouncyCastleProvider()); } Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC"); SecretKeySpec secretKeySpec = new SecretKeySpec(sign.getBytes(), "AES"); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); return new String(cipher.doFinal(decode)); } /** * 获取RSA加密公钥 */ public String getRsaPublicKey() { int maxRetries = 3; for (int retryCount = 0; retryCount < maxRetries; retryCount++) { try { System.out.println("尝试获取RSA公钥,第" + (retryCount + 1) + "次"); // 构建请求参数 Map<String, String> params = new HashMap<>(); params.put("mch_id", wechatPayConfig.getMchId()); params.put("sign_type", "MD5"); params.put("nonce_str", generateNonceStr()); // 生成签名 String sign = weixinSignature(params); params.put("sign", sign); // 转换为XML String xmlParams = XMLUtil.mapToXml(params); // 打印请求参数 System.out.println("请求参数: " + xmlParams); // 使用证书发送请求(关键修改:使用postWithCert而非普通HTTP请求) String result = postWithCert("https://fraud.mch.weixin.qq.com/risk/getpublickey", xmlParams); // 打印响应结果 System.out.println("响应结果: " + result); // 解析结果 Map<String, String> resultMap = XMLUtil.xmlToMap(result); System.out.println("获取RSA公钥结果: " + resultMap); // 检查返回状态 if (!"SUCCESS".equals(resultMap.get("return_code"))) { throw new Exception("RSA公钥获取失败: " + resultMap.get("return_msg")); } // 保存公钥到本地文件 savePublicKeyToClasspath(resultMap.get("pub_key")); return resultMap.get("pub_key"); } catch (Exception e) { System.err.println("获取RSA公钥异常: " + e.getMessage() + ", 重试次数: " + (retryCount + 1)); e.printStackTrace(); // 如果是最后一次重试,抛出异常 if (retryCount == maxRetries - 1) { return null; } // 重试前等待一段时间 try { Thread.sleep(1000 * (retryCount + 1)); // 指数退避 } catch (InterruptedException ie) { Thread.currentThread().interrupt(); return null; } } } return null; } /** * 保存RSA公钥到文件夹 * @param publicKey RSA公钥内容 */ private static void savePublicKeyToClasspath(String publicKey) throws IOException { // 创建 cert 目录(如果不存在) Path certDir = Paths.get(CERT_FOLDER); if (!Files.exists(certDir)) { Files.createDirectories(certDir); // 自动创建目录 } // 写入公钥文件(UTF-8 编码) Path keyFile = certDir.resolve(RSA_PUBLIC_KEY_FILENAME); Files.write(keyFile, publicKey.getBytes(StandardCharsets.UTF_8)); } /** * 从文件夹加载RSA公钥 * @return RSA公钥内容 */ private static String loadPublicKeyFromClasspath() throws IOException { Path keyFile = Paths.get(CERT_FOLDER + RSA_PUBLIC_KEY_FILENAME); byte[] bytes = Files.readAllBytes(keyFile); // 读取所有字节 return new String(bytes, StandardCharsets.UTF_8); // 转为UTF-8字符串 } /** * 加载公钥 返回PublicKey对象 */ public static PublicKey loadPublicKey(String pemContent) throws Exception { // 读取PEM文件内容 // String pemContent = new String(Files.readAllBytes(Paths.get(CERT_FOLDER + RSA_PUBLIC_KEY_FILENAME)), StandardCharsets.UTF_8); // 移除PEM头尾标记 String publicKeyPEM = pemContent .replace("-----BEGIN RSA PUBLIC KEY-----", "") .replace("-----END RSA PUBLIC KEY-----", "") .replaceAll("\\s", ""); // 去除换行/空格 // 解码Base64 byte[] encoded = Base64.getDecoder().decode(publicKeyPEM); // 手动解析PKCS#1格式 DerInputStream derReader = new DerInputStream(encoded); DerValue[] seq = derReader.getSequence(0); BigInteger modulus = seq[0].getBigInteger(); BigInteger exponent = seq[1].getBigInteger(); RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent); return KeyFactory.getInstance("RSA").generatePublic(keySpec); } /** * 使用RSA-OAEP加密数据 * @param plaintext 待加密的明文 * @return Base64编码的密文 */ public static String encrypt(String plaintext, PublicKey publicKey) throws Exception { Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(encryptedBytes); } /** * 商户付款到银行卡(优先使用本地保存的公钥) * @param partnerTradeNo 商户订单号 * @param bankNo 银行卡号 * @param trueName 银行卡真实姓名 * @param bankCode 银行编码 * @param amount 金额(分) * @param desc 付款说明 * @return 付款结果 */ public Map<String, String> payToBankCard(String partnerTradeNo, String bankNo, String trueName, String bankCode, BigDecimal amount, String desc) throws Exception { int i = amount.multiply(new BigDecimal("100")).intValue(); // 1. 尝试从本地加载RSA公钥 String pubKey = loadPublicKeyFromClasspath(); // 2. 如果本地没有公钥或公钥无效,则从微信获取新公钥 if (pubKey == null || pubKey.isEmpty()) { pubKey = getRsaPublicKey(); } //公钥对象 PublicKey publicKey = loadPublicKey(pubKey); // 3. 使用RSA公钥加密银行卡号和真实姓名 String encryptedBankNo = encrypt(bankNo, publicKey); String encryptedTrueName = encrypt(trueName, publicKey); // 4. 构建请求参数 Map<String, String> params = new HashMap<>(); params.put("mch_id", wechatPayConfig.getMchId()); params.put("partner_trade_no", partnerTradeNo); params.put("enc_bank_no", encryptedBankNo); params.put("enc_true_name", encryptedTrueName); params.put("bank_code", bankCode); params.put("amount", String.valueOf(i)); params.put("desc", desc); params.put("nonce_str", generateNonceStr()); // 生成签名 String sign = weixinSignature(params); params.put("sign", sign); // 将参数转换为XML String xmlParams = XMLUtil.mapToXml(params); // 5. 发送请求到微信支付付款到银行卡接口 String url = "https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank"; String result = postWithCert(url, xmlParams); // 解析返回结果 Map<String, String> resultMap = XMLUtil.xmlToMap(result); // 验证签名 if (!verifySign(resultMap, wechatPayConfig.getKey())) { throw new Exception("付款到银行卡签名验证失败"); } return resultMap; } public static void main(String[] args) throws IOException { String info="CjlaS7RVnPn7zzP5ByZDxUN7OrXGp1/DEdO0qahpIqDH/gTNHb/U7VmrVV0S4lXrIa0N8FEREC3CdIeT4XB5P4D0E8TSURu6J/cD01hFu28f0JDRfeips3vSpTgznRGyCfnUBDPYwyrVeP29Wac7WAb3CCcJf7OZWaweOUkaKjaBRa1GzMZcguSZnQJz0cD5Jb4HbTMvM0VAebfCY9aXdfFBIbm+cPYESo3awqwkNTQeT4V+FViw8f8sjkH0TScMgWBiSKmQC837BLD27yIGklqlYkDP2IMeiNw+b12qCAGszfp2vYd3X+HpViXkQQet3PJWYlAm55R+IgvschP7Ub65XzLINfQrJKrQUXiKKO2LwoSRSwZvfDkR8G8E8X59CnU2XvWKeos5Y0q8ckbJb97yI+09nNgMjYyJoVCVjTFgFMDEQ4+e3CpYRhD6V/3RBp+TvBwszldbRav2XEuCXL2kCJyJEAqLPMNyfYBSNF8z1btjyz0+y/xQQcySKlQInZ710FxSE7KwRSBQ92j9nDdlR7UxCrPVCkEd+GrVNSqqnyjNh1J/rPJPHvvGwkPPq72TKiw6ZgaIgIDhy0/lWHTclo4sjYAWuUVfg3CJ8dqkuQwVZ7i0+NiahIl78RtcUph8NR48yUgBkN7WhCcu5wLbg2tu8Qe0SIwHF+RW1x9Yc8akEkNbMd4xzs8lY5MYEU9V16U8RyWJuwPDph3RnmV8HQ+2hfzmjCvPkBwtfR8P5VdK86OIsHfnfQxAcPM2a86tOBBzFXPrLHgd2CRcDKH+MXTw7RSH/bk1PiMUAWF8TQsNDzgUlznJnkjiQxoym/4ZUf4C6072KKQHbp6bgBYkBhJLT2lmjVMNSX5b1SXM9eTQixRfq6MKGw3P8XJnKdofktVv+KtSzWQlW0C8p504NWACiExupF5EII7FG+xCWt7urWUbc4NRI36UFrKToQCLVv6UBCXt/t9iWlvs6SfuZhpCexeMmZWeiIldzRu87U9rXR46Hu7DAL8dZ+0ItsIZYThSIABzZgaLKggXlkjyAcbcPYKO7egrCmDtFhzHuh4uA3VeBylL3/ZLZ4FUedn/8L4e2iAu22Qj46ORlu17W5R8Ez9kubydeAgC9PkWnjptaubPxE0bjPN69tec"; String key="fD0JzscfMf295SYtRK3MnPRjSCA4Gahr"; try { String decrypted = decrypt(info, key); System.out.println("解密结果: " + decrypted); } catch (Exception e) { throw new RuntimeException(e); } } public static String decrypt(String encryptedStringA, String merchantKey) throws Exception { // 1. 对加密串A做base64解码,得到加密串B byte[] decode = Base64.getDecoder().decode(encryptedStringA); // 2. 对商户key做md5,得到32位小写key* String sign = MD5AndKL.MD5Encode(merchantKey, "UTF-8").toLowerCase(); // 3. 确保BouncyCastle提供者已添加 if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } // 4. 使用AES-256-ECB解密(PKCS7Padding) Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC"); // 注意:微信要求使用AES-256,所以密钥应为32字节(256位) // 如果MD5结果是32字节(256位),直接使用 byte[] aesKey = sign.getBytes(StandardCharsets.UTF_8); SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES"); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); // 执行解密并指定UTF-8编码 byte[] decryptedBytes = cipher.doFinal(decode); return new String(decryptedBytes, StandardCharsets.UTF_8); } } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wx/XMLUtil.java
New file @@ -0,0 +1,52 @@ package com.ruoyi.order.util.payment.wx; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * XML工具类 */ public class XMLUtil { /** * 将Map转换为XML字符串 */ public static String mapToXml(Map<String, String> params) { Document document = DocumentHelper.createDocument(); // 禁用XML声明 document.setXMLEncoding(null); // 关键设置 Element root = document.addElement("xml"); for (Map.Entry<String, String> entry : params.entrySet()) { String key = entry.getKey(); String value = entry.getValue() != null ? entry.getValue() : ""; root.addElement(key).setText(value); } return document.asXML(); } /** * 将XML字符串转换为Map */ public static Map<String, String> xmlToMap(String xmlStr) throws DocumentException { Map<String, String> map = new HashMap<>(); Document document = DocumentHelper.parseText(xmlStr); Element root = document.getRootElement(); for (Iterator<?> iterator = root.elementIterator(); iterator.hasNext();) { Element element = (Element) iterator.next(); // 关键修改:获取元素内所有内容(包括CDATA) String value = element.getStringValue(); // 自动处理CDATA map.put(element.getName(), value); } return map; } } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wx/vo/PayResult.java
New file @@ -0,0 +1,16 @@ package com.ruoyi.order.util.payment.wx.vo; import lombok.Data; /** * 支付结果VO */ @Data public class PayResult { // 订单ID private String orderNumber; // 微信交易ID private String transactionId; // 支付金额 private String totalFee; } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wx/vo/RefundCallbackResult.java
New file @@ -0,0 +1,38 @@ package com.ruoyi.order.util.payment.wx.vo; import lombok.Data; /** * 退款回调结果实体 */ @Data public class RefundCallbackResult { private boolean success; // 处理是否成功 private String msg; // 结果描述 private String orderNo; // 原订单号 private String refundNo; // 退款订单号 private String refundId; // 微信退款ID private String totalFee; // 原订单金额(分) private String refundFee; // 退款金额(分) private String refundStatus; // 退款状态 // 成功响应 public static RefundCallbackResult success() { return success("处理成功"); } public static RefundCallbackResult success(String msg) { RefundCallbackResult result = new RefundCallbackResult(); result.setSuccess(true); result.setMsg(msg); return result; } // 失败响应 public static RefundCallbackResult fail(String msg) { RefundCallbackResult result = new RefundCallbackResult(); result.setSuccess(false); result.setMsg(msg); return result; } } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wx/vo/UnifiedOrderResult.java
New file @@ -0,0 +1,38 @@ package com.ruoyi.order.util.payment.wx.vo; import lombok.Data; /** * 统一下单结果VO */ @Data public class UnifiedOrderResult { // 返回状态码 private String returnCode; // 返回信息 private String returnMsg; // 业务结果 private String resultCode; // 错误代码 private String errCode; // 错误代码描述 private String errCodeDes; // 公众账号ID private String appid; // 商户号 private String mchId; // 设备号 private String deviceInfo; // 随机字符串 private String nonceStr; // 签名 private String sign; // 签名类型 private String signType; // 交易类型 private String tradeType; // 预支付交易会话标识 private String prepayId; // 二维码链接 private String codeUrl; } ruoyi-service/ruoyi-other/pom.xml
@@ -142,6 +142,23 @@ <artifactId>geodesy</artifactId> <version>1.1.3</version> </dependency> <!--微信支付--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-http</artifactId> <version>5.8.25</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> </dependencies> <build> ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/ShopWithdrawController.java
@@ -26,6 +26,7 @@ import com.ruoyi.other.util.payment.model.SinglePay; import com.ruoyi.other.util.payment.model.SinglePayCallbackResult; import com.ruoyi.other.util.payment.model.SinglePayResult; import com.ruoyi.other.util.payment.wx.WechatPayService; import com.ruoyi.system.api.domain.SysUser; import com.ruoyi.system.api.feignClient.SysUserClient; import com.ruoyi.system.api.model.LoginUser; @@ -37,11 +38,13 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -75,6 +78,9 @@ private SysUserClient sysUserClient; @Autowired private ShopWithdrawMapper shopWithdrawMapper; @Autowired private WechatPayService wechatPayService; @GetMapping("/getShopById") @@ -269,33 +275,30 @@ Shop shop = shopService.getById(shopWithdraw1.getShopId()); BigDecimal money = shopWithdraw1.getMoney(); if(1 == shopWithdraw.getAuditStatus()){ //通过 // 先检查账户余额是否充足 todo 商户编号 AccountBalanceQueryResult accountBalanceQueryResult = TransferUtil.accountBalanceQuery(); if(null == accountBalanceQueryResult){ return R.fail("查询账户余额出错"); } Double useAbleSettAmount = accountBalanceQueryResult.getUseAbleSettAmount(); if(useAbleSettAmount < (shopWithdraw1.getMoney().doubleValue() + 1)){ return R.fail("账户可用余额不足,请先补充账户余额"); } //银行卡转账 SinglePay singlePay = new SinglePay(); singlePay.setTradeMerchantNo(TransferUtil.sysTradeMerchantNo); singlePay.setMerchantOrderNo(shopWithdraw1.getId().toString()); singlePay.setReceiverAccountNoEnc(shop.getReceiverAccountNoEnc()); singlePay.setReceiverNameEnc(shop.getReceiverNameEnc()); singlePay.setReceiverAccountType(shop.getReceiverAccountType()); singlePay.setReceiverBankChannelNo(shop.getReceiverBankChannelNo()); singlePay.setPaidAmount(shopWithdraw1.getMoney().doubleValue()); singlePay.setPaidDesc("账户余额提现"); singlePay.setPaidUse("208"); singlePay.setCallbackUrl("/other/shop-withdraw/withdrawalCallback"); SinglePayResult singlePayResult = TransferUtil.singlePay(singlePay); if(null == singlePayResult){ return R.fail("转账失败"); } shopWithdraw1.setStatus(1); //取消商户转账,线下转账 /* try { Map<String, String> map = wechatPayService.payToBankCard(shopWithdraw1.getId().toString(), shopWithdraw1.getReceiverAccountNoEnc(),shopWithdraw1.getReceiverNameEnc(),shopWithdraw1.getReceiverBankChannelNo(),shopWithdraw1.getMoney(),shopWithdraw.getRemark()); if (map.get("return_code").equals("SUCCESS")) { System.out.println("转账申请成功"); shopWithdraw1.setStatus(1); }else { return R.fail("转账申请失败"); } } catch (Exception e) { throw new RuntimeException(e); }*/ //到账 shopWithdraw1.setStatus(2); shopWithdraw1.setArrivalTime(LocalDateTime.now()); shopWithdrawService.updateById(shopWithdraw); //更新店铺审核中的金额,和提现金额 shop.setWithdrawAuditMoney(shop.getWithdrawAuditMoney().subtract(shopWithdraw1.getMoney()));//审核中金额 shop.setWithdrawMoney(shop.getWithdrawMoney().add(shopWithdraw1.getMoney()));//提现金额 shopService.updateById(shop); } if(2 == shopWithdraw.getAuditStatus()){ //审核不通过 @@ -330,8 +333,7 @@ return R.ok(); } /** * 提现审核通过后转账回调通知 * @param singlePayCallbackResult ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/ShopServiceImpl.java
@@ -338,10 +338,12 @@ //商品评价 GoodsEvaluate goodsEvaluateOne = goodsEvaluateMapper.getGoodsEvaluateOne(goods.getId()); AppUser appUserById = appUserClient.getAppUserById(goodsEvaluateOne.getAppUserId()); goodsEvaluateOne.setUserName(appUserById.getName()); goodsEvaluateOne.setAvatar(appUserById.getAvatar()); goodsVO.setGoodsEvaluate(goodsEvaluateOne); if(goodsEvaluateOne != null){ AppUser appUserById = appUserClient.getAppUserById(goodsEvaluateOne.getAppUserId()); goodsEvaluateOne.setUserName(appUserById.getName()); goodsEvaluateOne.setAvatar(appUserById.getAvatar()); goodsVO.setGoodsEvaluate(goodsEvaluateOne); } return goodsVO; } ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/wx/HttpUtil.java
New file @@ -0,0 +1,99 @@ package com.ruoyi.other.util.payment.wx; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import javax.net.ssl.*; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.URL; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; /** * HTTP工具类 */ public class HttpUtil { /** * 发送POST请求 */ public static String post(String urlStr, String data) throws Exception { // 设置超时时间(单位:毫秒) int timeout = 5000; // 5秒 // 发送 POST 请求 try (HttpResponse response = HttpRequest.post(urlStr) .body(data, "application/xml") // 设置 XML 请求体 .timeout(timeout) .execute()) { // 检查 HTTP 状态码 if (!response.isOk()) { throw new RuntimeException("HTTP请求失败,状态码: " + response.getStatus() + ", 响应: " + response.body()); } return response.body(); } } /** * 发送HTTPS请求 */ public static String postHttps(String urlStr, String data, String certPath, String certPassword) throws Exception { // 创建SSL上下文 SSLContext sslContext = SSLContext.getInstance("SSL"); TrustManager[] trustManagers = {new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } }}; sslContext.init(null, trustManagers, new java.security.SecureRandom()); // 创建HTTPS连接 URL url = new URL(urlStr); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setSSLSocketFactory(sslContext.getSocketFactory()); conn.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }); conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); conn.setRequestProperty("Content-Type", "application/xml"); conn.setRequestProperty("Connection", "Keep-Alive"); conn.setRequestProperty("Charset", "UTF-8"); // 发送请求 OutputStream os = conn.getOutputStream(); os.write(data.getBytes("UTF-8")); os.flush(); os.close(); // 获取响应 StringBuilder result = new StringBuilder(); BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); String line; while ((line = br.readLine()) != null) { result.append(line); } br.close(); return result.toString(); } } ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/wx/PayResult.java
New file @@ -0,0 +1,16 @@ package com.ruoyi.other.util.payment.wx; import lombok.Data; /** * 支付结果VO */ @Data public class PayResult { // 订单ID private String orderNumber; // 微信交易ID private String transactionId; // 支付金额 private String totalFee; } ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/wx/RefundCallbackResult.java
New file @@ -0,0 +1,38 @@ package com.ruoyi.other.util.payment.wx; import lombok.Data; /** * 退款回调结果实体 */ @Data public class RefundCallbackResult { private boolean success; // 处理是否成功 private String msg; // 结果描述 private String orderNo; // 原订单号 private String refundNo; // 退款订单号 private String refundId; // 微信退款ID private String totalFee; // 原订单金额(分) private String refundFee; // 退款金额(分) private String refundStatus; // 退款状态 // 成功响应 public static RefundCallbackResult success() { return success("处理成功"); } public static RefundCallbackResult success(String msg) { RefundCallbackResult result = new RefundCallbackResult(); result.setSuccess(true); result.setMsg(msg); return result; } // 失败响应 public static RefundCallbackResult fail(String msg) { RefundCallbackResult result = new RefundCallbackResult(); result.setSuccess(false); result.setMsg(msg); return result; } } ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/wx/WechatPayConfig.java
New file @@ -0,0 +1,26 @@ package com.ruoyi.other.util.payment.wx; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * 微信支付配置类 */ @Data @Component @ConfigurationProperties(prefix = "wx") public class WechatPayConfig { // 小程序APPID private String appId; // 商户号 private String mchId; // 商户API密钥 private String key; // 支付结果通知地址 private String callbackPath; // 证书路径 private String certPath; // 商户RAS加密公钥路径 private String RASPath; } ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/wx/WechatPayService.java
New file @@ -0,0 +1,734 @@ package com.ruoyi.other.util.payment.wx; import com.alibaba.fastjson2.JSON; import com.ruoyi.common.core.domain.R; import com.ruoyi.other.util.payment.MD5AndKL; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.dom4j.DocumentException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import sun.security.util.DerInputStream; import sun.security.util.DerValue; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.servlet.http.HttpServletRequest; import java.io.*; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.*; import java.security.spec.RSAPublicKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.*; /** * 微信支付服务类 */ @Service public class WechatPayService { @Autowired private WechatPayConfig wechatPayConfig; private static final String RSA_PUBLIC_KEY_FILENAME = "wechat_rsa_public_key.pem"; private static final String CERT_FOLDER = "C:\\cert\\"; /** * 统一下单 * @param orderNumber 订单号 * @param totalFee 总金额(分) * @param body 商品描述 * @param openid 用户openid * @return 预支付订单信息 */ public R unifiedOrder(String orderNumber, String totalFee, String body, String openid, String callbackPath) throws Exception { int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue(); String hostAddress = null; try { hostAddress = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); } // 构建请求参数 Map<String, String> params = new HashMap<>(); params.put("appid", wechatPayConfig.getAppId()); params.put("mch_id", wechatPayConfig.getMchId()); params.put("nonce_str", generateNonceStr()); params.put("body", body); params.put("out_trade_no", orderNumber); params.put("total_fee", String.valueOf(i) ); params.put("spbill_create_ip", "221.182.45.100"); // 实际应用中应获取客户端IP params.put("notify_url", wechatPayConfig.getCallbackPath()+callbackPath); params.put("trade_type", "JSAPI"); params.put("openid", openid); // 生成签名 String sign = weixinSignature(params); params.put("sign", sign); // 将参数转换为XML String xmlParams = XMLUtil.mapToXml(params).replaceFirst("^<\\?xml.+?\\?>\\s*", ""); // 发送请求到微信支付统一下单接口 String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; String result = HttpUtil.post(url, xmlParams); // 解析返回结果 Map<String, String> resultMap = XMLUtil.xmlToMap(result); // 验证签名 if (!verifySign(resultMap, wechatPayConfig.getKey())) { return R.fail("微信支付签名验证失败"); // throw new Exception("微信支付签名验证失败"); } // 构建小程序支付所需参数 Map<String, String> payParams = new HashMap<>(); payParams.put("appId", wechatPayConfig.getAppId()); payParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); payParams.put("nonceStr", generateNonceStr()); payParams.put("package", "prepay_id=" + resultMap.get("prepay_id")); payParams.put("signType", "MD5"); // 生成支付签名 String paySign = weixinSignature(payParams); payParams.put("paySign", paySign); //给前端标识 payParams.put("payMethod","1"); return R.ok(JSON.toJSONString(payParams)); } /** * 微信下单的签名算法 * @param map * @return */ private String weixinSignature(Map<String, String> map){ try { Set<Map.Entry<String, String>> entries = map.entrySet(); List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(entries); // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序) Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() { public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) { return (o1.getKey()).toString().compareTo(o2.getKey()); } }); // 构造签名键值对的格式 StringBuilder sb = new StringBuilder(); for (Map.Entry<String, String> item : infoIds) { if (item.getKey() != null || item.getKey() != "") { String key = item.getKey(); Object val = item.getValue(); if (!(val == "" || val == null)) { sb.append(key + "=" + val + "&"); } } } sb.append("key=" + wechatPayConfig.getKey()); String sign = MD5AndKL.MD5Encode(sb.toString(), "UTF-8").toUpperCase(); //注:MD5签名方式 return sign; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 处理支付结果通知 * @param request HTTP请求 * @return 处理结果 */ public PayResult processNotify(HttpServletRequest request) throws Exception { // 读取请求内容 BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)); String line; StringBuilder xml = new StringBuilder(); while ((line = br.readLine()) != null) { xml.append(line); } br.close(); // 解析XML Map<String, String> resultMap = XMLUtil.xmlToMap(xml.toString()); // 验证签名 if (!verifySign(resultMap, wechatPayConfig.getKey())) { throw new Exception("微信支付签名验证失败"); } // 验证支付结果 if (!"SUCCESS".equals(resultMap.get("return_code")) || !"SUCCESS".equals(resultMap.get("result_code"))) { throw new Exception("微信支付结果异常"); } // 处理业务逻辑 PayResult payResult = new PayResult(); payResult.setOrderNumber(resultMap.get("out_trade_no")); payResult.setTransactionId(resultMap.get("transaction_id")); payResult.setTotalFee(resultMap.get("total_fee")); return payResult; } /** * 生成随机字符串 */ private String generateNonceStr() { return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32); } /** * 微信支付API V2签名算法 * @param params 请求参数 * @param apiKey 商户API密钥 * @return 签名结果 */ public static String generateSign(Map<String, String> params, String apiKey) throws Exception { // 1. 过滤空值参数 Map<String, String> filteredParams = new HashMap<>(); for (Map.Entry<String, String> entry : params.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); // 排除sign字段和空值字段 if (!"sign".equals(key) && value != null && !value.isEmpty()) { filteredParams.put(key, value); } } // 2. 按照ASCII码排序参数名 List<String> keys = new ArrayList<>(filteredParams.keySet()); Collections.sort(keys); // 3. 构建签名原始字符串 StringBuilder sb = new StringBuilder(); for (String key : keys) { String value = filteredParams.get(key); sb.append(key).append("=").append(value).append("&"); } // 4. 添加API密钥 sb.append("key=").append(apiKey); // 5. MD5加密并转为大写 MessageDigest md = MessageDigest.getInstance("MD5"); byte[] digest = md.digest(sb.toString().getBytes("UTF-8")); // 6. 转换为十六进制字符串 StringBuilder sign = new StringBuilder(); for (byte b : digest) { sign.append(String.format("%02x", b & 0xff)); } return sign.toString().toUpperCase(); } /** * 验证签名 */ private boolean verifySign(Map<String, String> params, String key) throws Exception { String sign = params.get("sign"); if (StringUtils.isEmpty(sign)) { return false; } // 移除sign字段 Map<String, String> newParams = new HashMap<>(params); newParams.remove("sign"); // 生成新签名 String newSign = generateSign(newParams, key); return sign.equals(newSign); } /** * 关闭订单 */ public Map<String, String> closeOrder(String orderId){ // 构建请求参数 Map<String, String> params = new HashMap<>(); params.put("appid", wechatPayConfig.getAppId()); params.put("mch_id", wechatPayConfig.getMchId()); params.put("nonce_str", generateNonceStr()); params.put("out_trade_no", orderId); // 生成签名 String sign = weixinSignature(params); params.put("sign", sign); // 将参数转换为XML String xmlParams = XMLUtil.mapToXml(params); // 发送请求到微信支付关闭订单接口 String url = "https://api.mch.weixin.qq.com/pay/closeorder"; String result = null; try { result = HttpUtil.post(url, xmlParams); } catch (Exception e) { throw new RuntimeException(e); } // 解析返回结果 try { return XMLUtil.xmlToMap(result); } catch (DocumentException e) { throw new RuntimeException(e); } } /** * 查询订单 */ public Map<String, String> queryOrder(String orderId) throws Exception { // 构建请求参数 Map<String, String> params = new HashMap<>(); params.put("appid", wechatPayConfig.getAppId()); params.put("mch_id", wechatPayConfig.getMchId()); params.put("nonce_str", generateNonceStr()); params.put("out_trade_no", orderId); // 生成签名 String sign = generateSign(params, wechatPayConfig.getKey()); params.put("sign", sign); // 将参数转换为XML String xmlParams = XMLUtil.mapToXml(params); // 发送请求到微信支付查询订单接口 String url = "https://api.mch.weixin.qq.com/pay/orderquery"; String result = HttpUtil.post(url, xmlParams); // 解析返回结果 return XMLUtil.xmlToMap(result); } /** * 申请退款 - 使用证书 */ public Map<String, String> refund(String orderNo, String refundNo, String totalFee, String refundFee, String refundDesc,String callbackPath) { int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue(); int j = new BigDecimal(refundFee).multiply(new BigDecimal("100")).intValue(); try { // 构建请求参数 Map<String, String> params = new HashMap<>(); params.put("appid", wechatPayConfig.getAppId()); params.put("mch_id", wechatPayConfig.getMchId()); params.put("nonce_str", UUID.randomUUID().toString().replaceAll("-", "")); params.put("out_trade_no", orderNo); params.put("out_refund_no", refundNo); params.put("total_fee", String.valueOf(i)); params.put("refund_fee", String.valueOf(j)); params.put("refund_desc", refundDesc); params.put("notify_url", wechatPayConfig.getCallbackPath() + callbackPath); // 退款结果 // 生成签名 String sign = weixinSignature(params); params.put("sign", sign); // 转换为XML String xmlParams = XMLUtil.mapToXml(params); // 使用证书发送请求 String result = postWithCert("https://api.mch.weixin.qq.com/secapi/pay/refund", xmlParams); // 解析结果 Map<String, String> resultMap = XMLUtil.xmlToMap(result); System.out.println("申请退款结果"+resultMap); // 验证签名 if (!verifySign(resultMap, wechatPayConfig.getKey())) { resultMap.put("return_code","FAILED"); resultMap.put("return_msg","申请退款结果签名验证失败"); return resultMap; } return resultMap; } catch (Exception e) { Map<String, String> resultMap=new HashMap<>(); resultMap.put("return_code","FAILED"); resultMap.put("return_msg","申请退款失败"); return resultMap; } } /** * 使用证书发送请求 */ private String postWithCert(String url, String xmlData) throws Exception { // 证书类型为PKCS12 KeyStore keyStore = KeyStore.getInstance("PKCS12"); // 获取证书路径 String certPath = wechatPayConfig.getCertPath(); // 如果是classpath路径,使用ClassLoader加载 if (certPath.startsWith("classpath:")) { String path = certPath.substring("classpath:".length()); try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path)) { if (inputStream == null) { throw new FileNotFoundException("证书文件不存在: " + path); } keyStore.load(inputStream, wechatPayConfig.getMchId().toCharArray()); } } else { // 传统文件路径 try (FileInputStream inputStream = new FileInputStream(new File(certPath))) { keyStore.load(inputStream, wechatPayConfig.getMchId().toCharArray()); } } // 实例化密钥库 & 初始化密钥工厂 SSLContext sslContext = SSLContext.getInstance("TLS"); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, wechatPayConfig.getMchId().toCharArray()); sslContext.init(kmf.getKeyManagers(), null, new SecureRandom()); // 创建HttpsURLConnection对象 URL httpsUrl = new URL(url); HttpsURLConnection conn = (HttpsURLConnection) httpsUrl.openConnection(); conn.setSSLSocketFactory(sslContext.getSocketFactory()); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // 发送请求 OutputStream outputStream = conn.getOutputStream(); outputStream.write(xmlData.getBytes("UTF-8")); outputStream.flush(); outputStream.close(); // 获取响应 InputStream inputStream = conn.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); StringBuilder result = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { result.append(line); } bufferedReader.close(); inputStream.close(); conn.disconnect(); return result.toString(); } /** * 处理退款回调 */ public RefundCallbackResult processRefundCallback(String xmlData) { try { // 1. 解析回调XML数据 if (StringUtils.isEmpty(xmlData)) { // logger.error("退款回调数据为空"); return RefundCallbackResult.fail("回调数据为空"); } //2.解析参数 System.err.println(xmlData); System.out.println("----------------------------------------"); Map<String, String> resultMap = XMLUtil.xmlToMap(xmlData); System.out.println(resultMap.get("req_info")); // 3. 检查返回状态 String returnCode = resultMap.get("return_code"); if (!"SUCCESS".equals(returnCode)) { String errMsg = resultMap.get("return_msg"); return RefundCallbackResult.fail("通信失败:" + errMsg); } //4 使用商户API密钥解密req_info(AES-256-CBC算法) String decryptData = wxDecrypt(resultMap.get("req_info"), wechatPayConfig.getKey()); Map<String, String> refundDetail = XMLUtil.xmlToMap(decryptData); // 4. 提取退款信息 String orderNo = refundDetail.get("out_trade_no"); // 原订单号 String refundNo = refundDetail.get("out_refund_no"); // 退款订单号 String refundId = refundDetail.get("refund_id"); // 微信退款ID System.err.println("退款回调成功,订单号:"+orderNo+",退款号:"+refundNo+",状态:{}"+refundId); RefundCallbackResult refundCallbackResult = RefundCallbackResult.success(); refundCallbackResult.setOrderNo(orderNo); refundCallbackResult.setRefundNo(refundId); return refundCallbackResult; } catch (Exception e) { return RefundCallbackResult.fail("系统异常:" + e.getMessage()); } } private static String wxDecrypt(String req_info, String key) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException{ byte[] decode = Base64.getDecoder().decode(req_info); System.out.println(Arrays.toString(decode)); String sign = MD5AndKL.MD5Encode(key, "UTF-8").toLowerCase(); System.out.println(sign); if (Security.getProvider("BC") == null){ Security.addProvider(new BouncyCastleProvider()); } Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC"); SecretKeySpec secretKeySpec = new SecretKeySpec(sign.getBytes(), "AES"); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); return new String(cipher.doFinal(decode)); } /** * 获取RSA加密公钥 */ public String getRsaPublicKey() { int maxRetries = 3; for (int retryCount = 0; retryCount < maxRetries; retryCount++) { try { System.out.println("尝试获取RSA公钥,第" + (retryCount + 1) + "次"); // 构建请求参数 Map<String, String> params = new HashMap<>(); params.put("mch_id", wechatPayConfig.getMchId()); params.put("sign_type", "MD5"); params.put("nonce_str", generateNonceStr()); // 生成签名 String sign = weixinSignature(params); params.put("sign", sign); // 转换为XML String xmlParams = XMLUtil.mapToXml(params); // 打印请求参数 System.out.println("请求参数: " + xmlParams); // 使用证书发送请求(关键修改:使用postWithCert而非普通HTTP请求) String result = postWithCert("https://fraud.mch.weixin.qq.com/risk/getpublickey", xmlParams); // 打印响应结果 System.out.println("响应结果: " + result); // 解析结果 Map<String, String> resultMap = XMLUtil.xmlToMap(result); System.out.println("获取RSA公钥结果: " + resultMap); // 检查返回状态 if (!"SUCCESS".equals(resultMap.get("return_code"))) { throw new Exception("RSA公钥获取失败: " + resultMap.get("return_msg")); } // 保存公钥到本地文件 savePublicKeyToClasspath(resultMap.get("pub_key")); return resultMap.get("pub_key"); } catch (Exception e) { System.err.println("获取RSA公钥异常: " + e.getMessage() + ", 重试次数: " + (retryCount + 1)); e.printStackTrace(); // 如果是最后一次重试,抛出异常 if (retryCount == maxRetries - 1) { return null; } // 重试前等待一段时间 try { Thread.sleep(1000 * (retryCount + 1)); // 指数退避 } catch (InterruptedException ie) { Thread.currentThread().interrupt(); return null; } } } return null; } /** * 保存RSA公钥到文件夹 * @param publicKey RSA公钥内容 */ private static void savePublicKeyToClasspath(String publicKey) throws IOException { // 创建 cert 目录(如果不存在) Path certDir = Paths.get(CERT_FOLDER); if (!Files.exists(certDir)) { Files.createDirectories(certDir); // 自动创建目录 } // 写入公钥文件(UTF-8 编码) Path keyFile = certDir.resolve(RSA_PUBLIC_KEY_FILENAME); Files.write(keyFile, publicKey.getBytes(StandardCharsets.UTF_8)); } /** * 从文件夹加载RSA公钥 * @return RSA公钥内容 */ private static String loadPublicKeyFromClasspath() throws IOException { Path keyFile = Paths.get(CERT_FOLDER + RSA_PUBLIC_KEY_FILENAME); byte[] bytes = Files.readAllBytes(keyFile); // 读取所有字节 return new String(bytes, StandardCharsets.UTF_8); // 转为UTF-8字符串 } /** * 加载公钥 返回PublicKey */ public static PublicKey loadPublicKey(String pemContent) throws Exception { // 读取PEM文件内容 // String pemContent = new String(Files.readAllBytes(Paths.get(CERT_FOLDER + RSA_PUBLIC_KEY_FILENAME)), StandardCharsets.UTF_8); // 移除PEM头尾标记 String publicKeyPEM = pemContent .replace("-----BEGIN RSA PUBLIC KEY-----", "") .replace("-----END RSA PUBLIC KEY-----", "") .replaceAll("\\s", ""); // 去除换行/空格 // 解码Base64 byte[] encoded = Base64.getDecoder().decode(publicKeyPEM); // 手动解析PKCS#1格式 DerInputStream derReader = new DerInputStream(encoded); DerValue[] seq = derReader.getSequence(0); BigInteger modulus = seq[0].getBigInteger(); BigInteger exponent = seq[1].getBigInteger(); RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent); return KeyFactory.getInstance("RSA").generatePublic(keySpec); } /** * 使用RSA-OAEP加密数据 * @param plaintext 待加密的明文 * @return Base64编码的密文 */ public static String encrypt(String plaintext, PublicKey publicKey) throws Exception { Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(encryptedBytes); } /** * 商户付款到银行卡(优先使用本地保存的公钥) * @param partnerTradeNo 商户订单号 * @param bankNo 银行卡号 * @param trueName 银行卡真实姓名 * @param bankCode 银行编码 * @param amount 金额(分) * @param desc 付款说明 * @return 付款结果 */ public Map<String, String> payToBankCard(String partnerTradeNo, String bankNo, String trueName, String bankCode, BigDecimal amount, String desc) throws Exception { int i = amount.multiply(new BigDecimal("100")).intValue(); // 1. 尝试从本地加载RSA公钥 String pubKey = loadPublicKeyFromClasspath(); // 2. 如果本地没有公钥或公钥无效,则从微信获取新公钥 if (pubKey == null || pubKey.isEmpty()) { pubKey = getRsaPublicKey(); } //公钥对象 PublicKey publicKey = loadPublicKey(pubKey); // 3. 使用RSA公钥加密银行卡号和真实姓名 String encryptedBankNo = encrypt(bankNo, publicKey); String encryptedTrueName = encrypt(trueName, publicKey); // 4. 构建请求参数 Map<String, String> params = new HashMap<>(); params.put("mch_id", wechatPayConfig.getMchId()); params.put("partner_trade_no", partnerTradeNo); params.put("enc_bank_no", encryptedBankNo); params.put("enc_true_name", encryptedTrueName); params.put("bank_code", bankCode); params.put("amount", String.valueOf(i)); params.put("desc", desc); params.put("nonce_str", generateNonceStr()); // 生成签名 String sign = weixinSignature(params); params.put("sign", sign); // 将参数转换为XML String xmlParams = XMLUtil.mapToXml(params); // 5. 发送请求到微信支付付款到银行卡接口 String url = "https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank"; String result = postWithCert(url, xmlParams); // 解析返回结果 Map<String, String> resultMap = XMLUtil.xmlToMap(result); // 验证签名 if (!verifySign(resultMap, wechatPayConfig.getKey())) { throw new Exception("付款到银行卡签名验证失败"); } return resultMap; } public static void main(String[] args) throws IOException { /* try { // 1. 加载公钥 PublicKey publicKey = loadPublicKey(); // 2. 加密数据 String sensitiveData = "用户名"; String encryptedData = encrypt(sensitiveData, publicKey); System.out.println("加密结果(Base64):\n" + encryptedData); } catch (Exception e) { e.printStackTrace(); }*/ String info="CjlaS7RVnPn7zzP5ByZDxUN7OrXGp1/DEdO0qahpIqDH/gTNHb/U7VmrVV0S4lXrIa0N8FEREC3CdIeT4XB5P4D0E8TSURu6J/cD01hFu2/uJOvcE6EeQH2xiRg/Wir4qcW7c6uTiLoqyirCQXcGzQb3CCcJf7OZWaweOUkaKjaBRa1GzMZcguSZnQJz0cD5jTMx+Tch5+b7jBq5PrTFxtMSH/DAG+kgkRazDFnEzkMeT4V+FViw8f8sjkH0TScMgWBiSKmQC837BLD27yIGklqlYkDP2IMeiNw+b12qCAGszfp2vYd3X+HpViXkQQet3PJWYlAm55R+IgvschP7Ub65XzLINfQrJKrQUXiKKO2LwoSRSwZvfDkR8G8E8X59CnU2XvWKeos5Y0q8ckbJb97yI+09nNgMjYyJoVCVjTGc7ghcYvWKbqanJ8bSFqiBCIqLSXsRR2DmJIxHq9fGE72kCJyJEAqLPMNyfYBSNF8z1btjyz0+y/xQQcySKlQInZ710FxSE7KwRSBQ92j9nDdlR7UxCrPVCkEd+GrVNSqqnyjNh1J/rPJPHvvGwkPPq72TKiw6ZgaIgIDhy0/lWHTclo4sjYAWuUVfg3CJ8dqkuQwVZ7i0+NiahIl78RtcUph8NR48yUgBkN7WhCcu5wLbg2tu8Qe0SIwHF+RW1x9Yc8akEkNbMd4xzs8lY5MYEU9V16U8RyWJuwPDph3RnmV8HQ+2hfzmjCvPkBwtfR8P5VdK86OIsHfnfQxAcPM2a86tOBBzFXPrLHgd2CRcDKH+MXTw7RSH/bk1PiMUAWF8TQsNDzgUlznJnkjiQxoym/4ZUf4C6072KKQHbp6bgBYkBhJLT2lmjVMNSX5b1SXM9eTQixRfq6MKGw3P8XJnKdofktVv+KtSzWQlW0C8p504NWACiExupF5EII7FG+xbTa/s7vxXCP7R98tpcQTGoQCLVv6UBCXt/t9iWlvs6SfuZhpCexeMmZWeiIldzRu87U9rXR46Hu7DAL8dZ+0ItsIZYThSIABzZgaLKggXlkjyAcbcPYKO7egrCmDtFhwN50V7hoXEQB8G5kf/lMuT5+xNE2FRmv7H2a0ttZiv4u17W5R8Ez9kubydeAgC9PkWnjptaubPxE0bjPN69tec"; String key="fD0JzscfMf295SYtRK3MnPRjSCA4Gahr"; try { String decrypted = decrypt(info, key); System.out.println("解密结果: " + decrypted); } catch (Exception e) { throw new RuntimeException(e); } } public static String decrypt(String encryptedStringA, String merchantKey) throws Exception { try { byte[] decode = Base64.getDecoder().decode(encryptedStringA); String sign = MD5AndKL.MD5Encode(merchantKey, "UTF-8").toLowerCase(); System.out.println("MD5 Key: " + sign); // 调试输出 if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC"); byte[] aesKey = Arrays.copyOf(sign.getBytes("UTF-8"), 16); // 明确指定 UTF-8 SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES"); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); byte[] decryptedBytes = cipher.doFinal(decode); return new String(decryptedBytes, "UTF-8"); // 明确指定 UTF-8 } catch (Exception e) { System.err.println("解密失败: " + e.getMessage()); throw e; } } } ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/wx/XMLUtil.java
New file @@ -0,0 +1,52 @@ package com.ruoyi.other.util.payment.wx; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * XML工具类 */ public class XMLUtil { /** * 将Map转换为XML字符串 */ public static String mapToXml(Map<String, String> params) { Document document = DocumentHelper.createDocument(); // 禁用XML声明 document.setXMLEncoding(null); // 关键设置 Element root = document.addElement("xml"); for (Map.Entry<String, String> entry : params.entrySet()) { String key = entry.getKey(); String value = entry.getValue() != null ? entry.getValue() : ""; root.addElement(key).setText(value); } return document.asXML(); } /** * 将XML字符串转换为Map */ public static Map<String, String> xmlToMap(String xmlStr) throws DocumentException { Map<String, String> map = new HashMap<>(); Document document = DocumentHelper.parseText(xmlStr); Element root = document.getRootElement(); for (Iterator<?> iterator = root.elementIterator(); iterator.hasNext();) { Element element = (Element) iterator.next(); // 关键修改:获取元素内所有内容(包括CDATA) String value = element.getStringValue(); // 自动处理CDATA map.put(element.getName(), value); } return map; } }