liujie
2026-09-14 a910819c1853081735356c7a2c5e05a2953602ab
backend/src/main/java/com/tourguide/modules/order/service/MiniTourOrderCommandService.java
@@ -33,6 +33,7 @@
/** 小程序讲解订单创建及状态流转服务。 */
@Service
public class MiniTourOrderCommandService {
    private static final int DEFAULT_UNPAID_CANCEL_MINUTES = 15;
    private final TourOrderMapper orderMapper;
    private final MiniUserService miniUserService;
    private final OperationScenicSpotMapper scenicSpotMapper;
@@ -43,6 +44,7 @@
    private final GuideNotificationService notificationService;
    private final OrderRefundService orderRefundService;
    private final GuideOrderMatchingService matchingService;
    private final GuideSessionTransitionService sessionTransitionService;
    private final boolean skipPayment;
    public MiniTourOrderCommandService(TourOrderMapper orderMapper,
@@ -55,6 +57,7 @@
                                       GuideNotificationService notificationService,
                                       OrderRefundService orderRefundService,
                                       GuideOrderMatchingService matchingService,
                                       GuideSessionTransitionService sessionTransitionService,
                                       @Value("${tourguide.order.skip-payment:false}") boolean skipPayment) {
        this.orderMapper = orderMapper;
        this.miniUserService = miniUserService;
@@ -66,6 +69,7 @@
        this.notificationService = notificationService;
        this.orderRefundService = orderRefundService;
        this.matchingService = matchingService;
        this.sessionTransitionService = sessionTransitionService;
        this.skipPayment = skipPayment;
    }
@@ -159,10 +163,26 @@
                        .orderByDesc(ScenicPricing::getId)
                        .last("LIMIT 1"));
        GuideUser guide = order.getGuideId() == null ? null : guideUserMapper.selectById(order.getGuideId());
        int unpaidCancelMinutes = scenicSpot == null || scenicSpot.getUnpaidCancelMinutes() == null
                || scenicSpot.getUnpaidCancelMinutes() < 1
                ? DEFAULT_UNPAID_CANCEL_MINUTES : scenicSpot.getUnpaidCancelMinutes();
        cancelExpiredUnpaidIfNeeded(order, unpaidCancelMinutes);
        return TourOrderResponse.from(order)
                .enrich(scenicSpot, meetingPoint, pricing, guide)
                .withUnpaidCancelMinutes(unpaidCancelMinutes)
                .withUnacceptedAutoRefundMinutes(
                        platformSettingsService.getSettings().getUnacceptedAutoRefundMinutes());
    }
    /** 读取订单时惰性取消已超时的未支付订单,避免等待定时任务扫描延迟。 */
    private void cancelExpiredUnpaidIfNeeded(TourOrder order, int unpaidCancelMinutes) {
        if (!OrderStatus.PENDING_PAYMENT.matches(order.getStatus())) return;
        LocalDateTime baseTime = order.getPlacedAt() == null ? order.getCreatedAt() : order.getPlacedAt();
        if (baseTime == null || baseTime.plusMinutes(unpaidCancelMinutes).isAfter(LocalDateTime.now())) return;
        if (orderMapper.cancelUnpaid(order.getId()) > 0) {
            order.setStatus(OrderStatus.CANCELLED.getCode());
            order.setCancelledAt(LocalDateTime.now());
        }
    }
    @Transactional
@@ -181,11 +201,13 @@
            throw new BusinessException(409, "当前订单状态不支持取消");
        }
        order.setStatus(OrderStatus.CANCELLED.getCode());
        order.setCancelledAt(LocalDateTime.now());
        if (order.getPaymentTime() == null) {
            orderMapper.updateById(order);
        } else {
            orderRefundService.requestRefund(order);
        }
        if (notifyGuide) sessionTransitionService.restoreAfterOrderCancellation(order);
        if (notifyGuide) notificationService.notifyVisitorCancelledOrder(order);
        return TourOrderResponse.from(order);
    }