huliguo
4 天以前 6acf6357094588946b5528f1ef1ed84a0f1037fd
小程序收付款
14个文件已添加
11个文件已修改
3678 ■■■■■ 已修改文件
ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/domain/ShopWithdraw.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/AppUserServiceImpl.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/pom.xml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/OrderController.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/OrderService.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/OrderServiceImpl.java 207 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/PaymentUtil.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wechat/PayMoneyUtil.java 1295 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wx/HttpUtil.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wx/WechatPayConfig.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wx/WechatPayService.java 742 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wx/XMLUtil.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wx/vo/PayResult.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wx/vo/RefundCallbackResult.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/payment/wx/vo/UnifiedOrderResult.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/pom.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/ShopWithdrawController.java 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/ShopServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/wx/HttpUtil.java 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/wx/PayResult.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/wx/RefundCallbackResult.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/wx/WechatPayConfig.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/wx/WechatPayService.java 734 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/payment/wx/XMLUtil.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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;
    }
}