package com.tourguide.modules.order.service; import com.tourguide.common.huifu.RequestSeqUtil; import com.tourguide.modules.order.domain.OrderStatus; import com.tourguide.modules.order.domain.PaymentMethod; import com.tourguide.modules.order.domain.RefundAmountCalculator; 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.stereotype.Service; import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.time.LocalDateTime; @Service public class OrderRefundService { private static final Logger log = LoggerFactory.getLogger(OrderRefundService.class); private final TourOrderMapper orderMapper; private final PaymentRefundGateway paymentRefundGateway; public OrderRefundService(TourOrderMapper orderMapper, PaymentRefundGateway paymentRefundGateway) { this.orderMapper = orderMapper; this.paymentRefundGateway = paymentRefundGateway; } public boolean requestRefund(TourOrder order) { if (order == null) return false; BigDecimal refundAmount = RefundAmountCalculator.refundableAmount(order); return requestRefund(order, refundAmount); } public boolean requestRefund(TourOrder order, BigDecimal refundAmount) { if (order == null || order.getPaymentTime() == null) { return false; } if (RefundStatus.REFUNDED.matches(order.getRefundStatus()) || OrderStatus.REFUNDED.matches(order.getStatus())) { return true; } if (RefundStatus.REFUNDING.matches(order.getRefundStatus()) && StringUtils.hasText(order.getRefundOrderNo())) { return true; } if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) { return false; } BigDecimal refundableAmount = RefundAmountCalculator.refundableAmount(order); if (refundAmount.compareTo(refundableAmount) > 0) { return false; } order.setRefundOrderNo(RequestSeqUtil.generateForBusiness("RF")); order.setRefundStatus(RefundStatus.REFUNDING.getCode()); order.setRefundAmount(refundAmount); order.setRefundMethod(order.getPaymentMethod()); if (PaymentMethod.TEMPORARY_BYPASS.matches(order.getPaymentMethod())) { if (order.getCancelledAt() == null) order.setCancelledAt(LocalDateTime.now()); completeLocalRefund(order, refundAmount); order.setRefundTime(LocalDateTime.now()); orderMapper.updateById(order); return true; } orderMapper.updateById(order); try { String refundRequestNo = paymentRefundGateway.refund(order, refundAmount); if (!StringUtils.hasText(refundRequestNo)) { throw new IllegalStateException("Payment provider did not accept refund request"); } if (!OrderStatus.CANCELLED.matches(order.getStatus())) { order.setStatus(OrderStatus.CANCELLED.getCode()); } if (order.getCancelledAt() == null) order.setCancelledAt(LocalDateTime.now()); orderMapper.updateById(order); return true; } catch (RuntimeException exception) { order.setRefundStatus(RefundStatus.REFUND_FAILED.getCode()); orderMapper.updateById(order); log.error("Order refund request failed, orderId={}, orderNo={}, refundOrderNo={}", order.getId(), order.getOrderNo(), order.getRefundOrderNo(), exception); return false; } } private void completeLocalRefund(TourOrder order, BigDecimal refundAmount) { BigDecimal refundedAmount = RefundAmountCalculator.refundedAmount(order).add(refundAmount); order.setRefundedAmount(refundedAmount); if (refundedAmount.compareTo(RefundAmountCalculator.paidAmount(order)) >= 0) { order.setRefundStatus(RefundStatus.REFUNDED.getCode()); order.setStatus(OrderStatus.REFUNDED.getCode()); } else { order.setRefundStatus(RefundStatus.PARTIAL.getCode()); order.setStatus(OrderStatus.CANCELLED.getCode()); } } }