修复余额不足仍可启动充电问题

统一启动金额优先级并在启动前校验会员余额。

同步完成订单创建和余额支付后再返回鉴权成功,并增加支付凭证补偿、异常停机和结算保护。
This commit is contained in:
Guoqs
2026-07-25 09:54:37 +08:00
parent bfa209becb
commit 1b37a7d5e8
18 changed files with 851 additions and 126 deletions

View File

@@ -6,29 +6,28 @@ import com.jsowell.common.constant.Constants;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.enums.ykc.OrderPayModeEnum;
import com.jsowell.common.enums.ykc.ReturnCodeEnum;
import com.jsowell.common.exception.BusinessException;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.StringUtils;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.common.util.id.IdUtils;
import com.jsowell.common.util.spring.SpringUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.pile.domain.PileBasicInfo;
import com.jsowell.pile.dto.ConfirmStartChargingData;
import com.jsowell.pile.dto.GenerateOrderDTO;
import com.jsowell.pile.dto.SavePileMsgDTO;
import com.jsowell.pile.service.*;
import com.jsowell.pile.util.ChargeAmountUtils;
import com.jsowell.pile.vo.base.ConfirmStartChargingMemberVO;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* 充电桩主动申请启动充电 0x31
@@ -42,18 +41,9 @@ import java.util.concurrent.CompletableFuture;
public class ConfirmStartChargingRequestHandler extends AbstractYkcHandler {
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.REQUEST_START_CHARGING_CODE.getBytes());
// 引入线程池
private ThreadPoolTaskExecutor executor = SpringUtils.getBean("threadPoolTaskExecutor");
@Autowired
private PileAuthCardService pileAuthCardService;
@Autowired
private OrderBasicInfoService orderBasicInfoService;
@Autowired
private MemberPlateNumberRelationService memberPlateNumberRelationService;
@Autowired
private PileMsgRecordService pileMsgRecordService;
@@ -198,8 +188,14 @@ public class ConfirmStartChargingRequestHandler extends AbstractYkcHandler {
.vinCode(vinCode)
.build();
// 启动充电
Map<String, String> resultMap = this.confirmStartCharging(confirmStartChargingData);
Map<String, String> resultMap;
try {
// 启动充电
resultMap = this.confirmStartCharging(confirmStartChargingData);
} catch (Exception e) {
log.error("充电桩主动申请充电处理失败, param:{}", JSON.toJSONString(confirmStartChargingData), e);
resultMap = buildFailureResult("05");
}
byte[] defeatReasonByteArr = Constants.zeroByteArray;
/**
@@ -279,6 +275,10 @@ public class ConfirmStartChargingRequestHandler extends AbstractYkcHandler {
// 查询充电桩信息
PileBasicInfo pileBasicInfo = pileBasicInfoService.selectPileBasicInfoBySN(pileSn);
if (pileBasicInfo == null) {
log.info("充电桩主动申请充电未查询到桩信息, pileSn:{}", pileSn);
return buildFailureResult("05");
}
// 查询是否有权限
ConfirmStartChargingMemberVO memberWalletVO = null;
@@ -295,50 +295,38 @@ public class ConfirmStartChargingRequestHandler extends AbstractYkcHandler {
log.info("根据vin码:{},查询会员信息:{}", vinCode, JSON.toJSONString(memberWalletVO));
}
String accountBalance = Constants.ZERO;
// 如果查询后memberWalletVO为null, 则标示无权限
if (memberWalletVO == null) {
resultMap.put("transactionCode", Constants.ILLEGAL_TRANSACTION_CODE);
resultMap.put("accountBalance", accountBalance);
resultMap.put("defeatReasonCode", "01");
log.info("无此会员权限");
return resultMap;
}
// 如果不是平台测试员, 并且也不是站点白名单, 并且总余额小于等于0, 则提示余额不足
if (!StringUtils.equals(Constants.ONE, memberWalletVO.getPlatformTesterFlag())
&& !StringUtils.equals(Constants.ONE, memberWalletVO.getStationWhiteListFlag())
&& memberWalletVO.getTotalBalance().compareTo(BigDecimal.ZERO) <= 0) {
resultMap.put("transactionCode", Constants.ILLEGAL_TRANSACTION_CODE);
resultMap.put("accountBalance", accountBalance);
resultMap.put("defeatReasonCode", "03");
log.info("余额不足");
return resultMap;
return buildFailureResult("01");
}
/*
鉴权通过, 生成订单并支付
*/
BigDecimal chargeAmount;
String payMode;
if (StringUtils.equals(Constants.ONE, memberWalletVO.getPlatformTesterFlag())
|| StringUtils.equals(Constants.ONE, memberWalletVO.getStationWhiteListFlag())) {
// 如果是平台测试员或者是站点白名单
accountBalance = Constants.WHITELIST_DEFAULT_AMOUNT.toString();
chargeAmount = Constants.WHITELIST_DEFAULT_AMOUNT;
payMode = OrderPayModeEnum.PAYMENT_OF_WHITELIST.getValue();
} else {
BigDecimal totalBalance = memberWalletVO.getTotalBalance();
accountBalance = totalBalance.toString();
if (totalBalance.compareTo(Constants.BALANCE_PAY_MAX_AMOUNT_NEW) > 0) {
accountBalance = Constants.BALANCE_PAY_MAX_AMOUNT_NEW.toString();
chargeAmount = ChargeAmountUtils.resolveStartupAmount(
dto.getChargeAmount(), memberWalletVO.getDefaultChargeAmount());
BigDecimal totalBalance = memberWalletVO.getTotalBalance() == null
? BigDecimal.ZERO
: memberWalletVO.getTotalBalance();
if (!ChargeAmountUtils.isPositive(chargeAmount) || totalBalance.compareTo(chargeAmount) < 0) {
log.info("余额不足, memberId:{}, chargeAmount:{}, totalBalance:{}",
memberWalletVO.getMemberId(), chargeAmount, totalBalance);
return buildFailureResult("03");
}
payMode = OrderPayModeEnum.PAYMENT_OF_PRINCIPAL_BALANCE.getValue();
}
// 生成交易流水号, 传递给订单使用
String transactionCode = IdUtils.generateTransactionCode(pileSn, connectorCode);
resultMap.put("transactionCode", transactionCode);
resultMap.put("accountBalance", accountBalance);
dto.setPileSn(pileSn);
if (StringUtils.isNotBlank(memberWalletVO.getPlateNumber())) {
@@ -346,34 +334,40 @@ public class ConfirmStartChargingRequestHandler extends AbstractYkcHandler {
}
dto.setConnectorCode(connectorCode);
dto.setTransactionCode(transactionCode);
if (memberWalletVO.getDefaultChargeAmount() != null && memberWalletVO.getDefaultChargeAmount().compareTo(BigDecimal.ZERO) > 0) {
dto.setChargeAmount(memberWalletVO.getDefaultChargeAmount());
}else {
dto.setChargeAmount(new BigDecimal(accountBalance));
}
dto.setChargeAmount(chargeAmount);
dto.setPayMode(payMode);
dto.setStartMode(StringUtils.equals("01", startMode) ? "2" : "5");
dto.setMemberId(memberWalletVO.getMemberId());
dto.setMerchantId(pileBasicInfo.getMerchantId() + "");
// 校验充电桩相关的信息
try {
orderBasicInfoService.checkPileInfoForEV(dto);
// 必须等订单创建并完成余额支付后,才能向桩端返回鉴权成功。
Map<String, Object> map = orderBasicInfoService.generateOrderByCardV2(dto);
if (map == null) {
throw new BusinessException(ReturnCodeEnum.CODE_GENERATE_ORDER_ERROR);
}
} catch (BusinessException e) {
String defeatReasonCode = StringUtils.equals(
ReturnCodeEnum.CODE_BALANCE_IS_INSUFFICIENT.getValue(), e.getCode()) ? "03" : "05";
log.error("充电桩主动申请充电生成订单失败, memberId:{}, chargeAmount:{}",
memberWalletVO.getMemberId(), chargeAmount, e);
return buildFailureResult(defeatReasonCode);
} catch (Exception e) {
// 校验充电桩报错,返回校验值 05-桩停用
resultMap.put("transactionCode", Constants.ILLEGAL_TRANSACTION_CODE);
resultMap.put("accountBalance", accountBalance);
resultMap.put("defeatReasonCode", "05");
log.error("充电桩主动申请充电生成订单失败, memberId:{}, chargeAmount:{}",
memberWalletVO.getMemberId(), chargeAmount, e);
return buildFailureResult("05");
}
// 异步创建订单
CompletableFuture.runAsync(() -> {
try {
Map<String, Object> map = orderBasicInfoService.generateOrderByCardV2(dto);
} catch (Exception e) {
throw new RuntimeException(e);
}
}, executor);
resultMap.put("transactionCode", transactionCode);
resultMap.put("accountBalance", chargeAmount.toString());
return resultMap;
}
private Map<String, String> buildFailureResult(String defeatReasonCode) {
Map<String, String> resultMap = new HashMap<>();
resultMap.put("transactionCode", Constants.ILLEGAL_TRANSACTION_CODE);
resultMap.put("accountBalance", Constants.ZERO);
resultMap.put("defeatReasonCode", defeatReasonCode);
return resultMap;
}

View File

@@ -138,6 +138,9 @@ public class PileApplyMergeChargeHandler extends AbstractYkcHandler{
log.info("桩号:{}, 并充订单鉴权成功, 结果map:{}", pileSn, map);
} catch (Exception e) {
log.error("桩号:{}, 并充订单鉴权失败, ", pileSn, e);
map.put("transactionCode", Constants.ILLEGAL_TRANSACTION_CODE);
map.put("accountBalance", Constants.ZERO);
map.put("defeatReasonCode", "05");
}
String transactionCode = map.get("transactionCode");
// 如果交易流水号不等于Constants.ILLEGAL_TRANSACTION_CODE, 表示鉴权通过

View File

@@ -19,6 +19,7 @@ import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.pile.domain.OrderBasicInfo;
import com.jsowell.pile.service.OrderBasicInfoService;
import com.jsowell.pile.service.PileBasicInfoService;
import com.jsowell.pile.vo.web.OrderPaymentCheckResult;
import com.jsowell.thirdparty.common.CommonService;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
@@ -297,6 +298,22 @@ public class UploadRealTimeMonitorHandler extends AbstractYkcHandler {
// 查询数据库中该订单当前信息
OrderBasicInfo orderInfo = orderBasicInfoService.getOrderInfoByTransactionCode(transactionCode);
if (Objects.nonNull(orderInfo)) {
OrderPaymentCheckResult paymentCheck = orderBasicInfoService.checkAndRepairOrderPayment(orderInfo);
boolean paymentReady = paymentCheck.isOrderPaymentReady();
if (!paymentCheck.isCheckCompleted()) {
// 查询依赖暂时异常时只保留实时数据,等待后续补偿重试,不能据此判定订单未支付。
saveRedisFlag = true;
log.error("实时监控支付凭证校验未完成,保留实时数据且不变更订单状态, orderCode:{}, reason:{}",
orderInfo.getOrderCode(), paymentCheck.getReason());
} else if (!paymentReady) {
// 实时监控只能保存原始数据并隔离订单,不能把待支付订单改成已支付。
saveRedisFlag = true;
orderBasicInfoService.handleOrderPaymentAnomaly(orderInfo,
paymentCheck.getReason(), null, true);
log.warn("实时监控发现支付凭证无效, orderCode:{}, reason:{}",
orderInfo.getOrderCode(), paymentCheck.getReason());
}
if (paymentCheck.isCheckCompleted() && paymentReady) {
// 保存此订单的soc
YKCUtils.saveSOC(transactionCode, realTimeMonitorData.getSOC());
@@ -315,11 +332,6 @@ public class UploadRealTimeMonitorHandler extends AbstractYkcHandler {
orderInfo.setOrderStatus(OrderStatusEnum.IN_THE_CHARGING.getValue());
}
if (StringUtils.equals(orderInfo.getPayStatus(), OrderPayStatusEnum.unpaid.getValue())) {
// 如果发现该订单的支付状态为 0-待支付,将该订单支付状态改为 1-支付完成
orderInfo.setPayStatus(OrderPayStatusEnum.paid.getValue());
}
// 如果原来没有开始充电时间就保存当前时间为开始充电时间
if (orderInfo.getChargeStartTime() == null) {
updateFlag = true;
@@ -365,6 +377,7 @@ public class UploadRealTimeMonitorHandler extends AbstractYkcHandler {
log.error("异步绑定车辆停车优惠券error", e);
}
}, executor);
}
}
// 充电时保存实时数据到redis

View File

@@ -30,6 +30,7 @@ import com.jsowell.pile.service.PileBasicInfoService;
import com.jsowell.pile.service.PileMsgRecordService;
import com.jsowell.pile.service.YKCPushCommandService;
import com.jsowell.thirdparty.common.CommonService;
import com.jsowell.pile.vo.web.OrderPaymentCheckResult;
import com.rabbitmq.client.Channel;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
@@ -358,6 +359,22 @@ public class PileRabbitListener {
// 查询数据库中该订单当前信息
OrderBasicInfo orderInfo = orderBasicInfoService.getOrderInfoByTransactionCode(transactionCode);
if (Objects.nonNull(orderInfo)) {
OrderPaymentCheckResult paymentCheck = orderBasicInfoService.checkAndRepairOrderPayment(orderInfo);
boolean paymentReady = paymentCheck.isOrderPaymentReady();
if (!paymentCheck.isCheckCompleted()) {
// 查询依赖暂时异常时只保留实时数据,等待后续补偿重试,不能据此判定订单未支付。
saveRedisFlag = true;
log.error("实时监控支付凭证校验未完成,保留实时数据且不变更订单状态, orderCode:{}, reason:{}",
orderInfo.getOrderCode(), paymentCheck.getReason());
} else if (!paymentReady) {
// 实时监控不能把待支付订单伪造成已支付,只保留实时数据并执行幂等停机。
saveRedisFlag = true;
orderBasicInfoService.handleOrderPaymentAnomaly(orderInfo,
paymentCheck.getReason(), null, true);
log.warn("实时监控发现支付凭证无效, orderCode:{}, reason:{}",
orderInfo.getOrderCode(), paymentCheck.getReason());
}
if (paymentCheck.isCheckCompleted() && paymentReady) {
if (StringUtils.equals(orderInfo.getOrderStatus(), OrderStatusEnum.ORDER_COMPLETE.getValue())
|| StringUtils.equals(orderInfo.getOrderStatus(), OrderStatusEnum.STAY_SETTLEMENT.getValue())) {
// 在订单状态为 订单完成或待结算,不保存
@@ -373,11 +390,6 @@ public class PileRabbitListener {
orderInfo.setOrderStatus(OrderStatusEnum.IN_THE_CHARGING.getValue());
}
if (StringUtils.equals(orderInfo.getPayStatus(), OrderPayStatusEnum.unpaid.getValue())) {
// 如果发现该订单的支付状态为 0-待支付,将该订单支付状态改为 1-支付完成
orderInfo.setPayStatus(OrderPayStatusEnum.paid.getValue());
}
// 如果原来没有开始充电时间就保存当前时间为开始充电时间
if (orderInfo.getChargeStartTime() == null) {
updateFlag = true;
@@ -387,6 +399,7 @@ public class PileRabbitListener {
if (updateFlag) {
orderBasicInfoService.updateOrderBasicInfo(orderInfo);
}
}
}
// 充电时保存实时数据到redis