liujie
2026-09-14 82a8bc62d5b9d5d607f6952329952b17eda2f0a0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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.entity.TourOrderRefund;
import com.tourguide.modules.order.mapper.TourOrderMapper;
import com.tourguide.modules.order.mapper.TourOrderRefundMapper;
import com.tourguide.modules.order.payment.PaymentRefundGateway;
import com.tourguide.modules.finance.service.GuideBalanceSettlementService;
import org.springframework.beans.factory.annotation.Autowired;
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;
    private final TourOrderRefundMapper refundMapper;
    private final GuideBalanceSettlementService balanceSettlementService;
 
    public OrderRefundService(TourOrderMapper orderMapper, PaymentRefundGateway paymentRefundGateway) {
        this(orderMapper, paymentRefundGateway, null, null);
    }
 
    public OrderRefundService(TourOrderMapper orderMapper, PaymentRefundGateway paymentRefundGateway,
                              TourOrderRefundMapper refundMapper) {
        this(orderMapper, paymentRefundGateway, refundMapper, null);
    }
 
    @Autowired
    public OrderRefundService(TourOrderMapper orderMapper, PaymentRefundGateway paymentRefundGateway,
                              TourOrderRefundMapper refundMapper,
                              GuideBalanceSettlementService balanceSettlementService) {
        this.orderMapper = orderMapper;
        this.paymentRefundGateway = paymentRefundGateway;
        this.refundMapper = refundMapper;
        this.balanceSettlementService = balanceSettlementService;
    }
 
    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());
        TourOrderRefund refundRecord = createRefundRecord(order, refundAmount);
        if (PaymentMethod.TEMPORARY_BYPASS.matches(order.getPaymentMethod())) {
            if (order.getCancelledAt() == null) order.setCancelledAt(LocalDateTime.now());
            completeLocalRefund(order, refundAmount);
            LocalDateTime refundTime = LocalDateTime.now();
            order.setRefundTime(refundTime);
            // 明细状态描述当前这一笔退款;订单状态则描述累计退款进度。
            updateRefundRecord(refundRecord, RefundStatus.REFUNDED.getCode(), refundTime);
            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());
            updateRefundRecord(refundRecord, RefundStatus.REFUND_FAILED.getCode(), null);
            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 previousRefundedAmount = RefundAmountCalculator.refundedAmount(order);
        BigDecimal refundedAmount = previousRefundedAmount.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());
        }
        reverseSettledEarning(order, previousRefundedAmount, refundedAmount);
    }
 
    private void reverseSettledEarning(TourOrder order, BigDecimal previousRefundedAmount,
                                       BigDecimal currentRefundedAmount) {
        if (balanceSettlementService != null) {
            balanceSettlementService.reverseRefund(order, previousRefundedAmount, currentRefundedAmount);
        }
    }
 
    private TourOrderRefund createRefundRecord(TourOrder order, BigDecimal refundAmount) {
        if (refundMapper == null || order.getId() == null) return null;
        TourOrderRefund refund = new TourOrderRefund();
        refund.setOrderId(order.getId());
        refund.setRefundOrderNo(order.getRefundOrderNo());
        refund.setRefundAmount(refundAmount);
        refund.setRefundStatus(RefundStatus.REFUNDING.getCode());
        refund.setRefundMethod(order.getRefundMethod());
        refundMapper.insert(refund);
        return refund;
    }
 
    private void updateRefundRecord(TourOrderRefund refund, String status, LocalDateTime refundTime) {
        if (refundMapper == null || refund == null) return;
        refund.setRefundStatus(status);
        refund.setRefundTime(refundTime);
        refundMapper.updateById(refund);
    }
}