package com.kym.service.impl; import com.kym.entity.*; import com.kym.entity.awoara.OrderInfo; import com.kym.service.*; import com.kym.service.cache.KymCache; import com.kym.service.factory.DiscountStrategyFactory; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.Duration; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; /** * 订单结算服务实现 * * @author skyline */ @Slf4j @Service public class OrderSettlementServiceImpl implements OrderSettlementService { @Value("${kym.domain}") private String DOMAIN; private final WashOrderService washOrderService; private final WalletDetailService walletDetailService; private final AccountService accountService; private final SplitRecordService splitRecordService; private final PayLogService payLogService; private final UserService userService; private final MpMsgTemplateService mpMsgTemplateService; public OrderSettlementServiceImpl(WashOrderService washOrderService, WalletDetailService walletDetailService, AccountService accountService, SplitRecordService splitRecordService, PayLogService payLogService, UserService userService, MpMsgTemplateService mpMsgTemplateService) { this.washOrderService = washOrderService; this.walletDetailService = walletDetailService; this.accountService = accountService; this.splitRecordService = splitRecordService; this.payLogService = payLogService; this.userService = userService; this.mpMsgTemplateService = mpMsgTemplateService; } @Override @Transactional public void settleOrder(WashOrder washOrder, OrderInfo orderInfo) { log.info("执行订单结算,订单:{},结算信息:{}", orderInfo.getOrder_id(), orderInfo); // 幂等保护:重新加载订单并检查是否已结算 var freshOrder = washOrderService.lambdaQuery().eq(WashOrder::getId, washOrder.getId()).one(); if (freshOrder != null && Integer.valueOf(WashOrder.PAY_STATUS_已支付).equals(freshOrder.getPayStatus())) { log.warn("订单:{},已结算,跳过重复结算", orderInfo.getOrder_id()); return; } int rechargePayment; int grantsPayment = 0; var account = accountService.lambdaQuery().eq(Account::getUserId, washOrder.getUserId()).last("FOR UPDATE").one(); int amountReceived = orderInfo.getAmount_received(); if (account.getRechargeBalance() >= amountReceived) { rechargePayment = amountReceived; accountService.lambdaUpdate() .setSql("balance = balance - {0}, recharge_balance = recharge_balance - {1}", amountReceived, rechargePayment) .eq(Account::getUserId, washOrder.getUserId()) .update(); } else { rechargePayment = account.getRechargeBalance(); grantsPayment = amountReceived - account.getRechargeBalance(); accountService.lambdaUpdate() .setSql("balance = balance - {0}, recharge_balance = 0, grants_balance = grants_balance - {1}", amountReceived, grantsPayment) .eq(Account::getUserId, washOrder.getUserId()) .update(); } washOrder .setCloseType(orderInfo.getClose_type()) .setAmount(orderInfo.getAmount()) .setAmountReceivable(orderInfo.getAmount_receivable()) .setAmountReceived(amountReceived) .setDiscountMoney(orderInfo.getDiscount_money()) .setDetail(orderInfo.getDetail()) .setEndTime(LocalDateTime.now()) .setTotalSeconds(Duration.between(washOrder.getStartTime(), LocalDateTime.now()).toSeconds()) .setRechargePayment(rechargePayment) .setGrantsPayment(grantsPayment) .setPayStatus(WashOrder.PAY_STATUS_已支付) .setOrderStatus(WashOrder.ORDER_STATUS_成功); washOrderService.updateById(washOrder); ensureUserStationBelonging(washOrder.getUserId(), washOrder.getStationId()); backfillUnattributedRecharges(washOrder.getUserId()); if (orderInfo.getAmount() == 0) { return; } if (washOrder.getIsCross()) { doCrossSplit(washOrder, KymCache.INSTANCE.getUserStationId(washOrder.getUserId())); } else { doLocalSplit(washOrder); } var walletDetail = new WalletDetail(); walletDetail.setUserId(washOrder.getUserId()); walletDetail.setType(WalletDetail.TYPE_消费); walletDetail.setOrderNo(washOrder.getOrderId()); walletDetail.setAmount(amountReceived); walletDetail.setGrantsAmount(grantsPayment); walletDetail.setBeforeBalance(account.getBalance()); walletDetail.setAfterBalance(account.getBalance() - amountReceived); walletDetail.setBeforeGrantsBalance(account.getGrantsBalance()); walletDetail.setAfterGrantsBalance(account.getGrantsBalance() - grantsPayment); walletDetail.setTransactionId(washOrder.getId().toString()); walletDetail.setTransactionTime(LocalDateTime.now()); walletDetail.setStatus(WalletDetail.STATUS_已确认); walletDetailService.save(walletDetail); DiscountStrategyFactory.getDiscountStrategy(washOrder.getDiscountType()).computeDiscount(washOrder, account); washOrderService.updateById(washOrder); if (orderInfo.getAmount() >= KymCache.INSTANCE.getParkingCouponMinAmountByStationId(washOrder.getStationId())) { var parkingCouponUrl = KymCache.INSTANCE.getParkingQrCodeUrlByStationId(washOrder.getStationId()); var code = UUID.randomUUID().toString(); KymCache.INSTANCE.setParkingCouponCode(code, parkingCouponUrl, 3600 * 2L); var url = DOMAIN + "/api/parking-coupon?code=" + code; mpMsgTemplateService.sendParkingCouponMsg(washOrder, url); } else { mpMsgTemplateService.sendOrderCompletedMsg(washOrder, account.getBalance()); } } @Transactional protected void doLocalSplit(WashOrder washOrder) { log.info("订单:{},本店消费,V2方案无需分账", washOrder.getOrderId()); } @Transactional protected void doCrossSplit(WashOrder washOrder, String userStationId) { int rechargePayment = washOrder.getRechargePayment(); int grantsPayment = washOrder.getGrantsPayment(); int totalPayment = rechargePayment + grantsPayment; if (totalPayment == 0) { log.info("订单:{},支付金额为0,不产生跨店转账", washOrder.getOrderId()); return; } BigDecimal crossRate = new BigDecimal("0.7"); int crossAmount = crossRate.multiply(BigDecimal.valueOf(totalPayment)).setScale(0, RoundingMode.DOWN).intValue(); log.info("订单:{},执行跨店分账,归属站:{},消费站:{},充值款实收:{},赠款实收:{},转账金额:{}", washOrder.getOrderId(), userStationId, washOrder.getStationId(), rechargePayment, grantsPayment, crossAmount); var crossExpend = new SplitRecord() .setFromStationId(userStationId) .setToStationId(washOrder.getStationId()) .setTradeNo(washOrder.getOrderId()) .setAmount(crossAmount) .setType(SplitRecord.TYPE_CROSS_EXPEND); var crossIncome = new SplitRecord() .setFromStationId(userStationId) .setToStationId(washOrder.getStationId()) .setTradeNo(washOrder.getOrderId()) .setAmount(crossAmount) .setType(SplitRecord.TYPE_CROSS_INCOME); splitRecordService.saveBatch(List.of(crossExpend, crossIncome)); log.info("订单:{},跨店分账完成", washOrder.getOrderId()); } /** * 兜底保障:如果 createOrder 的自动归属没有生效,在结算时补齐。 * IoT 回调路径没有用户 Session,同步写 Redis 缓存供后续使用。 */ private void ensureUserStationBelonging(Long userId, String orderStationId) { var user = userService.getById(userId); if (user == null || user.getStationId() != null) { return; } userService.lambdaUpdate() .set(User::getStationId, orderStationId) .eq(User::getId, userId) .update(); KymCache.INSTANCE.putUserId2StationId(Map.of(userId, orderStationId)); log.info("兜底归属:用户={} 归属站点={}", userId, orderStationId); } private void backfillUnattributedRecharges(Long userId) { var userStationId = KymCache.INSTANCE.getUserStationId(userId); if (userStationId == null) { var user = userService.getById(userId); userStationId = user != null ? user.getStationId() : null; } if (userStationId == null) { return; } var walletDetails = walletDetailService.lambdaQuery() .eq(WalletDetail::getUserId, userId) .eq(WalletDetail::getType, WalletDetail.TYPE_充值) .eq(WalletDetail::getStatus, WalletDetail.STATUS_已确认) .list(); if (walletDetails.isEmpty()) { return; } var outTradeNos = walletDetails.stream().map(WalletDetail::getOrderNo).toList(); var payLogs = payLogService.lambdaQuery().in(PayLog::getOutTradeNo, outTradeNos).list(); if (payLogs.isEmpty()) { return; } var allTxIds = payLogs.stream().map(PayLog::getTransactionId).collect(Collectors.toSet()); var existingTxIds = splitRecordService.lambdaQuery() .in(SplitRecord::getTradeNo, allTxIds) .eq(SplitRecord::getType, SplitRecord.TYPE_RECHARGE) .list() .stream() .map(SplitRecord::getTradeNo) .collect(Collectors.toSet()); for (var payLog : payLogs) { if (!existingTxIds.contains(payLog.getTransactionId())) { var splitRecord = new SplitRecord() .setFromStationId(userStationId) .setToStationId(userStationId) .setTradeNo(payLog.getTransactionId()) .setAmount(payLog.getTotal()) .setType(SplitRecord.TYPE_RECHARGE); splitRecordService.save(splitRecord); log.info("追溯补建充值分账:用户={}, 站点={}, 交易号={}, 金额={}", userId, userStationId, payLog.getTransactionId(), payLog.getTotal()); } } } }