liujie
2026-09-11 4f6f15b818704079ff2e4e4719b798f48d68a335
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package com.tourguide.modules.finance.service;
 
import com.tourguide.common.exception.BusinessException;
import com.tourguide.modules.finance.dto.WithdrawalResponse;
import com.tourguide.modules.finance.entity.GuideWithdrawal;
import com.tourguide.modules.finance.mapper.GuideWithdrawalMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
 
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ThreadLocalRandom;
 
/** 管理提现资金流程的本地事务状态,不在数据库事务中调用外部资金接口。 */
@Service
public class GuideWithdrawalStateService {
    static final String PENDING = "PENDING";
    static final String TRANSFERRING = "TRANSFERRING";
    static final String TRANSFER_FAILED = "TRANSFER_FAILED";
    static final String TRANSFERRED = "TRANSFERRED";
    static final String PROCESSING = "PROCESSING";
    static final String APPROVED = "APPROVED";
    static final String REJECTED = "REJECTED";
 
    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");
    private static final DateTimeFormatter SEQ_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
 
    private final GuideWithdrawalMapper withdrawalMapper;
    private final GuideWithdrawableBalanceService balanceService;
 
    public GuideWithdrawalStateService(GuideWithdrawalMapper withdrawalMapper,
                                       GuideWithdrawableBalanceService balanceService) {
        this.withdrawalMapper = withdrawalMapper;
        this.balanceService = balanceService;
    }
 
    @Transactional
    public WithdrawalResponse reject(Long id, String remark, String processor) {
        GuideWithdrawal withdrawal = requireForUpdate(id);
        if (!canProcess(withdrawal)) throw new BusinessException(409, "提现申请已处理");
        if (!StringUtils.hasText(remark)) throw new BusinessException(400, "驳回提现时请填写处理备注");
        if (withdrawal.getTeamId() == null) {
            balanceService.credit(withdrawal.getGuideId(), withdrawal.getAmount());
        } else {
            balanceService.creditTeam(withdrawal.getTeamId(), withdrawal.getAmount());
        }
        withdrawal.setStatus(REJECTED);
        withdrawal.setAuditRemark(remark.trim());
        withdrawal.setProcessor(processor);
        withdrawal.setProcessedAt(LocalDateTime.now());
        update(withdrawal);
        return new WithdrawalResponse(withdrawal);
    }
 
    @Transactional
    public GuideWithdrawal prepareTransfer(Long id, String remark, String processor) {
        GuideWithdrawal withdrawal = requireForUpdate(id);
        if (!canProcess(withdrawal)) throw new BusinessException(409, "提现申请已处理");
        LocalDateTime now = LocalDateTime.now();
        withdrawal.setStatus(TRANSFERRING);
        withdrawal.setTransferReqSeqId(sequence("TF"));
        withdrawal.setTransferReqDate(now.format(DATE_FORMAT));
        withdrawal.setTransferHfSeqId(null);
        withdrawal.setPayoutMessage("等待主账户划账");
        withdrawal.setAuditRemark(StringUtils.hasText(remark) ? remark.trim() : null);
        withdrawal.setProcessor(processor);
        withdrawal.setProcessedAt(now);
        update(withdrawal);
        return withdrawal;
    }
 
    @Transactional
    public GuideWithdrawal markTransferred(Long id, String hfSeqId) {
        GuideWithdrawal withdrawal = requireForUpdate(id);
        if (!TRANSFERRING.equals(withdrawal.getStatus())) return withdrawal;
        withdrawal.setStatus(TRANSFERRED);
        withdrawal.setTransferHfSeqId(hfSeqId);
        withdrawal.setTransferredAt(LocalDateTime.now());
        withdrawal.setPayoutMessage("主账户划账成功,等待发起取现");
        update(withdrawal);
        return withdrawal;
    }
 
    @Transactional
    public GuideWithdrawal markTransferPending(Long id, String hfSeqId, String message) {
        GuideWithdrawal withdrawal = requireForUpdate(id);
        if (!TRANSFERRING.equals(withdrawal.getStatus())) return withdrawal;
        if (StringUtils.hasText(hfSeqId)) withdrawal.setTransferHfSeqId(hfSeqId);
        withdrawal.setPayoutMessage(limit(message));
        update(withdrawal);
        return withdrawal;
    }
 
    @Transactional
    public GuideWithdrawal markTransferFailed(Long id, String message) {
        GuideWithdrawal withdrawal = requireForUpdate(id);
        if (!TRANSFERRING.equals(withdrawal.getStatus())) return withdrawal;
        withdrawal.setStatus(TRANSFER_FAILED);
        withdrawal.setPayoutMessage(limit(message));
        update(withdrawal);
        return withdrawal;
    }
 
    @Transactional
    public GuideWithdrawal prepareCash(Long id) {
        GuideWithdrawal withdrawal = requireForUpdate(id);
        if (!TRANSFERRED.equals(withdrawal.getStatus())) return null;
        LocalDateTime now = LocalDateTime.now();
        withdrawal.setStatus(PROCESSING);
        withdrawal.setCashReqSeqId(sequence("CW"));
        withdrawal.setCashReqDate(now.format(DATE_FORMAT));
        withdrawal.setCashHfSeqId(null);
        withdrawal.setPayoutMessage("已发起导游账户取现");
        update(withdrawal);
        return withdrawal;
    }
 
    @Transactional
    public GuideWithdrawal resumeCashQuery(Long id) {
        GuideWithdrawal withdrawal = requireForUpdate(id);
        if (!TRANSFERRED.equals(withdrawal.getStatus())
                || !StringUtils.hasText(withdrawal.getCashReqSeqId())
                || !StringUtils.hasText(withdrawal.getCashReqDate())) {
            return null;
        }
        withdrawal.setStatus(PROCESSING);
        withdrawal.setPayoutMessage("检测到原取现流水,等待查询最终状态");
        update(withdrawal);
        return withdrawal;
    }
 
    @Transactional
    public GuideWithdrawal markCashPending(Long id, String hfSeqId, String message) {
        GuideWithdrawal withdrawal = requireForUpdate(id);
        if (!PROCESSING.equals(withdrawal.getStatus())) return withdrawal;
        if (StringUtils.hasText(hfSeqId)) withdrawal.setCashHfSeqId(hfSeqId);
        withdrawal.setPayoutMessage(limit(message));
        update(withdrawal);
        return withdrawal;
    }
 
    @Transactional
    public GuideWithdrawal markCashRetry(Long id, String message) {
        GuideWithdrawal withdrawal = requireForUpdate(id);
        if (!PROCESSING.equals(withdrawal.getStatus())) return withdrawal;
        LocalDateTime now = LocalDateTime.now();
        String retryMessage = limit(message);
        if (withdrawalMapper.resetCashForRetry(id, retryMessage, now, now) != 1) {
            throw new BusinessException(500, "提现状态更新失败");
        }
        withdrawal.setStatus(TRANSFERRED);
        withdrawal.setCashReqSeqId(null);
        withdrawal.setCashReqDate(null);
        withdrawal.setCashHfSeqId(null);
        withdrawal.setPayoutMessage(retryMessage);
        withdrawal.setProcessedAt(now);
        withdrawal.setUpdatedAt(now);
        return withdrawal;
    }
 
    @Transactional
    public GuideWithdrawal markApproved(Long id, String hfSeqId) {
        GuideWithdrawal withdrawal = requireForUpdate(id);
        if (!PROCESSING.equals(withdrawal.getStatus())) return withdrawal;
        withdrawal.setStatus(APPROVED);
        if (StringUtils.hasText(hfSeqId)) withdrawal.setCashHfSeqId(hfSeqId);
        withdrawal.setPayoutMessage("取现到账成功");
        withdrawal.setProcessedAt(LocalDateTime.now());
        update(withdrawal);
        return withdrawal;
    }
 
    public GuideWithdrawal find(Long id) {
        GuideWithdrawal withdrawal = withdrawalMapper.selectById(id);
        if (withdrawal == null) throw new BusinessException(404, "提现申请不存在");
        return withdrawal;
    }
 
    private GuideWithdrawal requireForUpdate(Long id) {
        GuideWithdrawal withdrawal = withdrawalMapper.selectForUpdate(id);
        if (withdrawal == null) throw new BusinessException(404, "提现申请不存在");
        return withdrawal;
    }
 
    private boolean canProcess(GuideWithdrawal withdrawal) {
        return PENDING.equals(withdrawal.getStatus()) || TRANSFER_FAILED.equals(withdrawal.getStatus());
    }
 
    private void update(GuideWithdrawal withdrawal) {
        withdrawal.setUpdatedAt(LocalDateTime.now());
        if (withdrawalMapper.updateById(withdrawal) != 1) {
            throw new BusinessException(500, "提现状态更新失败");
        }
    }
 
    private String sequence(String prefix) {
        return prefix + LocalDateTime.now().format(SEQ_TIME_FORMAT)
                + String.format("%04d", ThreadLocalRandom.current().nextInt(10000));
    }
 
    private String limit(String message) {
        if (!StringUtils.hasText(message)) return "斗拱资金处理结果待确认";
        String value = message.trim();
        return value.length() <= 255 ? value : value.substring(0, 255);
    }
}