package com.tourguide.modules.order.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.tourguide.modules.operation.service.PlatformSettingsService; import com.tourguide.modules.order.domain.OrderStatus; import com.tourguide.modules.order.domain.RefundStatus; import com.tourguide.modules.order.entity.TourOrder; import com.tourguide.modules.order.mapper.TourOrderMapper; import com.tourguide.modules.order.payment.PaymentRefundGateway; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; /** 处理已支付但长时间无人接单的订单自动退款。 */ @Service public class OrderAutoRefundService { private static final Logger log = LoggerFactory.getLogger(OrderAutoRefundService.class); private static final int BATCH_SIZE = 100; private final TourOrderMapper orderMapper; private final PlatformSettingsService platformSettingsService; private final PaymentRefundGateway paymentRefundGateway; public OrderAutoRefundService(TourOrderMapper orderMapper, PlatformSettingsService platformSettingsService, PaymentRefundGateway paymentRefundGateway) { this.orderMapper = orderMapper; this.platformSettingsService = platformSettingsService; this.paymentRefundGateway = paymentRefundGateway; } @Scheduled(fixedDelayString = "${tourguide.order.auto-refund-scan-delay-millis:10000}") @Transactional public void refundExpiredUnacceptedOrders() { Integer configuredMinutes = platformSettingsService.getSettings().getUnacceptedAutoRefundMinutes(); int timeoutMinutes = configuredMinutes == null || configuredMinutes < 1 ? 5 : configuredMinutes; LocalDateTime expiredBefore = LocalDateTime.now().minusMinutes(timeoutMinutes); List orders = orderMapper.selectList( new LambdaQueryWrapper() .eq(TourOrder::getStatus, OrderStatus.PENDING_ACCEPT.getCode()) .isNotNull(TourOrder::getPaymentTime) .le(TourOrder::getPaymentTime, expiredBefore) .and(wrapper -> wrapper.isNull(TourOrder::getRefundStatus) .or().eq(TourOrder::getRefundStatus, RefundStatus.NONE.getCode())) .orderByAsc(TourOrder::getPaymentTime) .last("LIMIT " + BATCH_SIZE)); for (TourOrder order : orders) { refundIfStillUnaccepted(order.getId()); } } void refundIfStillUnaccepted(Long orderId) { if (orderMapper.claimUnacceptedOrderForRefund(orderId) != 1) { return; } TourOrder order = orderMapper.selectById(orderId); BigDecimal refundAmount = order.getPaymentAmount() == null ? order.getAmount() : order.getPaymentAmount(); try { String refundOrderNo = paymentRefundGateway.refund(order, refundAmount); order.setRefundStatus(RefundStatus.REFUNDED.getCode()); order.setRefundAmount(refundAmount); order.setRefundTime(LocalDateTime.now()); order.setRefundOrderNo(refundOrderNo); order.setRefundMethod(order.getPaymentMethod()); } catch (RuntimeException exception) { order.setRefundStatus(RefundStatus.REFUND_FAILED.getCode()); log.error("订单自动退款失败,orderId={}, orderNo={}", order.getId(), order.getOrderNo(), exception); } orderMapper.updateById(order); } }