| | |
| | | import com.tourguide.modules.operation.mapper.ScenicPricingMapper; |
| | | import com.tourguide.modules.operation.service.PlatformSettingsService; |
| | | import com.tourguide.modules.order.domain.OrderStatus; |
| | | import com.tourguide.modules.order.domain.PaymentMethod; |
| | | import com.tourguide.modules.order.domain.RefundStatus; |
| | | import com.tourguide.modules.order.dto.MiniOrderCreateRequest; |
| | | import com.tourguide.modules.order.dto.TourOrderResponse; |
| | | import com.tourguide.modules.order.entity.TourOrder; |
| | | import com.tourguide.modules.order.mapper.TourOrderMapper; |
| | | import com.tourguide.modules.system.service.GuideNotificationService; |
| | | import com.tourguide.modules.user.entity.MiniUser; |
| | | import com.tourguide.modules.user.entity.GuideUser; |
| | | import com.tourguide.modules.user.mapper.GuideUserMapper; |
| | | import com.tourguide.modules.user.service.MiniUserService; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | |
| | | /** 小程序讲解订单创建及状态流转服务。 */ |
| | | @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; |
| | |
| | | private final ScenicPricingMapper pricingMapper; |
| | | private final GuideUserMapper guideUserMapper; |
| | | private final PlatformSettingsService platformSettingsService; |
| | | private final GuideNotificationService notificationService; |
| | | private final OrderRefundService orderRefundService; |
| | | private final GuideOrderMatchingService matchingService; |
| | | private final GuideSessionTransitionService sessionTransitionService; |
| | | private final boolean skipPayment; |
| | | |
| | | public MiniTourOrderCommandService(TourOrderMapper orderMapper, |
| | | MiniUserService miniUserService, |
| | |
| | | ScenicMeetingPointMapper meetingPointMapper, |
| | | ScenicPricingMapper pricingMapper, |
| | | GuideUserMapper guideUserMapper, |
| | | PlatformSettingsService platformSettingsService) { |
| | | PlatformSettingsService platformSettingsService, |
| | | GuideNotificationService notificationService, |
| | | OrderRefundService orderRefundService, |
| | | GuideOrderMatchingService matchingService, |
| | | GuideSessionTransitionService sessionTransitionService, |
| | | @Value("${tourguide.order.skip-payment:false}") boolean skipPayment) { |
| | | this.orderMapper = orderMapper; |
| | | this.miniUserService = miniUserService; |
| | | this.scenicSpotMapper = scenicSpotMapper; |
| | |
| | | this.pricingMapper = pricingMapper; |
| | | this.guideUserMapper = guideUserMapper; |
| | | this.platformSettingsService = platformSettingsService; |
| | | this.notificationService = notificationService; |
| | | this.orderRefundService = orderRefundService; |
| | | this.matchingService = matchingService; |
| | | this.sessionTransitionService = sessionTransitionService; |
| | | this.skipPayment = skipPayment; |
| | | } |
| | | |
| | | @Transactional |
| | |
| | | order.setVisitorId(visitor.getId()); |
| | | order.setVisitorNickname(visitor.getNickname()); |
| | | order.setVisitorMobile(visitor.getMobile()); |
| | | order.setScenicSpotId(scenicSpot.getId()); |
| | | order.setScenicName(scenicSpot.getScenicName()); |
| | | order.setMeetingPointId(meetingPoint.getId()); |
| | | order.setMeetingPointName(meetingPoint.getPointName()); |
| | | order.setPackageName(packageName(pricing.getServiceType())); |
| | | order.setServicePackageId(pricing.getId()); |
| | | order.setOrderType(pricing.getServiceType()); |
| | | order.setPeopleCount(request.getPeopleCount()); |
| | | order.setUnitPrice(discountUnitPrice); |
| | |
| | | order.setAmount(subtotalAmount); |
| | | order.setPlacedAt(now); |
| | | order.setMeetingPointCount(1); |
| | | order.setStatus(OrderStatus.PENDING_PAYMENT.getCode()); |
| | | Integer settlementDays = platformSettingsService.getSettings().getSettlementDays(); |
| | | order.setSettlementDays(settlementDays == null ? 1 : Math.max(0, settlementDays)); |
| | | if (skipPayment) { |
| | | order.setPaymentMethod(PaymentMethod.TEMPORARY_BYPASS.getCode()); |
| | | order.setPaymentOrderNo("TEMP" + order.getOrderNo()); |
| | | order.setPaymentAmount(subtotalAmount); |
| | | order.setPaymentTime(now); |
| | | order.setStatus(OrderStatus.PENDING_ACCEPT.getCode()); |
| | | } else { |
| | | order.setStatus(OrderStatus.PENDING_PAYMENT.getCode()); |
| | | } |
| | | order.setRefundStatus(RefundStatus.NONE.getCode()); |
| | | orderMapper.insert(order); |
| | | return TourOrderResponse.from(order); |
| | | } |
| | | |
| | | /** |
| | | * 确认游客订单支付成功,并将订单流转到等待导游接单状态。 |
| | | * 当前小程序尚未接入微信支付回调,因此由待支付页面在支付动作成功后调用; |
| | | * 正式接入微信支付后应由服务端支付回调触发本方法对应的领域逻辑。 |
| | | */ |
| | | @Transactional |
| | | public TourOrderResponse confirmPayment(Long visitorId, Long orderId) { |
| | | TourOrder order = requireVisitorOrder(visitorId, orderId); |
| | | if (!OrderStatus.PENDING_PAYMENT.matches(order.getStatus())) { |
| | | throw new BusinessException(409, "当前订单状态不支持支付"); |
| | | if (skipPayment) { |
| | | matchingService.match(order.getId()); |
| | | } |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | order.setPaymentMethod("WECHAT"); |
| | | order.setPaymentOrderNo("LOCAL" + order.getOrderNo()); |
| | | order.setPaymentAmount(order.getAmount()); |
| | | order.setPaymentTime(now); |
| | | order.setStatus(OrderStatus.PENDING_ACCEPT.getCode()); |
| | | orderMapper.updateById(order); |
| | | return TourOrderResponse.from(order); |
| | | } |
| | | |
| | |
| | | .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(); |
| | | return TourOrderResponse.from(order) |
| | | .enrich(scenicSpot, meetingPoint, pricing, guide) |
| | | .withUnpaidCancelMinutes(unpaidCancelMinutes) |
| | | .withUnacceptedAutoRefundMinutes( |
| | | platformSettingsService.getSettings().getUnacceptedAutoRefundMinutes()); |
| | | } |
| | | |
| | | @Transactional |
| | | public TourOrderResponse cancel(Long visitorId, Long orderId) { |
| | | TourOrder order = requireVisitorOrder(visitorId, orderId); |
| | | TourOrder order = orderMapper.selectForUpdate(orderId); |
| | | if (order == null || visitorId == null || !visitorId.equals(order.getVisitorId())) { |
| | | throw new BusinessException(404, "订单不存在"); |
| | | } |
| | | if (OrderStatus.CANCELLED.matches(order.getStatus())) { |
| | | return TourOrderResponse.from(order); |
| | | } |
| | | boolean notifyGuide = OrderStatus.PENDING_MEETING.matches(order.getStatus()) && order.getGuideId() != null; |
| | | if (!OrderStatus.PENDING_PAYMENT.matches(order.getStatus()) |
| | | && !OrderStatus.PENDING_ACCEPT.matches(order.getStatus()) |
| | | && !OrderStatus.PENDING_MEETING.matches(order.getStatus())) { |
| | | throw new BusinessException(409, "当前订单状态不支持取消"); |
| | | } |
| | | order.setStatus(OrderStatus.CANCELLED.getCode()); |
| | | if (order.getPaymentTime() != null) { |
| | | order.setRefundStatus(RefundStatus.REFUNDED.getCode()); |
| | | order.setRefundAmount(order.getPaymentAmount()); |
| | | order.setRefundTime(LocalDateTime.now()); |
| | | order.setRefundOrderNo("RF" + generateOrderNo()); |
| | | order.setRefundMethod(order.getPaymentMethod()); |
| | | order.setCancelledAt(LocalDateTime.now()); |
| | | if (order.getPaymentTime() == null) { |
| | | orderMapper.updateById(order); |
| | | } else { |
| | | orderRefundService.requestRefund(order); |
| | | } |
| | | orderMapper.updateById(order); |
| | | if (notifyGuide) sessionTransitionService.restoreAfterOrderCancellation(order); |
| | | if (notifyGuide) notificationService.notifyVisitorCancelledOrder(order); |
| | | return TourOrderResponse.from(order); |
| | | } |
| | | |