longz
2026-07-20 faaf70b3fc0cff90d25fa30b37f225f679a0d5a5
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -5,8 +5,10 @@
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.api.dto.SendMessageDTO;
import com.ruoyi.account.api.feignClient.*;
import com.ruoyi.account.api.model.*;
import com.ruoyi.account.api.vo.AppUserWhitelistMatchVO;
@@ -61,6 +63,8 @@
import com.ruoyi.order.util.mongodb.service.TransactionRecordService;
import com.ruoyi.order.util.mongodb.service.UploadRealTimeMonitoringDataService;
import com.ruoyi.order.vo.EndOfChargePageInfo;
import com.ruoyi.order.vo.ChargingSimulationVO;
import com.ruoyi.order.vo.IcbcAdminSubOrderVO;
import com.ruoyi.order.vo.OrderCountByDate;
import com.ruoyi.order.vo.StatisticsOfBatteryVO;
import com.ruoyi.other.api.domain.*;
@@ -77,6 +81,7 @@
import com.ruoyi.payment.api.vo.*;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.feignClient.SysUserClient;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
@@ -85,7 +90,12 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
@@ -120,6 +130,8 @@
   private static final int WHITELIST_BENEFIT_ALL_FREE = 1;
   private static final int WHITELIST_BENEFIT_ELECTRIC_FREE = 2;
   private static final int WHITELIST_BENEFIT_SERVICE_FREE = 3;
   private static final String SHARE_AFTER_COMMIT_RESOURCE_PREFIX = "chargingOrderShareAfterCommit:";
   private static final String SPECIAL_REFUND_TITLE = "特殊退款处理";
   @Resource
   private ChargingGunClient chargingGunClient;
@@ -155,6 +167,10 @@
   private IcbcPaymentClient icbcPaymentClient;
   @Resource
   private TChargingOrderShareRecordMapper chargingOrderShareRecordMapper;
   @Resource
   private ChargingOrderSettlementExecutor chargingOrderSettlementExecutor;
   @Resource
   private PlatformTransactionManager transactionManager;
   
   @Resource
   private AppCouponClient appCouponClient;
@@ -175,6 +191,8 @@
   
   @Resource
   private SendMessageClient sendMessageClient;
   @Resource
   private WxSubscribeMessageService wxSubscribeMessageService;
   
   @Resource
   private TOrderEvaluateService orderEvaluateService;
@@ -265,23 +283,30 @@
      Map<String, Object> map = new HashMap<>();
      int size = this.baseMapper.getMyChargingOrderList(appUserId, 1, null, null).size();
      map.put("number", size);
      query.setPageCurr((query.getPageCurr() - 1) * query.getPageSize());
      Integer pageCurr = query.getPageCurr() == null || query.getPageCurr() < 1 ? 1 : query.getPageCurr();
      Integer pageSize = query.getPageSize() == null || query.getPageSize() < 1 ? 10 : query.getPageSize();
      query.setPageCurr((pageCurr - 1) * pageSize);
      query.setPageSize(pageSize);
      List<TChargingOrder> list = this.baseMapper.getMyChargingOrderList(appUserId, query.getType(), query.getPageCurr(), query.getPageSize());
      List<MyChargingOrderList> orderLists = new ArrayList<>();
      for (TChargingOrder tChargingOrder : list) {
         MyChargingOrderList myChargingOrderList = new MyChargingOrderList();
         myChargingOrderList.setId(tChargingOrder.getId().toString());
         myChargingOrderList.setSiteId(tChargingOrder.getSiteId());
         myChargingOrderList.setStatus(tChargingOrder.getStatus());
         Site site = siteClient.getSiteByIds(Arrays.asList(tChargingOrder.getSiteId())).getData().get(0);
         myChargingOrderList.setTitle(site.getName());
         Site site = getOrderSite(tChargingOrder.getSiteId());
         myChargingOrderList.setTitle(site == null ? "未知电站" : site.getName());
         myChargingOrderList.setChargingDegree(tChargingOrder.getChargingCapacity());
         String name = chargingGunClient.getAllName(tChargingOrder.getChargingGunId()).getData();
         String name = getChargingGunName(tChargingOrder.getChargingGunId());
         myChargingOrderList.setChargingGunId(tChargingOrder.getChargingGunId());
         myChargingOrderList.setName(name);
         myChargingOrderList.setEndMode(tChargingOrder.getEndMode());
         BigDecimal payMoney = tChargingOrder.getStatus() < 4 ? tChargingOrder.getRechargeAmount() : tChargingOrder.getPaymentAmount();
         Integer status = tChargingOrder.getStatus();
         BigDecimal payMoney = status != null && status < 4 ? tChargingOrder.getRechargeAmount() : tChargingOrder.getPaymentAmount();
         myChargingOrderList.setPayMoney(payMoney);
         myChargingOrderList.setCreateTime(tChargingOrder.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
         if (tChargingOrder.getCreateTime() != null) {
            myChargingOrderList.setCreateTime(tChargingOrder.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
         }
         long count = orderEvaluateService.count(new LambdaQueryWrapper<TOrderEvaluate>().eq(TOrderEvaluate::getOrderType, 1)
               .eq(TOrderEvaluate::getOrderId, tChargingOrder.getId()).eq(TOrderEvaluate::getDelFlag, 0));
         myChargingOrderList.setEvaluate(count + 1L);
@@ -291,6 +316,23 @@
      map.put("list", orderLists);
      map.put("totle", list.size());
      return map;
   }
   private Site getOrderSite(Integer siteId) {
      if (siteId == null) {
         return null;
      }
      R<List<Site>> result = siteClient.getSiteByIds(Collections.singletonList(siteId));
      List<Site> sites = result == null ? Collections.emptyList() : result.getData();
      return sites == null || sites.isEmpty() ? null : sites.get(0);
   }
   private String getChargingGunName(Integer chargingGunId) {
      if (chargingGunId == null) {
         return "";
      }
      R<String> result = chargingGunClient.getAllName(chargingGunId);
      return result == null || result.getData() == null ? "" : result.getData();
   }
   
   
@@ -408,6 +450,7 @@
      for (TChargingOrder tChargingOrder : orderList) {
         MyChargingOrderList myChargingOrderList = new MyChargingOrderList();
         myChargingOrderList.setId(tChargingOrder.getId().toString());
         myChargingOrderList.setSiteId(tChargingOrder.getSiteId());
         myChargingOrderList.setStatus(tChargingOrder.getStatus());
         Site site = siteClient.getSiteByIds(Arrays.asList(tChargingOrder.getSiteId())).getData().get(0);
         myChargingOrderList.setTitle(site.getName());
@@ -453,6 +496,42 @@
        return amount == null ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP) : amount.setScale(2, RoundingMode.HALF_UP);
    }
    private void sendVipChargeFinishMessage(TAppUser appUser, TChargingOrder chargingOrder, Site site) {
        try {
            if (appUser == null || chargingOrder == null || appUser.getVipId() == null || !StringUtils.hasLength(appUser.getPhone())) {
                return;
            }
            GetAppUserVipDetail query = new GetAppUserVipDetail();
            query.setAppUserId(appUser.getId());
            query.setVipId(appUser.getVipId());
            TAppUserVipDetail vipDetail = appUserVipDetailClient.getAppUserVipDetail(query).getData();
            if (vipDetail == null) {
                return;
            }
            SendMessageDTO dto = new SendMessageDTO();
            dto.setType(4);
            dto.setPhone(appUser.getPhone());
            dto.setSite(site == null ? "充电站" : site.getName());
            dto.setChargingCapacity(formatSmsNumber(chargingOrder.getChargingCapacity()));
            BigDecimal fee = chargingOrder.getPaymentAmount() == null ? chargingOrder.getOrderAmount() : chargingOrder.getPaymentAmount();
            dto.setChargingFee(formatSmsNumber(fee));
            R<String> result = appUserClient.sendMessage(dto);
            if (result == null || result.getCode() != 200) {
                log.warn("VIP充电完成短信发送失败,orderCode={}, appUserId={}, result={}", chargingOrder.getCode(), appUser.getId(), result);
            }
        } catch (Exception e) {
            log.warn("VIP充电完成短信发送异常,orderCode={}, appUserId={}", chargingOrder == null ? null : chargingOrder.getCode(), appUser == null ? null : appUser.getId(), e);
        }
    }
    private String formatSmsNumber(BigDecimal value) {
        if (value == null) {
            return "0";
        }
        String text = value.setScale(0, RoundingMode.HALF_UP).toPlainString();
        return text.length() <= 5 ? text : text.substring(0, 5);
    }
    /**
     * 获取第三方实付金额,兼容早期未写入third_pay_amount的纯第三方支付订单。
     *
@@ -463,14 +542,7 @@
        if (order == null || isWhitelistAllFreeOrder(order)) {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        BigDecimal thirdPayAmount = money(order.getThirdPayAmount());
        if (thirdPayAmount.compareTo(BigDecimal.ZERO) > 0) {
            return thirdPayAmount;
        }
        if (Integer.valueOf(3).equals(order.getPayMethod())) {
            return money(order.getRechargeAmount());
        }
        return thirdPayAmount;
        return ChargingSimulationSupport.thirdPayAmount(order);
    }
    /**
@@ -591,6 +663,27 @@
      return chargingOrder != null && "ICBC".equals(chargingOrder.getSourceName());
   }
   private BigDecimal resolveUpfrontCouponDiscount(Long appCouponId, Long userId, BigDecimal rechargeAmount) {
      TAppCoupon appCoupon = appCouponClient.getAppCouponById(appCouponId).getData();
      if (appCoupon == null || !userId.equals(appCoupon.getAppUserId())) {
         throw new ServiceException("优惠券不存在或不属于当前用户");
      }
      if (!Integer.valueOf(1).equals(appCoupon.getStatus())) {
         throw new ServiceException("优惠券已使用");
      }
      LocalDateTime now = LocalDateTime.now();
      if ((appCoupon.getStartTime() != null && appCoupon.getStartTime().isAfter(now))
            || (appCoupon.getEndTime() != null && appCoupon.getEndTime().isBefore(now))) {
         throw new ServiceException("优惠券不在有效期内");
      }
      try {
         TCoupon coupon = JSON.parseObject(appCoupon.getCouponJson(), TCoupon.class);
         return ChargingCouponSupport.calculateUpfrontDiscount(coupon, rechargeAmount);
      } catch (IllegalArgumentException e) {
         throw new ServiceException(e.getMessage());
      }
   }
    /**
     * 判断订单是否为白名单订单
     *
@@ -636,7 +729,12 @@
        }
        return new BigDecimal[]{electricDiscount, serviceDiscount};
    }
    private TVip getVipSnapshot(TAppUserVipDetail vipDetail, Integer vipId) {
        if (vipDetail != null && StringUtils.hasLength(vipDetail.getVipJson())) {
            return JSON.parseObject(vipDetail.getVipJson(), TVip.class);
        }
        return vipId == null ? null : vipClient.getInfo1(vipId).getData();
    }
   @Override
   public AjaxResult paymentChargingOrder(AddChargingOrder addChargingOrder) {
      Long userId = tokenService.getLoginUserApplet().getUserId();
@@ -645,77 +743,48 @@
      BigDecimal rechargeAmount = money(addChargingOrder.getPaymentAmount());
      TChargingGun tChargingGun = chargingGunClient.getChargingGunById(addChargingOrder.getId()).getData();
      TChargingPile chargingPile = chargingPileClient.getChargingPileById(tChargingGun.getChargingPileId()).getData();
      TAppUserCar selectedCar = null;
      if (addChargingOrder.getAppUserCarId() != null) {
         selectedCar = appUserCarClient.getCarById(addChargingOrder.getAppUserCarId().toString()).getData();
      }
      String selectedVin = selectedCar == null ? null : normalizeVin(selectedCar.getVin());
      if (Integer.valueOf(1).equals(chargingPile.getVinFlag())
            && (!StringUtils.hasLength(selectedVin) || !selectedVin.matches("[A-HJ-NPR-Z0-9]{17}"))) {
         return AjaxResult.error("该充电桩需要17位有效车辆VIN,请先完善所选车辆VIN");
      }
      AppUserWhitelistMatchVO whitelist = appUserWhitelistClient.matchWhitelist(userId, tChargingGun.getSiteId()).getData();
      boolean whitelistMatched = whitelist != null && Boolean.TRUE.equals(whitelist.getMatched());
      if (whitelistMatched) {
         payMethod = 3;
         addChargingOrder.setPayMethod(3);
         addChargingOrder.setAppUserCouponId(null);
      }
      boolean whitelistAllFree = whitelistMatched
            && Integer.valueOf(WHITELIST_BENEFIT_ALL_FREE).equals(whitelist.getBenefitType());
      BigDecimal userBalance = money(appUser.getBalance());
      BigDecimal balancePayAmount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
      BigDecimal thirdPayAmount = rechargeAmount;
      BigDecimal upfrontCouponDiscount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
      if (payMethod == 2 && userBalance.compareTo(rechargeAmount) >= 0) {
         payMethod = 1;
         addChargingOrder.setPayMethod(1);
      }
      if (isBalancePayMode(payMethod)) {
         addChargingOrder.setAppUserCouponId(null);
         if (payMethod == 1 && userBalance.compareTo(rechargeAmount) < 0) {
      } else if (!whitelistMatched && addChargingOrder.getAppUserCouponId() != null) {
         upfrontCouponDiscount = resolveUpfrontCouponDiscount(
               addChargingOrder.getAppUserCouponId(), userId, rechargeAmount);
      }
      BigDecimal payableAmount = whitelistAllFree
            ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)
            : rechargeAmount.subtract(upfrontCouponDiscount).max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
      BigDecimal thirdPayAmount = payableAmount;
      if (isBalancePayMode(payMethod)) {
         if (payMethod == 1 && userBalance.compareTo(payableAmount) < 0) {
            return AjaxResult.error("余额不足,请选择组合支付或第三方支付");
         }
         balancePayAmount = payMethod == 1 ? rechargeAmount : userBalance.min(rechargeAmount).setScale(2, RoundingMode.HALF_UP);
         thirdPayAmount = rechargeAmount.subtract(balancePayAmount).setScale(2, RoundingMode.HALF_UP);
         balancePayAmount = payMethod == 1 ? payableAmount : userBalance.min(payableAmount).setScale(2, RoundingMode.HALF_UP);
         thirdPayAmount = payableAmount.subtract(balancePayAmount).setScale(2, RoundingMode.HALF_UP);
      }
      if (whitelistMatched) {
         balancePayAmount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
         thirdPayAmount = Integer.valueOf(WHITELIST_BENEFIT_ALL_FREE).equals(whitelist.getBenefitType())
               ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)
               : rechargeAmount;
      }
      //查询待支付状态的订单,没有支付则删除订单
      List<TChargingOrder> list = this.list(new LambdaQueryWrapper<TChargingOrder>().eq(TChargingOrder::getAppUserId, userId)
            .eq(TChargingOrder::getRechargePaymentStatus, 1).eq(TChargingOrder::getDelFlag, 0));
      for (TChargingOrder tChargingOrder : list) {
         Integer rechargePaymentType = tChargingOrder.getRechargePaymentType();
         if ("ICBC".equals(tChargingOrder.getSourceName())) {
            IcbcPaymentResp data = icbcPaymentClient.query(tChargingOrder.getCode()).getData();
            if (null != data && isIcbcRemovableStatus(data.getStatus(), tChargingOrder)) {
               this.removeById(tChargingOrder.getId());
            }
         }
         if (!"ICBC".equals(tChargingOrder.getSourceName()) && 1 == rechargePaymentType) {
            NotifyV3PayDecodeRespBody data = wxPaymentClient.queryOrderInfo(tChargingOrder.getCode()).getData();
            if (null != data) {
               String trade_state = data.getTrade_state();
               //支付失败,删除无效的订单
               if (trade_state.equals("REFUND") || trade_state.equals("NOTPAY") || trade_state.equals("REVOKED") || trade_state.equals("PAYERROR")) {
                  this.removeById(tChargingOrder.getId());
               }
            }
         }
         if (!"ICBC".equals(tChargingOrder.getSourceName()) && 2 == rechargePaymentType) {
            AliQueryOrder data = aliPaymentClient.query(tChargingOrder.getCode()).getData();
            if (null != data) {
               //支付失败,删除无效的订单
               String tradeStatus = data.getTradeStatus();
               if (null != tradeStatus && tradeStatus.equals("TRADE_CLOSED")) {
                  this.removeById(tChargingOrder.getId());
               }
            }
         }
         if (!"ICBC".equals(tChargingOrder.getSourceName()) && 3 == rechargePaymentType) {
            AliQueryOrder data = h5AliPaymentClient.query(tChargingOrder.getCode()).getData();
            if (null != data) {
               //支付失败,删除无效的订单
               String tradeStatus = data.getTradeStatus();
               if (null != tradeStatus && tradeStatus.equals("TRADE_CLOSED")) {
                  this.removeById(tChargingOrder.getId());
               }
            }
         }
      }
      payMethod = ChargingPaymentSupport.resolvePayMethod(balancePayAmount, thirdPayAmount, payMethod);
      addChargingOrder.setPayMethod(payMethod);
      //检查当前枪是否是正在使用中
      TChargingOrder one = this.getOne(new LambdaQueryWrapper<TChargingOrder>().eq(TChargingOrder::getChargingGunId, addChargingOrder.getId())
            .in(TChargingOrder::getStatus, Arrays.asList(3)).eq(TChargingOrder::getDelFlag, 0));
@@ -724,7 +793,8 @@
      }
      //查询当前充电枪是否有正在支付的订单
      one = this.getOne(new LambdaQueryWrapper<TChargingOrder>().eq(TChargingOrder::getChargingGunId, addChargingOrder.getId())
            .eq(TChargingOrder::getAppUserCarId, userId).eq(TChargingOrder::getRechargePaymentStatus, 1).eq(TChargingOrder::getDelFlag, 0));
            .eq(TChargingOrder::getAppUserId, userId).eq(TChargingOrder::getRechargePaymentStatus, 1)
            .eq(TChargingOrder::getDelFlag, 0).last("limit 1"));
      if (null != one) {
         //查询三方支付数据,支付中直接结束三方订单
         if ("ICBC".equals(one.getSourceName())) {
@@ -791,12 +861,11 @@
      chargingOrder.setOrderClassification(1);
      chargingOrder.setAppUserId(userId);
      chargingOrder.setAppUserCarId(addChargingOrder.getAppUserCarId());
      if (null != addChargingOrder.getAppUserCarId()) {
         TAppUserCar userCar = appUserCarClient.getCarById(addChargingOrder.getAppUserCarId().toString()).getData();
         if (null != userCar) {
            chargingOrder.setPlateNum(userCar.getLicensePlate());
         }
      if (selectedCar != null) {
         chargingOrder.setPlateNum(selectedCar.getLicensePlate());
         chargingOrder.setExpectedVin(selectedVin);
      }
      chargingOrder.setReservationTime(addChargingOrder.getReservationTime());
      chargingOrder.setSiteId(tChargingGun.getSiteId());
      chargingOrder.setChargingPileId(tChargingGun.getChargingPileId());
      chargingOrder.setChargingGunId(addChargingOrder.getId());
@@ -809,6 +878,7 @@
      chargingOrder.setThirdRefundAmount(BigDecimal.ZERO);
      chargingOrder.setRechargeAmount(rechargeAmount);
      chargingOrder.setAppCouponId(addChargingOrder.getAppUserCouponId());
      chargingOrder.setCouponDiscountAmount(upfrontCouponDiscount);
      chargingOrder.setIsWhitelist(whitelistMatched ? 1 : 0);
      chargingOrder.setWhitelistId(whitelistMatched ? whitelist.getWhitelistId() : null);
      chargingOrder.setWhitelistBenefitType(whitelistMatched ? whitelist.getBenefitType() : null);
@@ -817,12 +887,16 @@
      chargingOrder.setWhitelistServiceRefundAmount(BigDecimal.ZERO);
      chargingOrder.setVipDiscountAmount(BigDecimal.ZERO);
      chargingOrder.setOrderSource(1);
      if (thirdPayAmount.compareTo(BigDecimal.ZERO) > 0) {
      if (thirdPayAmount.compareTo(BigDecimal.ZERO) > 0 && Integer.valueOf(1).equals(addChargingOrder.getPaymentType())) {
         chargingOrder.setSourceName("ICBC");
      }
      chargingOrder.setTitle("【充电桩充电】" + chargingPile.getNumber() + "号桩/" + tChargingGun.getCode() + "号枪");
      
      Site site = siteClient.getSiteByIds(Arrays.asList(tChargingGun.getSiteId())).getData().get(0);
      AjaxResult reservationValidation = validateReservation(site, addChargingOrder.getReservationTime());
      if (reservationValidation != null) {
         return reservationValidation;
      }
      Integer accountingStrategyId = tChargingGun.getAccountingStrategyId();
      if (null == accountingStrategyId) {
         //查询站点上面的计费策略
@@ -830,24 +904,25 @@
      }
      TAccountingStrategy accountingStrategy = accountingStrategyClient.getAccountingStrategyById(accountingStrategyId).getData();
      
      //直营站点才可以享受会员折扣
      if (!whitelistMatched && !isBalancePayMode(payMethod) && null != appUser.getVipId() && 1 == site.getBusinessCategory()) {
         TVip vip = vipClient.getInfo1(appUser.getVipId()).getData();
         BigDecimal discount = null;
         if (1 == vip.getType()) {
            //普通会员折扣使用积分策略上的折扣,且有最高优惠金额
            discount = accountingStrategy.getDiscount();
         } else {
            //内部会员使用会员信息设置的折扣,没有最高优惠金额
            discount = vip.getDiscount();
         }
      //会员折扣支持直营网点和加盟商站点
      if (!whitelistMatched && Integer.valueOf(1).equals(site.getMemberDiscountFlag())
            && !isBalancePayMode(payMethod) && null != appUser.getVipId()) {
         GetAppUserVipDetail getAppUserVipDetail = new GetAppUserVipDetail();
         getAppUserVipDetail.setAppUserId(appUser.getId());
         getAppUserVipDetail.setVipId(appUser.getVipId());
         TAppUserVipDetail vipDetail = appUserVipDetailClient.getAppUserVipDetail(getAppUserVipDetail).getData();
         if (null != vipDetail) {
         TVip vip = getVipSnapshot(vipDetail, appUser.getVipId());
         if (null != vip && null != vipDetail) {
            BigDecimal discount = null;
            if (Integer.valueOf(1).equals(vip.getType())) {
               //普通会员折扣使用积分策略上的折扣,且有最高优惠金额
               discount = accountingStrategy.getDiscount();
            } else {
               //内部会员使用会员信息设置的折扣,没有最高优惠金额
               discount = vip.getDiscount();
            }
            Integer chargeNum = vipDetail.getChargeNum();
            if (0 < chargeNum) {
            if (null != discount && null != chargeNum && 0 < chargeNum) {
               chargingOrder.setVipDiscount(discount);
            }
         }
@@ -880,21 +955,446 @@
      }
      //会员优惠折扣将其计入增加充电时长(增加总充电金额)
      //如果充电总金额未使用完,则需要退回费用=(原金额/总金额)*(总金额-实际充电金额)
      IcbcPaymentReq req = new IcbcPaymentReq();
      req.setOutTradeNo(chargingOrder.getCode());
      req.setAmount(thirdPayAmount);
      req.setSubject("充电充值");
      req.setBody("充电充值");
      req.setPaymentType(addChargingOrder.getPaymentType());
      req.setNotifyUrl("/payment/icbc/callBack");
      req.setReturnUrl(addChargingOrder.getReturnUrl());
      req.setOpenId(Integer.valueOf(1).equals(addChargingOrder.getPaymentType()) ? appUser.getWxOpenid() : appUser.getAliOpenid());
      req.setProfitSharing(buildIcbcProfitSharing(site));
      IcbcPaymentResp data = icbcPaymentClient.payment(req).getData();
      if (isIcbcPaymentCreated(data)) {
         return AjaxResult.success(data);
      if (Integer.valueOf(1).equals(addChargingOrder.getPaymentType())) {
         IcbcPaymentReq req = new IcbcPaymentReq();
         req.setOutTradeNo(chargingOrder.getCode());
         req.setAmount(thirdPayAmount);
         req.setSubject("充电充值");
         req.setBody("充电充值");
         req.setPaymentType(1);
         req.setNotifyUrl("/payment/icbc/callBack");
         req.setReturnUrl(addChargingOrder.getReturnUrl());
         req.setOpenId(appUser.getWxOpenid());
         req.setProfitSharing(buildIcbcProfitSharing(site));
         R<IcbcPaymentResp> result = icbcPaymentClient.payment(req);
         if (result == null || R.isError(result)) {
            String reason = result == null ? "工行支付服务无响应" : firstNotBlank(result.getMsg(), "工行支付服务调用失败");
            log.error("调起工行支付失败,订单号:{},原因:{}", chargingOrder.getCode(), reason);
            throw new RuntimeException("调起工行支付失败:" + reason);
         }
         IcbcPaymentResp data = result.getData();
         if (isIcbcPaymentCreated(data)) {
            if (StringUtils.hasLength(data.getTradeNo())) {
               chargingOrder.setRechargeSerialNumber(data.getTradeNo());
               this.updateById(chargingOrder);
            }
            return AjaxResult.success(data);
         }
         String reason = buildIcbcPaymentFailReason(data);
         log.error("调起工行支付失败,订单号:{},工行返回:{}", chargingOrder.getCode(), reason);
         throw new RuntimeException("调起工行支付失败:" + reason);
      }
      throw new RuntimeException("调起工行支付失败");
      if (Integer.valueOf(2).equals(addChargingOrder.getPaymentType())) {
         AliPaymentReq req = new AliPaymentReq();
         req.setOutTradeNo(chargingOrder.getCode());
         req.setTotalAmount(thirdPayAmount.toString());
         req.setSubject("充电充值");
         req.setBuyerOpenId(appUser.getAliOpenid());
         req.setBody("充电充值");
         req.setNotifyUrl("/payment/ali/callBack");
         AliPaymentResp data = aliPaymentClient.payment(req).getData();
         if (data != null) {
            return AjaxResult.success(data);
         }
         throw new RuntimeException("调起支付宝支付失败");
      }
      if (Integer.valueOf(3).equals(addChargingOrder.getPaymentType())) {
         AliPaymentReq req = new AliPaymentReq();
         req.setOutTradeNo(chargingOrder.getCode());
         req.setTotalAmount(thirdPayAmount.toString());
         req.setSubject("充电充值");
         req.setBuyerOpenId(appUser.getAliOpenid());
         req.setBody("充电充值");
         req.setNotifyUrl("/payment/aliH5/callBack");
         req.setReturnUrl(addChargingOrder.getReturnUrl());
         String data = h5AliPaymentClient.payment(req).getData();
         if (StringUtils.hasLength(data)) {
            return AjaxResult.success("操作成功", data);
         }
      }
      throw new RuntimeException("无效的支付方式");
   }
   @Override
   public ChargingSimulationVO getCurrentSimulationOrder(Integer gunId) {
      if (gunId == null) {
         throw new ServiceException("充电枪不能为空");
      }
      Long userId = tokenService.getLoginUserApplet().getUserId();
      return buildSimulationVO(findSimulationOrder(userId, gunId));
   }
   @Override
   public ChargingSimulationVO completeSimulation(ChargingSimulationReq request) {
      if (request == null || request.getGunId() == null) {
         throw new ServiceException("充电枪不能为空");
      }
      Long userId = tokenService.getLoginUserApplet().getUserId();
      TChargingOrder order = findSimulationOrder(userId, request.getGunId());
      List<UploadRealTimeMonitoringData> monitoringData = uploadRealTimeMonitoringDataService.getDataByOrderCode(order.getCode());
      if (monitoringData != null && !monitoringData.isEmpty()) {
         throw new ServiceException("订单已存在真实充电监控数据,禁止模拟结算");
      }
      BigDecimal electricityFee = money(request.getElectricityFee());
      BigDecimal serviceFee = money(request.getServiceFee());
      BigDecimal simulationTotal = electricityFee.add(serviceFee).setScale(2, RoundingMode.HALF_UP);
      BigDecimal availableAmount = ChargingSimulationSupport.availableAmount(order);
      if (simulationTotal.compareTo(availableAmount) > 0) {
         throw new ServiceException("模拟消费金额不能超过订单可消费金额" + availableAmount.toPlainString() + "元");
      }
      List<AccountingStrategyDetailOrder> tariffs = accountingStrategyDetailOrderService.list(
            new LambdaQueryWrapper<AccountingStrategyDetailOrder>()
                  .eq(AccountingStrategyDetailOrder::getChargingOrderId, order.getId())
                  .orderByAsc(AccountingStrategyDetailOrder::getStartTime));
      ChargingSimulationSupport.PreparedSimulation prepared;
      try {
         prepared = ChargingSimulationSupport.prepare(order.getCode(), tariffs, electricityFee, serviceFee, LocalDateTime.now());
      } catch (IllegalArgumentException e) {
         throw new ServiceException(e.getMessage());
      }
      accountingStrategyDetailOrderService.updateById(prepared.getTariff());
      TChargingGun gun = chargingGunClient.getChargingGunById(order.getChargingGunId()).getData();
      TChargingPile pile = chargingPileClient.getChargingPileById(order.getChargingPileId()).getData();
      TransactionRecordMessageVO record = prepared.getRecord();
      record.setCharging_gun_code(gun == null ? null : gun.getCode());
      record.setCharging_pile_code(pile == null ? null : pile.getCode());
      if (Integer.valueOf(2).equals(order.getStatus())) {
         TChargingOrder charging = new TChargingOrder();
         charging.setId(order.getId());
         charging.setStatus(3);
         charging.setStartTime(LocalDateTime.now().minusSeconds(1));
         this.updateById(charging);
      }
      R settlement = chargingOrderSettlementExecutor.settle(record);
      if (R.isError(settlement)) {
         throw new ServiceException(StringUtils.hasLength(settlement.getMsg()) ? settlement.getMsg() : "模拟结算失败");
      }
      return buildSimulationVO(this.getById(order.getId()));
   }
   private TChargingOrder findSimulationOrder(Long userId, Integer gunId) {
      TChargingOrder order = this.getOne(new LambdaQueryWrapper<TChargingOrder>()
            .eq(TChargingOrder::getAppUserId, userId)
            .eq(TChargingOrder::getChargingGunId, gunId)
            .eq(TChargingOrder::getRechargePaymentStatus, 2)
            .in(TChargingOrder::getStatus, Arrays.asList(2, 3, 4))
            .eq(TChargingOrder::getDelFlag, 0)
            .isNull(TChargingOrder::getRefundStatus)
            .orderByDesc(TChargingOrder::getId)
            .last("limit 1"));
      if (order == null) {
         throw new ServiceException("当前枪没有属于你的可模拟已支付订单");
      }
      return order;
   }
   private ChargingSimulationVO buildSimulationVO(TChargingOrder order) {
      ChargingSimulationVO result = new ChargingSimulationVO();
      result.setOrderId(order.getId());
      result.setOrderCode(order.getCode());
      result.setOrderStatus(order.getStatus());
      result.setPaymentMode(ChargingSimulationSupport.paymentMode(order));
      result.setPayMethod(order.getPayMethod());
      result.setRechargePaymentType(order.getRechargePaymentType());
      result.setSourceName(order.getSourceName());
      result.setRechargeAmount(order.getRechargeAmount());
      result.setChargeAmount(order.getChargeAmount());
      result.setAvailableAmount(ChargingSimulationSupport.availableAmount(order));
      result.setBalancePayAmount(order.getBalancePayAmount());
      result.setThirdPayAmount(order.getThirdPayAmount());
      result.setElectricityFee(order.getElectrovalence());
      result.setServiceFee(order.getServiceCharge());
      result.setPaymentAmount(order.getPaymentAmount());
      result.setRefundAmount(order.getRefundAmount());
      result.setBalanceRefundAmount(order.getBalanceRefundAmount());
      result.setThirdRefundAmount(order.getThirdRefundAmount());
      result.setRefundStatus(order.getRefundStatus());
      result.setIcbcOrderId(order.getRechargeSerialNumber());
      TChargingOrderShareRecord shareRecord = chargingOrderShareRecordMapper.selectOne(
            new LambdaQueryWrapper<TChargingOrderShareRecord>()
                  .eq(TChargingOrderShareRecord::getChargingOrderId, order.getId())
                  .eq(TChargingOrderShareRecord::getPayChannel, "ICBC")
                  .eq(TChargingOrderShareRecord::getDelFlag, false)
                  .orderByDesc(TChargingOrderShareRecord::getId)
                  .last("limit 1"));
      if (shareRecord == null) {
         shareRecord = chargingOrderShareRecordMapper.selectOne(
               new LambdaQueryWrapper<TChargingOrderShareRecord>()
                     .eq(TChargingOrderShareRecord::getChargingOrderId, order.getId())
                     .eq(TChargingOrderShareRecord::getDelFlag, false)
                     .orderByDesc(TChargingOrderShareRecord::getId)
                     .last("limit 1"));
      }
      if (shareRecord != null) {
         result.setShareAmount(shareRecord.getShareAmount());
         result.setShareStatus(shareRecord.getOnlineShareStatus());
         result.setSubOrderId(shareRecord.getSubOrderId());
         result.setSubOrderSeq(shareRecord.getSubOrderSeq());
         result.setSubOrderTrxid(shareRecord.getSubOrderTrxid());
      }
      return result;
   }
   @Override
   public IcbcPaymentResp queryIcbcAdminPayment(String orderCode) {
      TChargingOrder order = loadIcbcAdminOrder(orderCode, false);
      return requireFeignData(icbcPaymentClient.query(order.getCode()), "工行支付查询失败");
   }
   @Transactional
   @Override
   public AjaxResult confirmIcbcPayment(String orderCode) {
      if (!StringUtils.hasLength(orderCode)) {
         return AjaxResult.error("订单号不能为空");
      }
      Long userId = tokenService.getLoginUserApplet().getUserId();
      TChargingOrder order = this.getOne(new LambdaQueryWrapper<TChargingOrder>()
            .eq(TChargingOrder::getCode, orderCode)
            .eq(TChargingOrder::getAppUserId, userId)
            .eq(TChargingOrder::getDelFlag, 0)
            .last("limit 1"));
      if (order == null) {
         return AjaxResult.error("支付订单不存在");
      }
      if (Integer.valueOf(2).equals(order.getRechargePaymentStatus())) {
         return AjaxResult.success(Collections.singletonMap("confirmed", true));
      }
      if (!isIcbcOrder(order)) {
         return AjaxResult.error("当前订单不是工行支付订单");
      }
      R<IcbcPaymentResp> queryResult = icbcPaymentClient.query(orderCode);
      IcbcPaymentResp payment = queryResult == null ? null : queryResult.getData();
      if (!IcbcAdminFundOperationSupport.isBankPaid(payment)
            || payment == null
            || !orderCode.equals(payment.getOrderId())
            || !StringUtils.hasLength(payment.getTradeNo())) {
         return AjaxResult.success(Collections.singletonMap("confirmed", false));
      }
      AjaxResult callbackResult = chargingOrderCallback(1, orderCode, payment.getTradeNo(), "1");
      if (!Integer.valueOf(200).equals(callbackResult.get("code"))) {
         return callbackResult;
      }
      return AjaxResult.success(Collections.singletonMap("confirmed", true));
   }
   @Override
   public IcbcRefundResp refundIcbcAdminPayment(IcbcAdminFundOperationReq request) {
      if (request == null) {
         throw new ServiceException("请求参数不能为空");
      }
      TChargingOrder order = loadIcbcAdminOrder(request.getOrderCode(), false);
      BigDecimal amount;
      try {
         amount = IcbcAdminFundOperationSupport.requireAmount(request.getAmount());
      } catch (IllegalArgumentException e) {
         throw new ServiceException(e.getMessage());
      }
      BigDecimal paidAmount = money(order.getThirdPayAmount()).compareTo(BigDecimal.ZERO) > 0
            ? money(order.getThirdPayAmount()) : money(order.getRechargeAmount());
      if (amount.compareTo(paidAmount) > 0) {
         throw new ServiceException("退款金额不能超过订单第三方支付金额" + paidAmount.toPlainString() + "元");
      }
      RLock lock = redissonClient.getLock("icbcAdminFund:order:" + order.getCode());
      if (!lock.tryLock()) {
         throw new ServiceException("该订单正在执行工行资金操作,请勿重复提交");
      }
      try {
         IcbcPaymentResp payment = requireFeignData(icbcPaymentClient.query(order.getCode()), "工行支付查询失败");
         if (!IcbcAdminFundOperationSupport.isBankPaid(payment)) {
            throw new ServiceException("工行查询未确认该订单支付成功,禁止退款");
         }
         String orderId = extractIcbcOrderId(payment);
         order.setRechargeSerialNumber(orderId);
         // 仅用于本次请求通过退款参数校验,不更新本地订单状态。
         order.setRechargePaymentStatus(2);
         RBucket<String> pendingRefund = redissonClient.getBucket("icbcAdminFund:pending:main:" + order.getCode());
         String generatedRefundNo = "ICBCADMINREFUND" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
         String pendingValue = pendingRefund.get();
         boolean reusingPending = StringUtils.hasLength(pendingValue);
         String refundNo;
         try {
            refundNo = IcbcAdminFundOperationSupport.resolveRefundNo(pendingValue, amount, generatedRefundNo);
         } catch (IllegalArgumentException e) {
            throw new ServiceException(e.getMessage());
         }
         IcbcRefundReq refundRequest = IcbcAdminFundOperationSupport.buildRefundRequest(order, amount, refundNo);
         if (!reusingPending) {
            pendingRefund.set(IcbcAdminFundOperationSupport.buildPendingRefundValue(refundNo, amount));
         }
         IcbcRefundResp response = requireFeignData(icbcPaymentClient.refund(refundRequest), "工行退款调用失败");
         if (IcbcAdminFundOperationSupport.shouldClearPendingRefund(
               reusingPending, response.getStatus(), isDefinitiveIcbcResponse(response.getRawResponse()))) {
            pendingRefund.delete();
         }
         return response;
      } finally {
         if (lock.isHeldByCurrentThread()) {
            lock.unlock();
         }
      }
   }
   @Override
   public List<IcbcAdminSubOrderVO> queryIcbcAdminSubOrders(String orderCode) {
      TChargingOrder order = loadIcbcAdminOrder(orderCode, true);
      List<TChargingOrderShareRecord> records = chargingOrderShareRecordMapper.selectList(
            new LambdaQueryWrapper<TChargingOrderShareRecord>()
                  .eq(TChargingOrderShareRecord::getChargingOrderId, order.getId())
                  .eq(TChargingOrderShareRecord::getPayChannel, "ICBC")
                  .eq(TChargingOrderShareRecord::getDelFlag, false)
                  .orderByAsc(TChargingOrderShareRecord::getId));
      List<IcbcAdminSubOrderVO> result = new ArrayList<>();
      for (TChargingOrderShareRecord record : records) {
         IcbcAdminSubOrderVO item = buildIcbcAdminSubOrderVO(record);
         if (!Boolean.TRUE.equals(record.getNeedOnlineShare())) {
            item.setQueryStatus("SKIP");
            item.setErrorMessage(record.getFailReason());
            result.add(item);
            continue;
         }
         try {
            IcbcSubOrderQueryReq query = IcbcAdminFundOperationSupport.buildSubOrderQueryRequest(order, record);
            item.applyQuery(requireFeignData(icbcPaymentClient.querySubOrder(query), "工行子订单查询失败"));
         } catch (Exception e) {
            item.setQueryStatus("FAIL");
            item.setErrorMessage(e.getMessage());
         }
         result.add(item);
      }
      return result;
   }
   @Override
   public IcbcSubOrderRefundResp refundIcbcAdminSubOrder(IcbcAdminFundOperationReq request) {
      if (request == null || request.getShareRecordId() == null) {
         throw new ServiceException("请选择需要退款的分账子订单");
      }
      TChargingOrder order = loadIcbcAdminOrder(request.getOrderCode(), true);
      TChargingOrderShareRecord shareRecord = chargingOrderShareRecordMapper.selectById(request.getShareRecordId());
      if (shareRecord == null || !order.getId().equals(shareRecord.getChargingOrderId())) {
         throw new ServiceException("分账记录不属于当前订单");
      }
      BigDecimal amount;
      try {
         amount = IcbcAdminFundOperationSupport.requireAmount(request.getAmount());
      } catch (IllegalArgumentException e) {
         throw new ServiceException(e.getMessage());
      }
      if (shareRecord.getShareAmount() != null && amount.compareTo(shareRecord.getShareAmount()) > 0) {
         throw new ServiceException("分账退款金额不能超过分账金额" + shareRecord.getShareAmount().toPlainString() + "元");
      }
      RLock lock = redissonClient.getLock("icbcAdminFund:order:" + order.getCode());
      if (!lock.tryLock()) {
         throw new ServiceException("该分账子订单正在退款,请勿重复提交");
      }
      try {
         String suffix = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
         RBucket<String> pendingRefund = redissonClient.getBucket("icbcAdminFund:pending:sub:" + shareRecord.getId());
         String generatedToken = "ICBCSUBREFUND" + suffix + ":ICBCSUBREFUNDNO" + suffix;
         String pendingValue = pendingRefund.get();
         boolean reusingPending = StringUtils.hasLength(pendingValue);
         String operationToken;
         try {
            operationToken = IcbcAdminFundOperationSupport.resolveRefundNo(pendingValue, amount, generatedToken);
         } catch (IllegalArgumentException e) {
            throw new ServiceException(e.getMessage());
         }
         String[] refundNumbers = operationToken.split(":", 2);
         if (refundNumbers.length != 2) {
            throw new ServiceException("工行分账退款幂等记录格式错误");
         }
         IcbcSubOrderRefundReq refundRequest;
         try {
            refundRequest = IcbcAdminFundOperationSupport.buildSubOrderRefundRequest(
                  order, shareRecord, amount, refundNumbers[0], refundNumbers[1]);
         } catch (IllegalArgumentException e) {
            throw new ServiceException(e.getMessage());
         }
         if (!reusingPending) {
            pendingRefund.set(IcbcAdminFundOperationSupport.buildPendingRefundValue(operationToken, amount));
         }
         IcbcSubOrderRefundResp response = requireFeignData(
               icbcPaymentClient.refundSubOrder(refundRequest), "工行分账退款调用失败");
         if (IcbcAdminFundOperationSupport.shouldClearPendingRefund(
               reusingPending, response.getStatus(), isDefinitiveIcbcResponse(response.getRawResponse()))) {
            pendingRefund.delete();
         }
         return response;
      } finally {
         if (lock.isHeldByCurrentThread()) {
            lock.unlock();
         }
      }
   }
   private TChargingOrder loadIcbcAdminOrder(String orderCode, boolean requireLocalPaid) {
      if (!StringUtils.hasLength(orderCode)) {
         throw new ServiceException("订单号不能为空");
      }
      TChargingOrder order = this.getOne(new LambdaQueryWrapper<TChargingOrder>()
            .eq(TChargingOrder::getCode, orderCode)
            .eq(TChargingOrder::getDelFlag, 0)
            .last("limit 1"));
      try {
         if (requireLocalPaid) {
            IcbcAdminFundOperationSupport.validateOrder(order);
         } else {
            IcbcAdminFundOperationSupport.validateIcbcOrder(order);
         }
      } catch (IllegalArgumentException e) {
         throw new ServiceException(e.getMessage());
      }
      return order;
   }
   private String extractIcbcOrderId(IcbcPaymentResp payment) {
      try {
         JSONObject response = JSON.parseObject(payment.getRawResponse());
         String orderId = response == null ? null : response.getString("order_id");
         if (StringUtils.hasLength(orderId)) {
            return orderId;
         }
      } catch (Exception ignored) {
      }
      throw new ServiceException("工行支付查询未返回真实order_id,禁止退款");
   }
   private boolean isDefinitiveIcbcResponse(String rawResponse) {
      if (!StringUtils.hasLength(rawResponse)) {
         return false;
      }
      try {
         JSONObject response = JSON.parseObject(rawResponse);
         return response != null && (response.containsKey("returnCode") || response.containsKey("return_code"));
      } catch (Exception ignored) {
         return false;
      }
   }
   private IcbcAdminSubOrderVO buildIcbcAdminSubOrderVO(TChargingOrderShareRecord record) {
      IcbcAdminSubOrderVO item = new IcbcAdminSubOrderVO();
      item.setShareRecordId(record.getId());
      item.setOrderCode(record.getChargingOrderCode());
      item.setShareAmount(record.getShareAmount());
      item.setOnlineShareStatus(record.getOnlineShareStatus());
      item.setSubOrderId(record.getSubOrderId());
      item.setSubMerId(record.getSubMerId());
      item.setSubMerPrtclNo(record.getSubMerPrtclNo());
      item.setOriTrxDate(record.getOriTrxDate());
      item.setBankOrderId(record.getIcbcOrderId());
      item.setSubOrderSeq(record.getSubOrderSeq());
      item.setSubOrderTrxid(record.getSubOrderTrxid());
      return item;
   }
   private <T> T requireFeignData(R<T> result, String message) {
      if (result == null || R.isError(result) || result.getData() == null) {
         throw new ServiceException(result != null && StringUtils.hasLength(result.getMsg()) ? result.getMsg() : message);
      }
      return result.getData();
   }
   
   
@@ -911,7 +1411,26 @@
   @Override
   public AjaxResult chargingOrderCallback(Integer paymentType, String out_trade_no, String transaction_id, String attach) {
      TChargingOrder chargingOrder = this.getOne(new LambdaQueryWrapper<TChargingOrder>().eq(TChargingOrder::getCode, out_trade_no));
      if (chargingOrder == null) {
         return AjaxResult.error("支付回调订单不存在");
      }
      if (chargingOrder.getRechargePaymentStatus() == 2) {
         if (isIcbcOrder(chargingOrder) && StringUtils.hasLength(transaction_id)
               && !transaction_id.equals(chargingOrder.getRechargeSerialNumber())) {
            chargingOrder.setRechargeSerialNumber(transaction_id);
            this.updateById(chargingOrder);
         }
         return AjaxResult.success();
      }
      LocalDateTime callbackPayTime = LocalDateTime.now();
      int claimed = this.baseMapper.update(null, new LambdaUpdateWrapper<TChargingOrder>()
            .eq(TChargingOrder::getId, chargingOrder.getId())
            .eq(TChargingOrder::getRechargePaymentStatus, 1)
            .set(TChargingOrder::getRechargePaymentStatus, 2)
            .set(TChargingOrder::getRechargeSerialNumber, transaction_id)
            .set(TChargingOrder::getStatus, 2)
            .set(TChargingOrder::getPayTime, callbackPayTime));
      if (claimed == 0) {
         return AjaxResult.success();
      }
      if (isBalancePayMode(chargingOrder.getPayMethod())
@@ -927,7 +1446,7 @@
      chargingOrder.setRechargePaymentStatus(2);
      chargingOrder.setRechargeSerialNumber(transaction_id);
      chargingOrder.setStatus(2);
      chargingOrder.setPayTime(LocalDateTime.now());
      chargingOrder.setPayTime(callbackPayTime);
      
      //添加安全检测数据到缓存中,每步安全检测完成后需要更新缓存数据
      PreChargeCheck preChargeCheck = new PreChargeCheck();
@@ -935,6 +1454,8 @@
      preChargeCheck.setInsulationTesting(false);
      preChargeCheck.setSecureConnectionDetection(false);
      preChargeCheck.setStartupSuccess(1);
      preChargeCheck.setStartCommandSent(false);
        preChargeCheck.setAddTime(chargingOrder.getReservationTime() != null ? chargingOrder.getReservationTime() : LocalDateTime.now());
      String key = "AQJC_" + chargingOrder.getChargingGunId();
      redisService.setCacheObject(key, preChargeCheck, 24L, TimeUnit.HOURS);
      
@@ -1012,10 +1533,10 @@
            
            if (discountAmount.compareTo(BigDecimal.ZERO) >= 0) {
               //计算会员最大优惠金额
               TVip vip = vipClient.getInfo1(appUser.getVipId()).getData();
               BigDecimal maximumDeduction = vip.getMaximumDeduction();
               TVip vip = getVipSnapshot(data, appUser.getVipId());
               BigDecimal maximumDeduction = vip == null ? null : vip.getMaximumDeduction();
               //普通会员有最高优惠限制
               if (vip.getType() == 1 && discountAmount.compareTo(maximumDeduction) > 0) {
               if (vip != null && Integer.valueOf(1).equals(vip.getType()) && maximumDeduction != null && discountAmount.compareTo(maximumDeduction) > 0) {
                  discountAmount = maximumDeduction;
               }
            }
@@ -1034,42 +1555,143 @@
      this.updateById(chargingOrder);
      
      
      TChargingPile chargingPile = chargingPileClient.getChargingPileById(tChargingGun.getChargingPileId()).getData();
      //调用远程启动充电消息
      PlatformStartCharging platformStartCharging = new PlatformStartCharging();
      platformStartCharging.setTransaction_serial_number(chargingOrder.getCode());
      platformStartCharging.setCharging_pile_code(chargingPile.getCode());
      platformStartCharging.setCharging_gun_code(tChargingGun.getCode());
      //使用订单id作为逻辑卡号
      platformStartCharging.setCard_number(chargingOrder.getId().toString());
      platformStartCharging.setAccount_balance(account_balance);
      log.info(chargingOrder.getCode() + ":-------------------远程调起开始充电请求-------------------" + platformStartCharging.toString());
      sendMessageClient.platformStartCharging(platformStartCharging);
      ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
      threadPoolExecutor.execute(()->{
         //推送成都监管
         NotificationConnectorChargeStatusVo vo = new NotificationConnectorChargeStatusVo();
         vo.setOrderCode(chargingOrder.getCode());
         vo.setChargingGunId(chargingOrder.getChargingGunId());
         chengDuJianGuanClient.notificationConnectorChargeStatus(vo);
      });
      //异步线程检测远程启动的应答结果。如果失败,则需要全额退款
      Long id = chargingOrder.getId();
      //执行10分钟的定时任务检测
      ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
      scheduler.scheduleAtFixedRate(()->{
         if(timingDetection(id)){
            scheduler.shutdown();
         }
      }, 10, 1, TimeUnit.SECONDS);
      scheduleReservationStart(chargingOrder);
      return AjaxResult.success();
   }
   private AjaxResult validateReservation(Site site, LocalDateTime reservationTime) {
      if (reservationTime == null) {
         return null;
      }
      if (!Integer.valueOf(1).equals(site.getSupportOrder())) {
         return AjaxResult.error("该电站暂不支持预约充电");
      }
      if (reservationTime.isBefore(LocalDateTime.now().minusMinutes(1))) {
         return AjaxResult.error("预约时间不能早于当前时间");
      }
      if (site.getReservationAheadMinute() != null && site.getReservationAheadMinute() >= 0
            && reservationTime.isAfter(LocalDateTime.now().plusMinutes(site.getReservationAheadMinute()))) {
         return AjaxResult.error("预约时间不能超过最早可提前预约时限");
      }
      if (!StringUtils.hasLength(site.getReservationStartTime())
            || !StringUtils.hasLength(site.getReservationEndTime())) {
         return AjaxResult.error("电站尚未配置预约时段");
      }
      LocalTime start = LocalTime.parse(site.getReservationStartTime(), DateTimeFormatter.ofPattern("HH:mm"));
      LocalTime end = LocalTime.parse(site.getReservationEndTime(), DateTimeFormatter.ofPattern("HH:mm"));
      if (!isTimeInWindow(reservationTime.toLocalTime(), start, end)) {
         return AjaxResult.error("预约时间不在允许预约时段内");
      }
      return null;
   }
   private boolean isTimeInWindow(LocalTime value, LocalTime start, LocalTime end) {
      if (start.equals(end)) {
         return true;
      }
      if (start.isBefore(end)) {
         return !value.isBefore(start) && !value.isAfter(end);
      }
      return !value.isBefore(start) || !value.isAfter(end);
   }
   private boolean isSafetyCheckPassed(PreChargeCheck check) {
      return check != null
            && Boolean.TRUE.equals(check.getElectronicLockLock())
            && Boolean.TRUE.equals(check.getInsulationTesting())
            && Boolean.TRUE.equals(check.getSecureConnectionDetection());
   }
   private String normalizeVin(String vin) {
      return StringUtils.hasLength(vin) ? vin.replaceAll("\\s+", "").toUpperCase(Locale.ROOT) : null;
   }
   private boolean vinMatches(String expectedVin, String actualVin) {
      if (!StringUtils.hasLength(expectedVin) || !StringUtils.hasLength(actualVin)) {
         return false;
      }
      return expectedVin.equals(actualVin)
            || expectedVin.equals(new StringBuilder(actualVin).reverse().toString());
   }
   private void scheduleReservationStart(TChargingOrder chargingOrder) {
      if (chargingOrder.getReservationTime() == null || !chargingOrder.getReservationTime().isAfter(LocalDateTime.now())) {
         return;
      }
      long delay = Math.max(0L, Duration.between(LocalDateTime.now(), chargingOrder.getReservationTime()).toMillis());
      ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
      scheduler.schedule(() -> {
         try {
            TChargingOrder latest = this.getById(chargingOrder.getId());
            if (latest == null || latest.getStatus() != 2) {
               return;
            }
            PreChargeCheck check = redisService.getCacheObject("AQJC_" + latest.getChargingGunId());
            if (isSafetyCheckPassed(check)) {
               startChargingAfterSafety(latest, check);
            }
         } finally {
            scheduler.shutdown();
         }
      }, delay, TimeUnit.MILLISECONDS);
   }
   private void startChargingAfterSafety(TChargingOrder chargingOrder, PreChargeCheck check) {
      RLock lock = redissonClient.getLock("START_AFTER_SAFETY:" + chargingOrder.getId());
      try {
         if (!lock.tryLock(1, 10, TimeUnit.SECONDS)) {
            return;
         }
         TChargingOrder latest = this.getById(chargingOrder.getId());
         if (latest == null || latest.getStatus() != 2 || Boolean.TRUE.equals(check.getStartCommandSent())) {
            return;
         }
         if (latest.getReservationTime() != null && latest.getReservationTime().isAfter(LocalDateTime.now())) {
            return;
         }
         TChargingGun gun = chargingGunClient.getChargingGunById(latest.getChargingGunId()).getData();
         TChargingPile pile = chargingPileClient.getChargingPileById(gun.getChargingPileId()).getData();
         String actualVin = normalizeVin(check.getActualVin());
         String expectedVin = normalizeVin(latest.getExpectedVin());
         if (Integer.valueOf(1).equals(pile.getVinFlag())
               && !vinMatches(expectedVin, actualVin)) {
            check.setStartupSuccess(3);
            check.setFailureCause(9);
            latest.setActualVin(actualVin);
            this.updateById(latest);
            redisService.setCacheObject("AQJC_" + latest.getChargingGunId(), check, 24L, TimeUnit.HOURS);
            refund(latest.getCode());
            return;
         }
         PlatformStartCharging command = new PlatformStartCharging();
         command.setTransaction_serial_number(latest.getCode());
         command.setCharging_pile_code(pile.getCode());
         command.setCharging_gun_code(gun.getCode());
         command.setCard_number(latest.getId().toString());
         command.setAccount_balance(latest.getChargeAmount());
         check.setStartCommandSent(true);
         latest.setActualVin(actualVin);
         this.updateById(latest);
         redisService.setCacheObject("AQJC_" + latest.getChargingGunId(), check, 24L, TimeUnit.HOURS);
         log.info(latest.getCode() + ":安全检测及VIN校验通过,下发启动充电命令:" + command);
         sendMessageClient.platformStartCharging(command);
         ScheduledExecutorService resultScheduler = Executors.newScheduledThreadPool(1);
         resultScheduler.scheduleAtFixedRate(() -> {
            if (timingDetection(latest.getId())) {
               resultScheduler.shutdown();
            }
         }, 10, 1, TimeUnit.SECONDS);
      } catch (InterruptedException e) {
         Thread.currentThread().interrupt();
         throw new ServiceException("启动充电锁等待被中断");
      } finally {
         if (lock.isHeldByCurrentThread()) {
            lock.unlock();
         }
      }
   }
   /**
    * 定时检测mongodb数据库数据
    *
@@ -1231,10 +1853,23 @@
      TChargingGun chargingGun = chargingGunClient.getChargingGunByCode(code).getData();
      PreChargeCheck preChargeCheck1 = redisService.getCacheObject("AQJC_" + chargingGun.getId());
      if (null != preChargeCheck1) {
         preChargeCheck1.setElectronicLockLock(true);
         preChargeCheck1.setInsulationTesting(true);
         preChargeCheck1.setSecureConnectionDetection(securityDetection.getSecure_connection() == 1);
         preChargeCheck1.setElectronicLockLock(Integer.valueOf(1).equals(securityDetection.getElectronic_lock_lock()));
         preChargeCheck1.setInsulationTesting(Integer.valueOf(0).equals(securityDetection.getNsulation_detection()));
         preChargeCheck1.setSecureConnectionDetection(Integer.valueOf(1).equals(securityDetection.getSecure_connection()));
         preChargeCheck1.setActualVin(normalizeVin(securityDetection.getVin()));
         redisService.setCacheObject("AQJC_" + chargingGun.getId(), preChargeCheck1, 24L, TimeUnit.HOURS);
         if (isSafetyCheckPassed(preChargeCheck1)) {
            TChargingOrder pendingOrder = this.getOne(new LambdaQueryWrapper<TChargingOrder>()
                  .eq(TChargingOrder::getChargingGunId, chargingGun.getId())
                  .eq(TChargingOrder::getRechargePaymentStatus, 2)
                  .eq(TChargingOrder::getStatus, 2)
                  .eq(TChargingOrder::getDelFlag, 0)
                  .orderByDesc(TChargingOrder::getCreateTime)
                  .last("limit 1"));
            if (pendingOrder != null) {
               startChargingAfterSafety(pendingOrder, preChargeCheck1);
            }
         }
      }
   }
   
@@ -1281,6 +1916,7 @@
            //推送状态给三方平台
            tcecClient.pushChargingGunStatus(chargingGun.getFullNumber(), 4);
         }
         wxSubscribeMessageService.sendChargeStart(order, site, chargingGun);
      }
      redisService.setCacheObject("AQJC_" + order.getChargingGunId(), preChargeCheck1, 24L, TimeUnit.HOURS);
      
@@ -1757,12 +2393,23 @@
    * @param chargingOrder
    */
   public void editUserTag(TChargingOrder chargingOrder) {
      if (chargingOrder == null || chargingOrder.getAppUserId() == null) {
         return;
      }
      //处理用户标签数据
      List<TUserTag> data = userTagClient.getAllUserTag().getData();
      R<List<TUserTag>> tagResult = userTagClient.getAllUserTag();
      List<TUserTag> data = tagResult == null ? null : tagResult.getData();
      if (data == null || data.isEmpty()) {
         return;
      }
      //累计充电次数
      List<TUserTag> userTagList1 = data.stream().filter(s -> s.getStandardCondition() == 1).collect(Collectors.toList());
      List<TUserTag> userTagList1 = data.stream().filter(s -> Integer.valueOf(1).equals(s.getStandardCondition())).collect(Collectors.toList());
      for (TUserTag tUserTag : userTagList1) {
         Integer times = JSON.parseObject(tUserTag.getConditions()).getInteger("times");
         JSONObject conditions = JSON.parseObject(tUserTag.getConditions());
         Integer times = conditions == null ? null : conditions.getInteger("times");
         if (times == null || times <= 0 || tUserTag.getCreateTime() == null) {
            continue;
         }
         TAppUserTag appUserTag = new TAppUserTag();
         appUserTag.setAppUserId(chargingOrder.getAppUserId());
         appUserTag.setUserTagId(tUserTag.getId());
@@ -1781,18 +2428,21 @@
         }
      }
      //充电频率
      List<TUserTag> userTagList2 = data.stream().filter(s -> s.getStandardCondition() == 2).collect(Collectors.toList());
      List<TUserTag> userTagList2 = data.stream().filter(s -> Integer.valueOf(2).equals(s.getStandardCondition())).collect(Collectors.toList());
      for (TUserTag tUserTag : userTagList2) {
         TAppUserTag appUserTag = new TAppUserTag();
         appUserTag.setAppUserId(chargingOrder.getAppUserId());
         appUserTag.setUserTagId(tUserTag.getId());
         TAppUserTag data1 = appUserTagClient.getUserTag(appUserTag).getData();
         
         Integer day = JSON.parseObject(tUserTag.getConditions()).getInteger("day");
         Integer times = JSON.parseObject(tUserTag.getConditions()).getInteger("times");
         JSONObject conditions = JSON.parseObject(tUserTag.getConditions());
         Integer day = conditions == null ? null : conditions.getInteger("day");
         Integer times = conditions == null ? null : conditions.getInteger("times");
         if (day == null || day <= 0 || times == null || times <= 0) {
            continue;
         }
         LocalDateTime endTime = LocalDateTime.now();
         endTime.plusDays(day);
         String start = tUserTag.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
         String start = endTime.minusDays(day).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
         String end = endTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
         long count1 = this.count(new LambdaQueryWrapper<TChargingOrder>().eq(TChargingOrder::getAppUserId, chargingOrder.getAppUserId())
               .eq(TChargingOrder::getRechargePaymentStatus, 2).between(TChargingOrder::getStartTime, start, end).eq(TChargingOrder::getDelFlag, 0));
@@ -1825,10 +2475,14 @@
         
         TAppUser appUser1 = appUserClient.getUserById(appUser.getInviteUserId()).getData();
         if (null != appUser1.getVipId()) {
            TVip vip = vipClient.getInfo1(appUser1.getVipId()).getData();
            Integer doubleIntegration = vip.getDoubleIntegration();
            GetAppUserVipDetail getAppUserVipDetail = new GetAppUserVipDetail();
            getAppUserVipDetail.setAppUserId(appUser1.getId());
            getAppUserVipDetail.setVipId(appUser1.getVipId());
            TAppUserVipDetail vipDetail = appUserVipDetailClient.getAppUserVipDetail(getAppUserVipDetail).getData();
            TVip vip = getVipSnapshot(vipDetail, appUser1.getVipId());
            Integer doubleIntegration = vip == null ? null : vip.getDoubleIntegration();
            //双倍积分
            if (1 == doubleIntegration) {
            if (Integer.valueOf(1).equals(doubleIntegration)) {
               num1 *= 2;
            }
            
@@ -1916,17 +2570,84 @@
   
   @Override
   public TCharingOrderVO chargingOrder(ChargingOrderQuery dto) {
      TCharingOrderVO summary = chargingOrderSummary(dto);
      summary.setList(chargingOrderList(dto));
      return summary;
   }
   @Override
   public PageInfo<ChargingOrderVO> chargingOrderList(ChargingOrderQuery dto) {
      String[] timeRange = parseChargingOrderTimeRange(dto);
      String startTime1 = timeRange[0];
      String startTime2 = timeRange[1];
      String endTime1 = timeRange[2];
      String endTime2 = timeRange[3];
      PageInfo<ChargingOrderVO> pageInfo = new PageInfo<>(dto.getPageCurr(), dto.getPageSize());
      if (StringUtils.hasLength(dto.getPhone())) {
         List<Long> data = appUserClient.getUserIdsByPhone(dto.getPhone()).getData();
         if (data == null || data.isEmpty()) {
            return pageInfo;
         }
         dto.setUserIds(data);
      }
      Long userId = tokenService.getLoginUser().getUserid();
      SysUser sysUser = sysUserClient.getSysUser(userId).getData();
      Integer roleType = sysUser.getRoleType();
      setChargingOrderAuthSiteIds(dto, sysUser, userId, roleType);
      List<ChargingOrderVO> list = this.baseMapper.chargingOrder(pageInfo, dto, startTime1, startTime2, endTime1, endTime2);
      fillChargingOrderBalanceIncome(list);
      fillChargingOrderShareIncome(list);
      fillChargingOrderPageExtra(list, dto.getSiteIds(), sysUser, roleType);
      pageInfo.setRecords(list);
      return pageInfo;
   }
   @Override
   public TCharingOrderVO chargingOrderSummary(ChargingOrderQuery dto) {
      TCharingOrderVO tCharingOrderVO = new TCharingOrderVO();
      List<TChargingGun> allGun = chargingGunClient.getAllGun().getData();
      List<TChargingPile> allPile = chargingGunClient.getAllPile().getData();
      List<Site> data9 = siteClient.getSiteAll().getData();
      List<TAppUser> data6 = appUserClient.getAllUser().getData();
      List<TAppUserCar> data4 = appUserCarClient.getAllCar().getData();
      fillEmptyChargingOrderSummary(tCharingOrderVO);
      String[] timeRange = parseChargingOrderTimeRange(dto);
      String startTime1 = timeRange[0];
      String startTime2 = timeRange[1];
      String endTime1 = timeRange[2];
      String endTime2 = timeRange[3];
      if (StringUtils.hasLength(dto.getPhone())) {
         List<Long> data = appUserClient.getUserIdsByPhone(dto.getPhone()).getData();
         if (data == null || data.isEmpty()) {
            return tCharingOrderVO;
         }
         dto.setUserIds(data);
      }
      Long userId = tokenService.getLoginUser().getUserid();
      SysUser sysUser = sysUserClient.getSysUser(userId).getData();
      setChargingOrderAuthSiteIds(dto, sysUser, userId, sysUser.getRoleType());
      Map<String, Object> map = this.baseMapper.chargingOrderBaseSummary(dto, startTime1, startTime2, endTime1, endTime2);
      if (null != map) {
         tCharingOrderVO.setTotal(getSummaryDecimal(map, "total"));
         tCharingOrderVO.setTime(getSummaryLong(map, "time"));
         tCharingOrderVO.setIncome(getSummaryDecimal(map, "income"));
         tCharingOrderVO.setElectronicMoney(getSummaryDecimal(map, "electronicMoney"));
         tCharingOrderVO.setServiceMoney(getSummaryDecimal(map, "serviceMoney"));
         tCharingOrderVO.setWechatIncome(getSummaryDecimal(map, "wechatIncome"));
         tCharingOrderVO.setAlipayIncome(getSummaryDecimal(map, "alipayIncome"));
         tCharingOrderVO.setWhitelistChargingIncome(getSummaryDecimal(map, "whitelistChargingIncome"));
         tCharingOrderVO.setWhitelistRefund(getSummaryDecimal(map, "whitelistRefund"));
      }
      List<Long> summaryOrderIds = this.baseMapper.chargingOrderSummaryOrderIds(dto, startTime1, startTime2, endTime1, endTime2);
      Map<String, Object> shareIncomeMap = getChargingOrderShareIncomeSummary(summaryOrderIds);
      tCharingOrderVO.setPlatformIncomeTotal(getSummaryDecimal(shareIncomeMap, "platformIncomeTotal"));
      tCharingOrderVO.setPartnerIncomeTotal(getSummaryDecimal(shareIncomeMap, "partnerIncomeTotal"));
      Map<String, Object> balanceMap = getExchangeCodeBalanceSummary(summaryOrderIds);
      tCharingOrderVO.setBalanceIncome(getSummaryDecimal(balanceMap, "balanceIncome"));
      tCharingOrderVO.setBalanceGiftIncome(getSummaryDecimal(balanceMap, "balanceGiftIncome"));
      return tCharingOrderVO;
   }
   private String[] parseChargingOrderTimeRange(ChargingOrderQuery dto) {
      String startTime1 = null;
      String startTime2 = null;
      String endTime1 = null;
      String endTime2 = null;
      if (StringUtils.hasLength(dto.getStartTime())) {
         String[] split = dto.getStartTime().split(" - ");
         startTime1 = split[0];
@@ -1937,115 +2658,76 @@
         endTime1 = split[0];
         endTime2 = split[1];
      }
      PageInfo<ChargingOrderVO> pageInfo = new PageInfo<>(dto.getPageCurr(), dto.getPageSize());
      if (StringUtils.hasLength(dto.getPhone())) {
         List<Long> data = appUserClient.getUserIdsByPhone(dto.getPhone()).getData();
         if (data.isEmpty()) {
            tCharingOrderVO.setList(pageInfo);
            return tCharingOrderVO;
         }
         dto.setUserIds(data);
      return new String[]{startTime1, startTime2, endTime1, endTime2};
   }
   private void fillChargingOrderPageExtra(List<ChargingOrderVO> list, List<Integer> siteIds, SysUser sysUser, Integer roleType) {
      if (list == null || list.isEmpty()) {
         return;
      }
      Long userId = tokenService.getLoginUser().getUserid();
      // 查询当前登陆人按钮权限
      SysUser sysUser = sysUserClient.getSysUser(tokenService.getLoginUser().getUserid()).getData();
      Integer roleType = sysUser.getRoleType();
         setChargingOrderAuthSiteIds(dto, sysUser, userId, roleType);
         List<Integer> siteIds = dto.getSiteIds();
         List<ChargingOrderVO> list = this.baseMapper.chargingOrder(pageInfo, dto, startTime1, startTime2, endTime1, endTime2);
         fillChargingOrderBalanceIncome(list);
         fillChargingOrderShareIncome(list);
         List<TChargingOrderAccountingStrategy> list3 = chargingOrderAccountingStrategyService.list();
      List<Long> orderIds = list.stream().map(ChargingOrderVO::getId).filter(Objects::nonNull).collect(Collectors.toList());
      List<Integer> pageSiteIds = list.stream().map(ChargingOrderVO::getSiteId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
      List<Integer> pageGunIds = list.stream().map(ChargingOrderVO::getChargingGunId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
      List<TChargingGun> pageGuns = pageGunIds.isEmpty() ? new ArrayList<>() : Optional.ofNullable(chargingGunClient.getChargingGunByIds(pageGunIds).getData()).orElse(new ArrayList<>());
      Map<Integer, TChargingGun> gunMap = pageGuns.stream()
            .filter(item -> item.getId() != null)
            .collect(Collectors.toMap(TChargingGun::getId, item -> item, (a, b) -> a));
      List<TChargingPile> pagePiles = pageSiteIds.isEmpty() ? new ArrayList<>() : Optional.ofNullable(chargingPileClient.getChargingPileBySiteIds(pageSiteIds).getData()).orElse(new ArrayList<>());
      Map<Integer, TChargingPile> pileMap = pagePiles.stream()
            .filter(item -> item.getId() != null)
            .collect(Collectors.toMap(TChargingPile::getId, item -> item, (a, b) -> a));
      List<Site> pageSites = pageSiteIds.isEmpty() ? new ArrayList<>() : Optional.ofNullable(siteClient.getSiteByIds(pageSiteIds).getData()).orElse(new ArrayList<>());
      Map<Integer, Site> siteMap = pageSites.stream()
            .filter(item -> item.getId() != null)
            .collect(Collectors.toMap(Site::getId, item -> item, (a, b) -> a));
      Map<Long, List<TChargingOrderAccountingStrategy>> strategyMap = orderIds.isEmpty() ? new HashMap<>() :
            chargingOrderAccountingStrategyService.lambdaQuery()
                  .in(TChargingOrderAccountingStrategy::getChargingOrderId, orderIds)
                  .list()
                  .stream()
                  .collect(Collectors.groupingBy(TChargingOrderAccountingStrategy::getChargingOrderId));
      Map<Integer, Boolean> authRecordMap = new HashMap<>();
      if (roleType == 2 && siteIds != null) {
         for (Integer siteId : siteIds) {
            List<Boolean> permission = partnerClient.getChargingOrderMenu(sysUser.getObjectId(), siteId).getData();
            authRecordMap.put(siteId, permission != null && !permission.isEmpty() && Boolean.TRUE.equals(permission.get(0)));
         }
      }
      for (ChargingOrderVO chargingOrderVO : list) {
         if (roleType == 2) {
            for (Integer siteId : siteIds) {
               // 校验有没有这个站点的权限
               List<Boolean> t1 = partnerClient.getChargingOrderMenu(sysUser.getObjectId(), siteId).getData();
               chargingOrderVO.setAuthRecord(t1.get(0));
            }
         if (roleType == 2 && chargingOrderVO.getSiteId() != null) {
            chargingOrderVO.setAuthRecord(Boolean.TRUE.equals(authRecordMap.get(chargingOrderVO.getSiteId())));
         }
         chargingOrderVO.setCommissionAmount(chargingOrderVO.getRechargeAmount() != null ? chargingOrderVO.getRechargeAmount().multiply(new BigDecimal("0.006")) : new BigDecimal("0"));
         chargingOrderVO.setPlatFormMoney(chargingOrderVO.getRechargeAmount() != null ? chargingOrderVO.getRechargeAmount().multiply(new BigDecimal("0.006")).setScale(2, BigDecimal.ROUND_DOWN) : new BigDecimal("0"));
         chargingOrderVO.setUid(chargingOrderVO.getId() + "");
         TChargingGun data3 = allGun.stream().filter(e -> e.getId().equals(chargingOrderVO.getChargingGunId())).findFirst().orElse(null);
         TChargingPile data2 = allPile.stream().filter(e -> e.getId().equals(chargingOrderVO.getChargingPileId())).findFirst().orElse(null);
         if (chargingOrderVO.getSiteId() != null) {
            List<Integer> integers = new ArrayList<>();
            integers.add(chargingOrderVO.getSiteId());
            Site site = data9.stream().filter(e -> e.getId().equals(chargingOrderVO.getSiteId())).findFirst().orElse(null);
            if (site != null) chargingOrderVO.setSiteName(site.getName());
         Site site = siteMap.get(chargingOrderVO.getSiteId());
         if (site != null) {
            chargingOrderVO.setSiteName(site.getName());
         }
         if (data2 != null && data3 != null) {
            chargingOrderVO.setTerminalName(data2.getName() + "-" + data3.getName());
         TChargingGun gun = gunMap.get(chargingOrderVO.getChargingGunId());
         TChargingPile pile = pileMap.get(chargingOrderVO.getChargingPileId());
         if (pile != null && gun != null) {
            chargingOrderVO.setTerminalName(pile.getName() + "-" + gun.getName());
         }
         // 充电订单 明细记录
         List<TChargingOrderAccountingStrategy> chargingOrderId = list3.stream().filter(e -> e.getChargingOrderId().equals(chargingOrderVO.getId())).collect(Collectors.toList());
         List<TChargingOrderAccountingStrategy> strategies = strategyMap.getOrDefault(chargingOrderVO.getId(), new ArrayList<>());
         if (chargingOrderVO.getStartTime() != null && chargingOrderVO.getEndTime() != null) {
            LocalDateTime startTime = chargingOrderVO.getStartTime();
            LocalDateTime endTime = chargingOrderVO.getEndTime();
            // 计算时间差 单位秒
            long between = ChronoUnit.SECONDS.between(startTime, endTime);
            long between = ChronoUnit.SECONDS.between(chargingOrderVO.getStartTime(), chargingOrderVO.getEndTime());
            chargingOrderVO.setChargingSecond(between);
            BigDecimal electronic_reduce = chargingOrderId.stream().map(TChargingOrderAccountingStrategy::getPeriodElectricPrice).reduce(BigDecimal.ZERO, BigDecimal::add);
            chargingOrderVO.setElectrovalence(electronic_reduce);
            BigDecimal service_reduce = chargingOrderId.stream().map(TChargingOrderAccountingStrategy::getPeriodServicePrice).reduce(BigDecimal.ZERO, BigDecimal::add);
            if (!chargingOrderVO.getOrderSource().equals(2)) {
               chargingOrderVO.setServiceCharge(service_reduce);
            BigDecimal electronicReduce = strategies.stream().map(TChargingOrderAccountingStrategy::getPeriodElectricPrice).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
            chargingOrderVO.setElectrovalence(electronicReduce);
            BigDecimal serviceReduce = strategies.stream().map(TChargingOrderAccountingStrategy::getPeriodServicePrice).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
            if (!Objects.equals(chargingOrderVO.getOrderSource(), 2)) {
               chargingOrderVO.setServiceCharge(serviceReduce);
               chargingOrderVO.setSharingAmount(BigDecimal.ZERO);
            } else {
               chargingOrderVO.setServiceCharge(service_reduce.multiply(new BigDecimal("0.8")));
               chargingOrderVO.setSharingAmount(service_reduce.multiply(new BigDecimal("0.2")));
               chargingOrderVO.setServiceCharge(serviceReduce.multiply(new BigDecimal("0.8")));
               chargingOrderVO.setSharingAmount(serviceReduce.multiply(new BigDecimal("0.2")));
            }
         }
         // 充电时段数
         int size = chargingOrderId.size();
         chargingOrderVO.setCount(size);
         // 用户手机号
         TAppUser data = data6.stream().filter(e -> e.getId().equals(chargingOrderVO.getAppUserId())).findFirst().orElse(null);
         if (data != null) {
            // 车牌号
            chargingOrderVO.setPhone(data.getPhone());
            List<Long> longs = new ArrayList<>();
            if (chargingOrderVO.getAppUserCarId() != null) {
               longs.add(chargingOrderVO.getAppUserCarId());
               TAppUserCar tAppUserCar = data4.stream().filter(e -> e.getId().equals(chargingOrderVO.getAppUserCarId())).findFirst().orElse(null);
               if (tAppUserCar != null) {
                  chargingOrderVO.setLicensePlate(tAppUserCar.getLicensePlate());
               }
            } else {
               chargingOrderVO.setLicensePlate(chargingOrderVO.getPlateNum());
            }
         }
         chargingOrderVO.setCount(strategies.size());
         chargingOrderVO.setLicensePlate(chargingOrderVO.getPlateNum());
      }
      fillEmptyChargingOrderSummary(tCharingOrderVO);
      Map<String, Object> map = this.baseMapper.chargingOrderNolimit(dto, startTime1, startTime2, endTime1, endTime2);
      if (null != map) {
         tCharingOrderVO.setTotal(getSummaryDecimal(map, "total"));
         tCharingOrderVO.setTime(getSummaryLong(map, "time"));
         tCharingOrderVO.setIncome(getSummaryDecimal(map, "income"));
         tCharingOrderVO.setElectronicMoney(getSummaryDecimal(map, "electronicMoney"));
         tCharingOrderVO.setServiceMoney(getSummaryDecimal(map, "serviceMoney"));
      }
      Map<String, Object> fundMap = this.baseMapper.chargingOrderFundSummary(dto, startTime1, startTime2, endTime1, endTime2);
      if (null != fundMap) {
         tCharingOrderVO.setWechatIncome(getSummaryDecimal(fundMap, "wechatIncome"));
         tCharingOrderVO.setAlipayIncome(getSummaryDecimal(fundMap, "alipayIncome"));
         tCharingOrderVO.setWhitelistChargingIncome(getSummaryDecimal(fundMap, "whitelistChargingIncome"));
         tCharingOrderVO.setWhitelistRefund(getSummaryDecimal(fundMap, "whitelistRefund"));
      }
      Map<String, Object> shareIncomeMap = getChargingOrderShareIncomeSummary(dto, startTime1, startTime2, endTime1, endTime2);
      tCharingOrderVO.setPlatformIncomeTotal(getSummaryDecimal(shareIncomeMap, "platformIncomeTotal"));
      tCharingOrderVO.setPartnerIncomeTotal(getSummaryDecimal(shareIncomeMap, "partnerIncomeTotal"));
      Map<String, Object> balanceMap = getExchangeCodeBalanceSummary(dto, startTime1, startTime2, endTime1, endTime2);
      tCharingOrderVO.setBalanceIncome(getSummaryDecimal(balanceMap, "balanceIncome"));
      tCharingOrderVO.setBalanceGiftIncome(getSummaryDecimal(balanceMap, "balanceGiftIncome"));
      pageInfo.setRecords(list);
      tCharingOrderVO.setList(pageInfo);
         return tCharingOrderVO;
      }
   }
      /**
       * 填充充电桩订单列表余额收入,余额收入按真实充值余额统计,赠送余额单独展示。
@@ -2195,11 +2877,10 @@
   /**
    * 汇总充电桩订单余额收入,兑换码使用记录的usedAmount包含赠送余额,SQL中需扣除giftUsedAmount。
    */
   private Map<String, Object> getExchangeCodeBalanceSummary(ChargingOrderQuery dto, String startTime1, String startTime2, String endTime1, String endTime2) {
   private Map<String, Object> getExchangeCodeBalanceSummary(List<Long> orderIds) {
      Map<String, Object> totalMap = new HashMap<>();
      totalMap.put("balanceIncome", BigDecimal.ZERO);
      totalMap.put("balanceGiftIncome", BigDecimal.ZERO);
      List<Long> orderIds = this.baseMapper.chargingOrderSummaryOrderIds(dto, startTime1, startTime2, endTime1, endTime2);
      if (orderIds == null || orderIds.isEmpty()) {
         return totalMap;
      }
@@ -2219,11 +2900,10 @@
      return totalMap;
   }
   private Map<String, Object> getChargingOrderShareIncomeSummary(ChargingOrderQuery dto, String startTime1, String startTime2, String endTime1, String endTime2) {
   private Map<String, Object> getChargingOrderShareIncomeSummary(List<Long> orderIds) {
      Map<String, Object> totalMap = new HashMap<>();
      totalMap.put("platformIncomeTotal", BigDecimal.ZERO);
      totalMap.put("partnerIncomeTotal", BigDecimal.ZERO);
      List<Long> orderIds = this.baseMapper.chargingOrderSummaryOrderIds(dto, startTime1, startTime2, endTime1, endTime2);
      if (orderIds == null || orderIds.isEmpty()) {
         return totalMap;
      }
@@ -2276,13 +2956,23 @@
   @Override
   public R<PageInfo<PayOrderDto>> payOrderQuery(PayOrderQueryDto payOrderQueryDto) {
      if (payOrderQueryDto == null) {
         payOrderQueryDto = new PayOrderQueryDto();
      }
      PageInfo<PayOrderDto> pageInfo = new PageInfo<>(payOrderQueryDto.getPageCurr(), payOrderQueryDto.getPageSize());
      List<PayOrderDto> list = this.baseMapper.payOrderQuery(pageInfo, payOrderQueryDto);
      if (list == null || list.isEmpty()) {
         pageInfo.setRecords(Collections.emptyList());
         return R.ok(pageInfo);
      }
      for (PayOrderDto payOrderDto : list) {
         if (payOrderDto.getType() == 2 || payOrderDto.getType() == 3) {
         if (payOrderDto == null) {
            continue;
         }
         if (Integer.valueOf(2).equals(payOrderDto.getType()) || Integer.valueOf(3).equals(payOrderDto.getType())) {
            payOrderDto.setFinalAmount(payOrderDto.getOrderAmount());
         }
         if (payOrderDto.getType() == 1 && payOrderDto.getStatus() == 3) {
         if (Integer.valueOf(1).equals(payOrderDto.getType()) && Integer.valueOf(3).equals(payOrderDto.getStatus())) {
            payOrderDto.setFinalAmount(payOrderDto.getOrderAmount());
            payOrderDto.setOrderAmount(payOrderDto.getOrderAmount());
            payOrderDto.setPaymentAmount(payOrderDto.getOrderAmount());
@@ -2291,7 +2981,62 @@
      pageInfo.setRecords(list);
      return R.ok(pageInfo);
   }
      @Override
   public R<PageInfo<EnterpriseChargingAnalysisVO>> enterpriseChargingAnalysis(EnterpriseChargingAnalysisQuery query) {
      PageInfo<EnterpriseChargingAnalysisVO> pageInfo = new PageInfo<>(query.getPageCurr(), query.getPageSize());
      List<EnterpriseChargingAnalysisVO> list = this.baseMapper.enterpriseChargingAnalysis(pageInfo, query);
      for (EnterpriseChargingAnalysisVO row : list) {
         EnterpriseChargingAnalysisQuery rowQuery = buildEnterpriseRowQuery(query, row);
         List<Map<String, Object>> topSites = this.baseMapper.enterpriseTopSites(rowQuery);
         String topSiteNames = topSites.stream()
               .map(item -> Objects.toString(item.get("siteName"), ""))
               .filter(org.springframework.util.StringUtils::hasText)
               .collect(Collectors.joining("、"));
         row.setTopSiteNames(topSiteNames);
         row.setAverageChargingInterval(calculateAverageChargingInterval(this.baseMapper.enterpriseChargingTimes(rowQuery)));
      }
      pageInfo.setRecords(list);
      return R.ok(pageInfo);
   }
   @Override
   public R<PageInfo<EnterpriseChargingDetailVO>> enterpriseChargingDetail(EnterpriseChargingAnalysisQuery query) {
      PageInfo<EnterpriseChargingDetailVO> pageInfo = new PageInfo<>(query.getPageCurr(), query.getPageSize());
      List<EnterpriseChargingDetailVO> list = this.baseMapper.enterpriseChargingDetail(pageInfo, query);
      pageInfo.setRecords(list);
      return R.ok(pageInfo);
   }
   @Override
   public List<EnterpriseChargingDetailVO> enterpriseChargingDetailExport(EnterpriseChargingAnalysisQuery query) {
      return this.baseMapper.enterpriseChargingDetailExport(query);
   }
   private EnterpriseChargingAnalysisQuery buildEnterpriseRowQuery(EnterpriseChargingAnalysisQuery source, EnterpriseChargingAnalysisVO row) {
      EnterpriseChargingAnalysisQuery rowQuery = new EnterpriseChargingAnalysisQuery();
      BeanUtils.copyProperties(source, rowQuery);
      rowQuery.setCompanyName(null);
      rowQuery.setExactCompanyName(row.getCompanyName());
      rowQuery.setSocialCreditCode(row.getSocialCreditCode());
      return rowQuery;
   }
   private BigDecimal calculateAverageChargingInterval(List<LocalDateTime> times) {
      if (times == null || times.size() < 2) {
         return BigDecimal.ZERO;
      }
      long totalMinutes = 0L;
      for (int i = 1; i < times.size(); i++) {
         long minutes = ChronoUnit.MINUTES.between(times.get(i - 1), times.get(i));
         if (minutes > 0) {
            totalMinutes += minutes;
         }
      }
      return BigDecimal.valueOf(totalMinutes)
            .divide(BigDecimal.valueOf(times.size() - 1L), 4, RoundingMode.HALF_UP)
            .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
   }
   @Override
   public R<PageInfo<TChargingOrderRefund>> getRefundList(ChargingRefundDto chargingRefundDto) {
      PageInfo<TChargingOrderRefund> pageInfo = new PageInfo<>(chargingRefundDto.getPageCurr(), chargingRefundDto.getPageSize());
@@ -3183,15 +3928,19 @@
    @Transactional
   public R endChargeBillingCharge(TransactionRecordMessageVO vo) {
      RLock lock = redissonClient.getLock("endOrder:" + vo.getTransaction_serial_number());
      boolean locked = false;
      try {
            boolean tryLock = lock.tryLock(60, 60, TimeUnit.SECONDS);
            locked = lock.tryLock(60, 60, TimeUnit.SECONDS);
            if (!locked) {
                return R.fail("订单正在结算,请勿重复操作");
            }
            //添加redis缓存数据,过期时间,用于防止处理缓慢且硬件重复发送报文导致的重复退款
            Boolean hasKey = redisService.hasKey("END_ORDER:" + vo.getTransaction_serial_number());
            if(hasKey){
                return R.fail("不能重复退款");
            }
            redisService.setCacheObject("END_ORDER:" + vo.getTransaction_serial_number(), System.currentTimeMillis() + "", 10L, TimeUnit.MINUTES);
            if(tryLock){
            if(locked){
                TChargingOrder chargingOrder = this.getOne(new LambdaQueryWrapper<TChargingOrder>().eq(TChargingOrder::getCode, vo.getTransaction_serial_number()));
                if (null == chargingOrder) {
                    log.error("订单不存在,订单号:{}", vo.getTransaction_serial_number());
@@ -3436,14 +4185,18 @@
                }
                //计算优惠券
                BigDecimal couponDiscount = BigDecimal.ZERO;
                boolean couponAppliedUpfront = money(chargingOrder.getCouponDiscountAmount()).compareTo(BigDecimal.ZERO) > 0;
                BigDecimal couponDiscount = couponAppliedUpfront
                        ? money(chargingOrder.getCouponDiscountAmount()) : BigDecimal.ZERO;
                if (!isWhitelistOrder(chargingOrder) && !isBalancePayMode(chargingOrder.getPayMethod()) && null != chargingOrder.getAppCouponId()) {
                    //判断实际充电金额是否满足优惠券使用条件,如果不满足则不适用优惠券。
                    TAppCoupon appCoupon = appCouponClient.getAppCouponById(chargingOrder.getAppCouponId()).getData();
                    String couponJson = appCoupon.getCouponJson();
                    TCoupon tCoupon = JSON.parseObject(couponJson, TCoupon.class);
                    Integer preferentialMode = tCoupon.getPreferentialMode();
                    if (1 == preferentialMode) {
                    if (couponAppliedUpfront) {
                        appCoupon.setStatus(2);
                        appCouponClient.updateAppCoupon(appCoupon);
                    } else if (1 == preferentialMode) {
                        //满减
                        if (payAmount.compareTo(tCoupon.getMeetTheConditions()) >= 0) {
                            BigDecimal couponDiscountAmount = tCoupon.getDiscountAmount();
@@ -3462,7 +4215,7 @@
                            chargingOrder.setCouponDiscountAmount(BigDecimal.ZERO);
                        }
                    }
                    if (2 == preferentialMode) {
                    if (!couponAppliedUpfront && 2 == preferentialMode) {
                        //抵扣
                        if (payAmount.compareTo(tCoupon.getMeetTheConditions()) >= 0) {
                            //折扣金额
@@ -3498,13 +4251,13 @@
                }
                //优惠券优惠金额
                couponDiscount = couponDiscount.setScale(4, RoundingMode.HALF_EVEN);
                //退款金额+优惠券
                refundAmount = refundAmount.add(couponDiscount);
                //实际支付金额-优惠券
                payAmount = payAmount.subtract(couponDiscount);
                if (serviceCharge_total.compareTo(BigDecimal.ZERO) > 0) {
                    serviceCharge_total = serviceCharge_total.subtract(couponDiscount);
                //历史订单在结算时计算优惠;新订单已在预付时少收优惠金额,不能再次增加退款。
                if (!couponAppliedUpfront) {
                    refundAmount = refundAmount.add(couponDiscount);
                }
                //实际支付金额-优惠券
                payAmount = payAmount.subtract(couponDiscount).max(BigDecimal.ZERO);
                serviceCharge_total = serviceCharge_total.subtract(couponDiscount).max(BigDecimal.ZERO);
                chargingOrder.setCouponDiscountAmount(couponDiscount.setScale(2, RoundingMode.HALF_EVEN));
                chargingOrder.setWhitelistDiscountAmount(whitelistDiscount.setScale(2, RoundingMode.HALF_EVEN));
                chargingOrder.setWhitelistElectricRefundAmount(electricDiscount.setScale(2, RoundingMode.HALF_EVEN));
@@ -3610,19 +4363,25 @@
                if (2 == chargingOrder.getOrderSource() && 1 == site.getIsOpenSynchronization()) {
                    tcecClient.pushChargingGunStatus(chargingGun.getFullNumber(), 2);
                }
                wxSubscribeMessageService.sendChargeEnd(chargingOrder, site, chargingGun);
                //添加积分
                if (1 == chargingOrder.getOrderSource()) {
                    TAppUser appUser = appUserClient.getUserById(chargingOrder.getAppUserId()).getData();
                    sendVipChargeFinishMessage(appUser, chargingOrder, site);
                    TIntegralRule integralRule = integralRuleClient.getSet().getData();
                    if (null != integralRule) {
                        TAppUser appUser = appUserClient.getUserById(chargingOrder.getAppUserId()).getData();
                        Integer num1 = JSON.parseObject(integralRule.getChargeCredit()).getInteger("num1");
                        Integer integral = chargingOrder.getServiceCharge().intValue() * num1;
                        if (null != appUser.getVipId()) {
                            TVip vip = vipClient.getInfo1(appUser.getVipId()).getData();
                            Integer doubleIntegration = vip.getDoubleIntegration();
                            GetAppUserVipDetail getAppUserVipDetail = new GetAppUserVipDetail();
                            getAppUserVipDetail.setAppUserId(appUser.getId());
                            getAppUserVipDetail.setVipId(appUser.getVipId());
                            TAppUserVipDetail vipDetail = appUserVipDetailClient.getAppUserVipDetail(getAppUserVipDetail).getData();
                            TVip vip = getVipSnapshot(vipDetail, appUser.getVipId());
                            Integer doubleIntegration = vip == null ? null : vip.getDoubleIntegration();
                            //双倍积分
                            if (1 == doubleIntegration) {
                            if (Integer.valueOf(1).equals(doubleIntegration)) {
                                integral *= 2;
                            }
                        }
@@ -3683,8 +4442,15 @@
                            this.updateById(chargingOrder);
                            triggerChargingOrderShare(chargingOrder);
                        } else if (isIcbcOrder(chargingOrder)) {
                            refundByIcbcAndFinish(chargingOrder, chargingOrderRefund, thirdRefundAmount, "充电完成退款", false,
                                    refundAmount, balanceRefundAmount);
                            boolean refundSuccess = refundByIcbcAndFinish(chargingOrder, chargingOrderRefund, thirdRefundAmount,
                                    "充电完成退款", false, refundAmount, balanceRefundAmount);
                            if (!refundSuccess) {
                                chargingOrderRefund.setRefundStatus(1);
                                chargingOrderRefundService.save(chargingOrderRefund);
                                chargingOrder.setRefundStatus(1);
                                this.updateById(chargingOrder);
                                throw new ServiceException("工行退款失败,结算未完成,请检查支付流水后重试退款");
                            }
                        } else if (1 == rechargePaymentType) {
                            WxPaymentRefundModel model = new WxPaymentRefundModel();
                            model.setOut_trade_no(chargingOrder.getCode());
@@ -3827,9 +4593,12 @@
                chargingMessageClient.pushOrderStatus(chargingOrderVo);
            }
      } catch (Exception e) {
         e.printStackTrace();
         log.error("充电订单结算失败,订单号:{}", vo == null ? null : vo.getTransaction_serial_number(), e);
         return R.fail("充电订单结算失败:" + firstNotBlank(e.getMessage(), "未知异常"));
      } finally {
         lock.unlock();
         if (locked && lock.isHeldByCurrentThread()) {
            lock.unlock();
         }
      }
      return R.ok();
   }
@@ -3926,7 +4695,289 @@
   
   
   @Override
   public R<PayOrderInfoDto> getSpecialRefundOrder(PayOrderRefundDto payOrderQueryDto) {
      TChargingOrder order = getSpecialRefundChargingOrder(payOrderQueryDto);
      if (order == null) {
         return R.fail("未查询到支付成功的充电订单");
      }
      PayOrderInfoDto dto = new PayOrderInfoDto();
      dto.setOrderId(order.getId().toString());
      dto.setCode(order.getCode());
      dto.setTradeNo(order.getRechargeSerialNumber());
      dto.setPayType(order.getRechargePaymentType());
      dto.setPayAmount(money(order.getPaymentAmount()));
      dto.setPayTime(order.getPayTime());
      dto.setRefundAmount(money(order.getRefundAmount()));
      dto.setRechargeAmount(money(order.getRechargeAmount()));
      dto.setFinalAmount(money(order.getPaymentAmount()).subtract(money(order.getRefundAmount())).setScale(2, RoundingMode.HALF_UP));
      dto.setPayMethod(order.getPayMethod());
      dto.setBalancePayAmount(order.getBalancePayAmount());
      dto.setThirdPayAmount(order.getThirdPayAmount());
      dto.setBalanceRefundAmount(order.getBalanceRefundAmount());
      dto.setThirdRefundAmount(order.getThirdRefundAmount());
      return R.ok(dto);
   }
   @Override
   @Transactional
   public R specialRefund(PayOrderRefundDto payOrderQueryDto) {
      R passwordValid = validateOperationPassword(payOrderQueryDto);
      if (passwordValid.getCode() != 200) {
         return passwordValid;
      }
      if (payOrderQueryDto.getRefundAmount() == null || payOrderQueryDto.getRefundAmount().compareTo(BigDecimal.ZERO) <= 0) {
         return R.fail("请输入正确的退款金额");
      }
      TChargingOrder order = getSpecialRefundChargingOrder(payOrderQueryDto);
      if (order == null) {
         return R.fail("未查询到支付成功的充电订单");
      }
      RLock lock = redissonClient.getLock("SPECIAL_REFUND_ORDER_LOCK:" + order.getId());
      if (!lock.tryLock()) {
         return R.fail("该订单正在退款处理中,请勿重复提交");
      }
      try {
         order = this.getById(order.getId());
         if (order == null) {
            return R.fail("充电订单不存在");
         }
         BigDecimal refundAmount = money(payOrderQueryDto.getRefundAmount());
         BigDecimal refundableAmount = money(order.getPaymentAmount()).subtract(money(order.getRefundAmount())).setScale(2, RoundingMode.HALF_UP);
         if (refundAmount.compareTo(refundableAmount) > 0) {
            return R.fail("退款金额不能超过剩余可退金额");
         }
         BigDecimal refundedThirdAmount = money(order.getThirdRefundAmount());
         BigDecimal refundableThirdAmount = money(order.getThirdPayAmount()).subtract(refundedThirdAmount);
         if (refundableThirdAmount.compareTo(BigDecimal.ZERO) < 0) {
            refundableThirdAmount = BigDecimal.ZERO;
         }
         BigDecimal thirdRefundAmount = isBalancePayMode(order.getPayMethod())
               ? refundAmount.min(refundableThirdAmount).setScale(2, RoundingMode.HALF_UP)
               : refundAmount;
         BigDecimal balanceRefundAmount = refundAmount.subtract(thirdRefundAmount).setScale(2, RoundingMode.HALF_UP);
         if (balanceRefundAmount.compareTo(BigDecimal.ZERO) < 0) {
            balanceRefundAmount = BigDecimal.ZERO;
         }
         TChargingOrderRefund refund = buildSpecialChargingRefund(order, payOrderQueryDto, refundAmount, balanceRefundAmount, thirdRefundAmount);
         if (thirdRefundAmount.compareTo(BigDecimal.ZERO) == 0) {
            refund.setRefundSerialNumber("BALANCE");
            finishManualChargingRefund(order, refund, refundAmount, balanceRefundAmount, thirdRefundAmount);
            return R.ok();
         }
         Integer payType = order.getRechargePaymentType();
         boolean success = false;
         if (Integer.valueOf(1).equals(payType)) {
            R shareRefund = refundIcbcShareBeforeMainRefund(order, refund, thirdRefundAmount);
            if (shareRefund.getCode() != 200) {
               return shareRefund;
            }
            success = refundByIcbcAndFinish(order, refund, thirdRefundAmount, SPECIAL_REFUND_TITLE, true, refundAmount, balanceRefundAmount);
         } else if (Integer.valueOf(2).equals(payType)) {
            RefundReq dto = new RefundReq();
            dto.setOutTradeNo(order.getCode());
            dto.setOutRequestNo(refund.getRefundCode());
            dto.setRefundAmount(thirdRefundAmount.toString());
            dto.setRefundReason(SPECIAL_REFUND_TITLE);
            RefundResp resp = aliPaymentClient.refund(dto).getData();
            if (resp != null) {
               refund.setRefundSerialNumber(resp.getTradeNo());
               refund.setRefundTime(LocalDateTime.now());
               finishManualChargingRefund(order, refund, refundAmount, balanceRefundAmount, thirdRefundAmount);
               success = true;
            }
         } else if (Integer.valueOf(3).equals(payType)) {
            RefundReq dto = new RefundReq();
            dto.setOutTradeNo(order.getRechargeSerialNumber());
            dto.setOutRequestNo(refund.getRefundCode());
            dto.setRefundAmount(thirdRefundAmount.toString());
            dto.setRefundReason(SPECIAL_REFUND_TITLE);
            RefundResp resp = h5AliPaymentClient.refund(dto).getData();
            if (resp != null && "10000".equals(resp.getCode())) {
               refund.setRefundSerialNumber(resp.getTradeNo());
               refund.setRefundTime(LocalDateTime.now());
               finishManualChargingRefund(order, refund, refundAmount, balanceRefundAmount, thirdRefundAmount);
               success = true;
            }
         }
         return success ? R.ok() : R.fail("退款失败,请检查支付渠道或稍后重试");
      } finally {
         if (lock.isHeldByCurrentThread()) {
            lock.unlock();
         }
      }
   }
   private R refundIcbcShareBeforeMainRefund(TChargingOrder order, TChargingOrderRefund refund, BigDecimal thirdRefundAmount) {
      if (order == null || refund == null || thirdRefundAmount == null || thirdRefundAmount.compareTo(BigDecimal.ZERO) <= 0) {
         return R.ok();
      }
      if (!"ICBC".equals(order.getSourceName())) {
         return R.ok();
      }
      List<TChargingOrderShareRecord> shareRecords = chargingOrderShareRecordMapper.selectList(
            new LambdaQueryWrapper<TChargingOrderShareRecord>()
                  .eq(TChargingOrderShareRecord::getChargingOrderId, order.getId())
                  .eq(TChargingOrderShareRecord::getPayChannel, "ICBC")
                  .eq(TChargingOrderShareRecord::getNeedOnlineShare, true)
                  .eq(TChargingOrderShareRecord::getOnlineShareStatus, "SUCCESS")
                  .eq(TChargingOrderShareRecord::getDelFlag, false)
                  .orderByAsc(TChargingOrderShareRecord::getId));
      if (shareRecords == null || shareRecords.isEmpty()) {
         return R.ok();
      }
      BigDecimal thirdPayAmount = getRefundableThirdPayAmount(order);
      if (thirdPayAmount.compareTo(BigDecimal.ZERO) <= 0) {
         return R.fail("订单已分账但第三方支付金额异常,禁止退款");
      }
      BigDecimal cumulativeThirdRefund = money(order.getThirdRefundAmount()).add(thirdRefundAmount).min(thirdPayAmount).setScale(2, RoundingMode.HALF_UP);
      for (TChargingOrderShareRecord shareRecord : shareRecords) {
         BigDecimal shareAmount = money(shareRecord.getShareAmount());
         if (shareAmount.compareTo(BigDecimal.ZERO) <= 0) {
            continue;
         }
         BigDecimal targetRefundAmount = shareAmount.multiply(cumulativeThirdRefund).divide(thirdPayAmount, 2, RoundingMode.HALF_UP).min(shareAmount).setScale(2, RoundingMode.HALF_UP);
         BigDecimal refundedShareAmount;
         try {
            refundedShareAmount = queryIcbcShareRefundAmount(order, shareRecord);
         } catch (ServiceException e) {
            return R.fail(e.getMessage());
         }
         BigDecimal shareRefundAmount = targetRefundAmount.subtract(refundedShareAmount).setScale(2, RoundingMode.HALF_UP);
         if (shareRefundAmount.compareTo(BigDecimal.ZERO) <= 0) {
            continue;
         }
         R result = refundIcbcShareRecord(order, shareRecord, refund, shareRefundAmount);
         if (result.getCode() != 200) {
            return result;
         }
      }
      return R.ok();
   }
   private BigDecimal queryIcbcShareRefundAmount(TChargingOrder order, TChargingOrderShareRecord shareRecord) {
      try {
         IcbcSubOrderQueryReq queryReq = IcbcAdminFundOperationSupport.buildSubOrderQueryRequest(order, shareRecord);
         R<IcbcSubOrderQueryResp> queryResult = icbcPaymentClient.querySubOrder(queryReq);
         IcbcSubOrderQueryResp queryResp = queryResult == null ? null : queryResult.getData();
         if (queryResult == null || queryResult.getCode() != 200 || queryResp == null || !"SUCCESS".equals(queryResp.getStatus())) {
            throw new ServiceException(queryResult == null ? "工行子订单查询无响应" : firstNotBlank(queryResult.getMsg(), "工行子订单查询失败"));
         }
         return parseIcbcAmount(queryResp.getOrderRefundAmnt());
      } catch (IllegalArgumentException e) {
         throw new ServiceException(e.getMessage());
      } catch (ServiceException e) {
         throw e;
      } catch (Exception e) {
         throw new ServiceException("工行子订单查询异常:" + firstNotBlank(e.getMessage(), "未知异常"));
      }
   }
   private R refundIcbcShareRecord(TChargingOrder order, TChargingOrderShareRecord shareRecord, TChargingOrderRefund refund, BigDecimal shareRefundAmount) {
      String suffix = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
      String retSubOrderId = ("SPSUB" + refund.getRefundCode() + shareRecord.getId()).substring(0, Math.min(50, ("SPSUB" + refund.getRefundCode() + shareRecord.getId()).length()));
      String outtrxSerialNo = ("SPSUBREF" + suffix + shareRecord.getId()).substring(0, Math.min(50, ("SPSUBREF" + suffix + shareRecord.getId()).length()));
      try {
         IcbcSubOrderRefundReq refundReq = IcbcAdminFundOperationSupport.buildSubOrderRefundRequest(
               order, shareRecord, shareRefundAmount, retSubOrderId, outtrxSerialNo);
         refundReq.setMerSpInfor("特殊退款分账退回:" + order.getCode());
         R<IcbcSubOrderRefundResp> refundResult = icbcPaymentClient.refundSubOrder(refundReq);
         IcbcSubOrderRefundResp refundResp = refundResult == null ? null : refundResult.getData();
         if (refundResult == null || refundResult.getCode() != 200 || refundResp == null || !"SUCCESS".equals(refundResp.getStatus())) {
            String message = refundResult == null ? "工行分账退款无响应" : firstNotBlank(refundResult.getMsg(), refundResp == null ? null : refundResp.getRawResponse(), "工行分账退款失败");
            log.error("特殊退款分账退回失败,订单号:{},退款单号:{},分账记录:{},金额:{},原因:{}",
                  order.getCode(), refund.getRefundCode(), shareRecord.getId(), shareRefundAmount, message);
            return R.fail("分账退回失败:" + message);
         }
         log.info("特殊退款分账退回成功,订单号:{},退款单号:{},分账记录:{},金额:{}",
               order.getCode(), refund.getRefundCode(), shareRecord.getId(), shareRefundAmount);
         return R.ok();
      } catch (IllegalArgumentException | ServiceException e) {
         log.error("特殊退款分账退回失败,订单号:{},退款单号:{},分账记录:{},金额:{}",
               order.getCode(), refund.getRefundCode(), shareRecord.getId(), shareRefundAmount, e);
         return R.fail("分账退回失败:" + e.getMessage());
      } catch (Exception e) {
         log.error("特殊退款分账退回异常,订单号:{},退款单号:{},分账记录:{},金额:{}",
               order.getCode(), refund.getRefundCode(), shareRecord.getId(), shareRefundAmount, e);
         return R.fail("分账退回异常:" + firstNotBlank(e.getMessage(), "未知异常"));
      }
   }
   private BigDecimal parseIcbcAmount(String amount) {
      if (!StringUtils.hasLength(amount)) {
         return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
      }
      String value = amount.trim();
      try {
         if (value.contains(".")) {
            return money(new BigDecimal(value));
         }
         return new BigDecimal(value).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
      } catch (NumberFormatException e) {
         throw new ServiceException("工行金额格式异常:" + amount);
      }
   }
   private R validateOperationPassword(PayOrderRefundDto payOrderQueryDto) {
      if (payOrderQueryDto == null || StringUtils.isEmpty(payOrderQueryDto.getOperationPassword())) {
         return R.fail("请输入操作密码");
      }
      SysUser sysUser = sysUserClient.getSysUser(tokenService.getLoginUser().getUserid()).getData();
      if (sysUser == null || StringUtils.isEmpty(sysUser.getOperationPassword())) {
         return R.fail("当前账号未配置操作密码");
      }
      if (!SecurityUtils.matchesPassword(payOrderQueryDto.getOperationPassword(), sysUser.getOperationPassword())) {
         return R.fail("操作密码错误");
      }
      return R.ok();
   }
   private TChargingOrder getSpecialRefundChargingOrder(PayOrderRefundDto payOrderQueryDto) {
      if (payOrderQueryDto == null) {
         return null;
      }
      LambdaQueryWrapper<TChargingOrder> wrapper = new LambdaQueryWrapper<TChargingOrder>()
            .eq(TChargingOrder::getRechargePaymentStatus, 2)
            .eq(TChargingOrder::getDelFlag, false);
      if (payOrderQueryDto.getOrderId() != null) {
         wrapper.eq(TChargingOrder::getId, payOrderQueryDto.getOrderId());
      } else if (StringUtils.hasLength(payOrderQueryDto.getCode())) {
         wrapper.eq(TChargingOrder::getCode, payOrderQueryDto.getCode());
      } else if (StringUtils.hasLength(payOrderQueryDto.getSerialNumber())) {
         wrapper.eq(TChargingOrder::getRechargeSerialNumber, payOrderQueryDto.getSerialNumber());
      } else {
         return null;
      }
      return this.getOne(wrapper.last("limit 1"));
   }
   private TChargingOrderRefund buildSpecialChargingRefund(TChargingOrder order, PayOrderRefundDto dto, BigDecimal refundAmount,
                                             BigDecimal balanceRefundAmount, BigDecimal thirdRefundAmount) {
      TChargingOrderRefund refund = new TChargingOrderRefund();
      refund.setChargingOrderId(order.getId());
      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
      refund.setRefundCode("TSF" + sdf.format(new Date()) + (Double.valueOf(Math.random() * 1000).intValue()));
      refund.setRefundAmount(refundAmount);
      refund.setBalanceRefundAmount(balanceRefundAmount);
      refund.setThirdRefundAmount(thirdRefundAmount);
      refund.setRefundStatus(1);
      refund.setPayType(order.getRechargePaymentType());
      refund.setRefundTime(LocalDateTime.now());
      refund.setCode(order.getCode());
      refund.setRefundTitle(SPECIAL_REFUND_TITLE);
      refund.setRefundContent(SPECIAL_REFUND_TITLE);
      refund.setRefundReason(StringUtils.hasLength(dto.getReason()) ? dto.getReason() : SPECIAL_REFUND_TITLE);
      refund.setRefundRemark(dto.getRemark());
      refund.setRefundTotalAmount(money(order.getRefundAmount()).add(refundAmount));
      refund.setPayAmount(money(order.getPaymentAmount()));
      return refund;
   }
   @Override
   public R payRefund(PayOrderRefundDto payOrderQueryDto) {
      R passwordValid = validateOperationPassword(payOrderQueryDto);
      if (passwordValid.getCode() != 200) {
         return passwordValid;
      }
      if (payOrderQueryDto.getType() == 1) {
         TChargingOrder tChargingOrder = this.baseMapper.selectById(payOrderQueryDto.getOrderId());
         if (tChargingOrder.getPaymentAmount().compareTo(payOrderQueryDto.getRefundAmount()) == -1) {
@@ -5170,6 +6221,8 @@
      preChargeCheck.setInsulationTesting(false);
      preChargeCheck.setSecureConnectionDetection(false);
      preChargeCheck.setStartupSuccess(1);
      preChargeCheck.setStartCommandSent(false);
        preChargeCheck.setAddTime(chargingOrder.getReservationTime() != null ? chargingOrder.getReservationTime() : LocalDateTime.now());
      String key = "AQJC_" + chargingOrder.getChargingGunId();
      redisService.setCacheObject(key, preChargeCheck, 24L, TimeUnit.HOURS);
      
@@ -5809,14 +6862,18 @@
                }
                //计算优惠券
                BigDecimal couponDiscount = BigDecimal.ZERO;
                boolean couponAppliedUpfront = money(chargingOrder.getCouponDiscountAmount()).compareTo(BigDecimal.ZERO) > 0;
                BigDecimal couponDiscount = couponAppliedUpfront
                        ? money(chargingOrder.getCouponDiscountAmount()) : BigDecimal.ZERO;
                if (null != chargingOrder.getAppCouponId()) {
                    //判断实际充电金额是否满足优惠券使用条件,如果不满足则不适用优惠券。
                    TAppCoupon appCoupon = appCouponClient.getAppCouponById(chargingOrder.getAppCouponId()).getData();
                    String couponJson = appCoupon.getCouponJson();
                    TCoupon tCoupon = JSON.parseObject(couponJson, TCoupon.class);
                    Integer preferentialMode = tCoupon.getPreferentialMode();
                    if (1 == preferentialMode) {
                    if (couponAppliedUpfront) {
                        appCoupon.setStatus(2);
                        appCouponClient.updateAppCoupon(appCoupon);
                    } else if (1 == preferentialMode) {
                        //满减
                        if (payAmount.compareTo(tCoupon.getMeetTheConditions()) >= 0) {
                            BigDecimal couponDiscountAmount = tCoupon.getDiscountAmount();
@@ -5835,7 +6892,7 @@
                            chargingOrder.setCouponDiscountAmount(BigDecimal.ZERO);
                        }
                    }
                    if (2 == preferentialMode) {
                    if (!couponAppliedUpfront && 2 == preferentialMode) {
                        //抵扣
                        if (payAmount.compareTo(tCoupon.getMeetTheConditions()) >= 0) {
                            //折扣金额
@@ -5860,13 +6917,12 @@
                }
                //优惠券优惠金额
                couponDiscount = couponDiscount.setScale(4, RoundingMode.HALF_EVEN);
                //退款金额+优惠券
                refundAmount = refundAmount.add(couponDiscount);
                //实际支付金额-优惠券
                payAmount = payAmount.subtract(couponDiscount);
                if (serviceCharge_total.compareTo(BigDecimal.ZERO) > 0) {
                    serviceCharge_total = serviceCharge_total.subtract(couponDiscount);
                if (!couponAppliedUpfront) {
                    refundAmount = refundAmount.add(couponDiscount);
                }
                //实际支付金额-优惠券
                payAmount = payAmount.subtract(couponDiscount).max(BigDecimal.ZERO);
                serviceCharge_total = serviceCharge_total.subtract(couponDiscount).max(BigDecimal.ZERO);
                chargingOrder.setCouponDiscountAmount(couponDiscount.setScale(2, RoundingMode.HALF_EVEN));
                chargingOrder.setServiceCharge(serviceCharge_total.setScale(2, RoundingMode.HALF_EVEN));
                chargingOrder.setPaymentAmount(payAmount.setScale(2, RoundingMode.HALF_EVEN));
@@ -6131,10 +7187,84 @@
      if (resp == null || !StringUtils.hasLength(resp.getStatus())) {
         return false;
      }
      return "SUCCESS".equals(resp.getStatus());
      if (!"SUCCESS".equals(resp.getStatus()) || !StringUtils.hasLength(resp.getPayData())) {
         return false;
      }
      try {
         JSONObject payData = JSON.parseObject(resp.getPayData());
         return StringUtils.hasLength(payData.getString("wxDataPackage")) || StringUtils.hasLength(payData.getString("package"));
      } catch (Exception e) {
         return false;
      }
   }
   private String buildIcbcPaymentFailReason(IcbcPaymentResp resp) {
      if (resp == null) {
         return "工行支付服务未返回支付数据";
      }
      String reason = firstNotBlank(resp.getRawResponse(), resp.getStatus());
      if (!StringUtils.hasLength(reason)) {
         return "工行支付服务返回数据不完整";
      }
      if (reason.length() > 500) {
         return reason.substring(0, 500);
      }
      return reason;
   }
   private void triggerChargingOrderShare(TChargingOrder chargingOrder, Site site) {
      if (!isNeedChargingOrderShare(chargingOrder)) {
         return;
      }
      if (TransactionSynchronizationManager.isActualTransactionActive()
            && TransactionSynchronizationManager.isSynchronizationActive()) {
         Long chargingOrderId = chargingOrder.getId();
         Integer siteId = chargingOrder.getSiteId();
         String resourceKey = SHARE_AFTER_COMMIT_RESOURCE_PREFIX + chargingOrderId;
         if (TransactionSynchronizationManager.hasResource(resourceKey)) {
            return;
         }
         TransactionSynchronizationManager.bindResource(resourceKey, Boolean.TRUE);
         try {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
               @Override
               public void afterCommit() {
                  executeChargingOrderShareAfterCommit(chargingOrderId, siteId);
               }
               @Override
               public void afterCompletion(int status) {
                  if (TransactionSynchronizationManager.hasResource(resourceKey)) {
                     TransactionSynchronizationManager.unbindResource(resourceKey);
                  }
               }
            });
         } catch (RuntimeException e) {
            TransactionSynchronizationManager.unbindResourceIfPossible(resourceKey);
            throw e;
         }
         log.info("充电订单分账已登记为事务提交后执行,订单号:{}", chargingOrder.getCode());
         return;
      }
      executeChargingOrderShare(chargingOrder, site);
   }
   private void executeChargingOrderShareAfterCommit(Long chargingOrderId, Integer siteId) {
      try {
         TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
         transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
         transactionTemplate.execute(status -> {
            TChargingOrder latestOrder = chargingOrderId == null ? null : this.getById(chargingOrderId);
            Site latestSite = loadChargingOrderSite(siteId);
            executeChargingOrderShare(latestOrder, latestSite);
            return null;
         });
      } catch (Exception e) {
         log.error("事务提交后执行充电订单分账失败,订单ID:{}", chargingOrderId, e);
      }
   }
   private void executeChargingOrderShare(TChargingOrder chargingOrder, Site site) {
      try {
         if (!isNeedChargingOrderShare(chargingOrder)) {
            return;
@@ -6166,13 +7296,25 @@
         if (chargingOrder == null || chargingOrder.getSiteId() == null) {
            return;
         }
         R<List<Site>> siteResult = siteClient.getSiteByIds(Arrays.asList(chargingOrder.getSiteId()));
         List<Site> sites = siteResult == null ? null : siteResult.getData();
         Site site = sites == null || sites.isEmpty() ? null : sites.get(0);
         if (TransactionSynchronizationManager.isActualTransactionActive()
               && TransactionSynchronizationManager.isSynchronizationActive()) {
            triggerChargingOrderShare(chargingOrder, null);
            return;
         }
         Site site = loadChargingOrderSite(chargingOrder.getSiteId());
         triggerChargingOrderShare(chargingOrder, site);
      } catch (Exception e) {
         log.error("充电订单分账站点加载失败,订单号:{}", chargingOrder == null ? null : chargingOrder.getCode(), e);
      }
   }
   private Site loadChargingOrderSite(Integer siteId) {
      if (siteId == null) {
         return null;
      }
      R<List<Site>> siteResult = siteClient.getSiteByIds(Collections.singletonList(siteId));
      List<Site> sites = siteResult == null ? null : siteResult.getData();
      return sites == null || sites.isEmpty() ? null : sites.get(0);
   }
   private boolean isNeedChargingOrderShare(TChargingOrder chargingOrder) {
@@ -6251,18 +7393,21 @@
      record.setElectricityShareAmount(electricityShareAmount);
      record.setServiceShareAmount(serviceShareAmount);
      record.setShareAmount(electricityShareAmount.add(serviceShareAmount).setScale(2, RoundingMode.HALF_UP));
      record.setNeedOnlineShare("ICBC".equals(payChannel) && record.getShareAmount().compareTo(BigDecimal.ZERO) > 0);
      record.setOnlineShareStatus(Boolean.TRUE.equals(record.getNeedOnlineShare()) ? "INIT" : "RECORDED");
      if ("ICBC".equals(payChannel)) {
         record.setSeqNo(chargingOrder.getCode());
         record.setBusiType("1");
         record.setOriTrxDate(chargingOrder.getPayTime() == null ? LocalDate.now() : chargingOrder.getPayTime().toLocalDate());
         record.setSubOrderId("ICBCS" + chargingOrder.getCode());
         record.setSubOrderId(chargingOrder.getCode());
      }
      if ("ICBC".equals(payChannel) && partner != null) {
         record.setSubMerId(partner.getIcbcSubMerId());
         record.setSubMerPrtclNo(partner.getIcbcSubMerPrtclNo());
         record.setMerAcct(partner.getIcbcMerAcct());
      }
      boolean needOnlineShare = shouldProcessIcbcOnlineShare(payChannel, record.getShareAmount(), partner);
      record.setNeedOnlineShare(needOnlineShare);
      record.setOnlineShareStatus(needOnlineShare ? "INIT" : "RECORDED");
      if ("ICBC".equals(payChannel) && record.getShareAmount().compareTo(BigDecimal.ZERO) > 0 && !needOnlineShare) {
         record.setFailReason("未配置完整工行二级商户参数,仅记录本地分账");
      }
      record.setRetryCount(0);
      record.setDelFlag(false);
@@ -6308,7 +7453,30 @@
      Map<String, Object> row = rows.get(0);
      BigDecimal principalAmount = getSummaryDecimal(row, "balanceIncome").max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
      BigDecimal giftAmount = getSummaryDecimal(row, "balanceGiftIncome").max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
      return new BalanceShareAmount(principalAmount, giftAmount);
      BigDecimal[] normalized = normalizeBalanceShareAmounts(fallbackBalancePay, principalAmount, giftAmount);
      return new BalanceShareAmount(normalized[0], normalized[1]);
   }
   /**
    * 余额退款流水可能在跨服务事务提交前暂时不可见,按订单净余额约束本金和赠送金额总和。
    */
   static BigDecimal[] normalizeBalanceShareAmounts(BigDecimal netBalanceAmount,
                                                    BigDecimal principalAmount,
                                                    BigDecimal giftAmount) {
      BigDecimal netAmount = netBalanceAmount == null ? BigDecimal.ZERO : netBalanceAmount.max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
      BigDecimal principal = principalAmount == null ? BigDecimal.ZERO : principalAmount.max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
      BigDecimal gift = giftAmount == null ? BigDecimal.ZERO : giftAmount.max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
      BigDecimal recordedTotal = principal.add(gift).setScale(2, RoundingMode.HALF_UP);
      if (netAmount.compareTo(BigDecimal.ZERO) <= 0) {
         return new BigDecimal[]{BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP), BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)};
      }
      if (recordedTotal.compareTo(BigDecimal.ZERO) <= 0) {
         return new BigDecimal[]{netAmount, BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)};
      }
      BigDecimal normalizedPrincipal = netAmount.multiply(principal)
            .divide(recordedTotal, 2, RoundingMode.HALF_UP);
      BigDecimal normalizedGift = netAmount.subtract(normalizedPrincipal).setScale(2, RoundingMode.HALF_UP);
      return new BigDecimal[]{normalizedPrincipal, normalizedGift};
   }
   private BigDecimal getChargingOrderShareElectricityAmount(TChargingOrder chargingOrder) {
@@ -6431,6 +7599,15 @@
      return amount.multiply(ratio).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
   }
   static boolean shouldProcessIcbcOnlineShare(String payChannel, BigDecimal shareAmount, Partner partner) {
      return "ICBC".equals(payChannel)
            && shareAmount != null
            && shareAmount.compareTo(BigDecimal.ZERO) > 0
            && partner != null
            && StringUtils.hasLength(partner.getIcbcSubMerId())
            && StringUtils.hasLength(partner.getIcbcSubMerPrtclNo());
   }
   private IcbcSubOrderSplitReq buildIcbcSubOrderSplitReq(TChargingOrderShareRecord record) {
      IcbcSubOrderSplitReq req = new IcbcSubOrderSplitReq();
      req.setChargingOrderCode(record.getChargingOrderCode());
@@ -6442,7 +7619,6 @@
      req.setSubMerId(record.getSubMerId());
      req.setSubMerPrtclNo(record.getSubMerPrtclNo());
      req.setClassifyAmt(record.getShareAmount());
      req.setMerAcct(record.getMerAcct());
      req.setMerSpInfor("充电订单分账:" + record.getChargingOrderCode());
      return req;
   }
@@ -6477,7 +7653,6 @@
      data.put("icbcPartnerId", partner.getIcbcPartnerId());
      data.put("icbcSubMerId", partner.getIcbcSubMerId());
      data.put("icbcSubMerPrtclNo", partner.getIcbcSubMerPrtclNo());
      data.put("icbcMerAcct", partner.getIcbcMerAcct());
      if (partner.getElectricityShareRatio() != null) {
         data.put("electricityShareRatio", partner.getElectricityShareRatio());
      }