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 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 (!PENDING.equals(withdrawal.getStatus())) throw new BusinessException(409, "提现申请已处理"); if (!StringUtils.hasText(remark)) throw new BusinessException(400, "驳回提现时请填写处理备注"); balanceService.credit(withdrawal.getGuideId(), 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 (!PENDING.equals(withdrawal.getStatus())) 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(PENDING); 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 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); } }