2026-06-24 f4bd1f3c89d906131495a0aca5aaf82966378510
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
package cn.iocoder.yudao.module.pay.service.demo;
 
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pay.api.transfer.PayTransferApi;
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateRespDTO;
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawCreateReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO;
import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoWithdrawMapper;
import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawStatusEnum;
import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawTypeEnum;
import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
 
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
 
/**
 * 示例转账业务 Service 实现类
 *
 * @author jason
 */
@Service
@Validated
@Slf4j
public class PayDemoWithdrawServiceImpl implements PayDemoWithdrawService {
 
    /**
     * 接入的支付应用标识
     *
     * 从 [支付管理 -> 应用信息] 里添加
     */
    private static final String PAY_APP_KEY = "demo";
 
    @Resource
    private PayDemoWithdrawMapper demoTransferMapper;
 
    @Resource
    private PayTransferApi payTransferApi;
 
    @Override
    public Long createDemoWithdraw(@Valid PayDemoWithdrawCreateReqVO reqVO) {
        PayDemoWithdrawDO withdraw = BeanUtils.toBean(reqVO, PayDemoWithdrawDO.class)
                .setTransferChannelCode(getTransferChannelCode(reqVO.getType()))
                .setStatus(PayDemoWithdrawStatusEnum.WAITING.getStatus());
        demoTransferMapper.insert(withdraw);
        return withdraw.getId();
    }
 
    @Override
    public Long transferDemoWithdraw(Long id, Long userId) {
        // 1.1 校验提现单
        PayDemoWithdrawDO withdraw = validateDemoWithdrawCanTransfer(id);
        // 1.2 特殊:如果是转账失败的情况,需要充值下
        if (PayDemoWithdrawStatusEnum.isClosed(withdraw.getStatus())) {
            int updateCount = demoTransferMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(),
                    new PayDemoWithdrawDO().setStatus(PayDemoWithdrawStatusEnum.WAITING.getStatus()).setTransferErrorMsg(""));
            if (updateCount == 0) {
                throw exception(DEMO_WITHDRAW_TRANSFER_FAIL_STATUS_NOT_WAITING_OR_CLOSED);
            }
            withdraw.setStatus(PayDemoWithdrawStatusEnum.WAITING.getStatus());
        }
 
        // 2.1 创建支付单
        PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO()
                .setAppKey(PAY_APP_KEY).setChannelCode(withdraw.getTransferChannelCode()).setUserIp(getClientIP()) // 支付应用
                .setUserId(userId).setUserType(UserTypeEnum.ADMIN.getValue()) // 用户信息
                .setMerchantTransferId(String.valueOf(withdraw.getId())) // 业务的订单编号
                .setSubject(withdraw.getSubject()).setPrice(withdraw.getPrice()) // 价格信息
                .setUserAccount(withdraw.getUserAccount()).setUserName(withdraw.getUserName()); // 收款信息
        if (ObjectUtil.equal(withdraw.getType(), PayDemoWithdrawTypeEnum.WECHAT.getType())) {
            // 注意:微信很特殊!提现需要写明用途!!!
            transferReqDTO.setChannelExtras(PayTransferCreateReqDTO.buildWeiXinChannelExtra1000(
                    "测试活动", "测试奖励"));
        }
        PayTransferCreateRespDTO transferRespDTO = payTransferApi.createTransfer(transferReqDTO);
 
        // 2.2 更新转账单到 demo 示例提现单,并将状态更新为转账中
        demoTransferMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(),
                new PayDemoWithdrawDO().setPayTransferId(transferRespDTO.getId()));
        return transferRespDTO.getId();
    }
 
    private PayDemoWithdrawDO validateDemoWithdrawCanTransfer(Long id) {
        // 校验存在
        PayDemoWithdrawDO withdraw = demoTransferMapper.selectById(id);
        if (withdraw == null) {
            throw exception(DEMO_WITHDRAW_NOT_FOUND);
        }
        // 校验状态,只有等待中或转账失败的订单,才能发起转账
        if (!PayDemoWithdrawStatusEnum.isWaiting(withdraw.getStatus())
            && !PayDemoWithdrawStatusEnum.isClosed(withdraw.getStatus())) {
            throw exception(DEMO_WITHDRAW_TRANSFER_FAIL_STATUS_NOT_WAITING_OR_CLOSED);
        }
        return withdraw;
    }
 
    private String getTransferChannelCode(Integer type) {
        if (ObjectUtil.equal(type, PayDemoWithdrawTypeEnum.ALIPAY.getType())) {
            return PayChannelEnum.ALIPAY_PC.getCode();
        }
        if (ObjectUtil.equal(type, PayDemoWithdrawTypeEnum.WECHAT.getType())) {
            return PayChannelEnum.WX_LITE.getCode();
        }
        if (ObjectUtil.equal(type, PayDemoWithdrawTypeEnum.WALLET.getType())) {
            return PayChannelEnum.WALLET.getCode();
        }
        throw new IllegalArgumentException("未知提现方式:" + type);
    }
 
    @Override
    public PageResult<PayDemoWithdrawDO> getDemoWithdrawPage(PageParam pageVO) {
        return demoTransferMapper.selectPage(pageVO);
    }
 
    @Override
    public void updateDemoWithdrawTransferred(Long id, Long payTransferId) {
        // 1.1 校验转账单是否存在
        PayDemoWithdrawDO withdraw = demoTransferMapper.selectById(id);
        if (withdraw == null) {
            log.error("[updateDemoWithdrawStatus][withdraw({}) payOrder({}) 不存在提现单,请进行处理!]", id, payTransferId);
            throw exception(DEMO_WITHDRAW_NOT_FOUND);
        }
        // 1.2 校验转账单已成结束(成功或失败)
        if (PayDemoWithdrawStatusEnum.isSuccess(withdraw.getStatus())
            || PayDemoWithdrawStatusEnum.isClosed(withdraw.getStatus())) {
            // 特殊:转账单编号相同,直接返回,说明重复回调
            if (ObjectUtil.equal(withdraw.getPayTransferId(), payTransferId)) {
                log.warn("[updateDemoWithdrawStatus][withdraw({}) 已结束,且转账单编号相同({}),直接返回]", withdraw, payTransferId);
                return;
            }
            // 异常:转账单编号不同,说明转账单编号错误
            log.error("[updateDemoWithdrawStatus][withdraw({}) 转账单不匹配({}),请进行处理!]", withdraw, payTransferId);
            throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR);
        }
 
        // 2. 校验提现单的合法性
        PayTransferRespDTO payTransfer = validateDemoTransferStatusCanUpdate(withdraw, payTransferId);
 
        // 3. 更新示例订单状态
        Integer newStatus = PayTransferStatusEnum.isSuccess(payTransfer.getStatus()) ? PayDemoWithdrawStatusEnum.SUCCESS.getStatus() :
                PayTransferStatusEnum.isClosed(payTransfer.getStatus()) ? PayDemoWithdrawStatusEnum.CLOSED.getStatus() : null;
        Assert.notNull(newStatus, "转账单状态({}) 不合法", payTransfer.getStatus());
        demoTransferMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(),
                new PayDemoWithdrawDO().setStatus(newStatus).setTransferTime(payTransfer.getSuccessTime())
                        .setTransferErrorMsg(payTransfer.getChannelErrorMsg()));
    }
 
    private PayTransferRespDTO validateDemoTransferStatusCanUpdate(PayDemoWithdrawDO withdraw, Long payTransferId) {
        // 1. 校验转账单是否存在
        PayTransferRespDTO payTransfer = payTransferApi.getTransfer(payTransferId);
        if (payTransfer == null) {
            log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 不存在,请进行处理!]", withdraw.getId(), payTransferId);
            throw exception(PAY_TRANSFER_NOT_FOUND);
        }
 
        // 2.1 校验转账单已成功
        if (!PayTransferStatusEnum.isSuccessOrClosed(payTransfer.getStatus())) {
            log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 未结束,请进行处理!payTransfer 数据是:{}]",
                    withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
            throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED);
        }
        // 2.2 校验转账金额一致
        if (ObjectUtil.notEqual(payTransfer.getPrice(), withdraw.getPrice())) {
            log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账金额不匹配,请进行处理!withdraw 数据是:{},payTransfer 数据是:{}]",
                    withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer));
            throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH);
        }
        // 2.3 校验转账订单匹配(二次)
        if (ObjectUtil.notEqual(payTransfer.getMerchantTransferId(), withdraw.getId().toString())) {
            log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) 转账单不匹配({}),请进行处理!payTransfer 数据是:{}]",
                    withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
            throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS);
        }
        // 2.4 校验转账渠道一致
        if (ObjectUtil.notEqual(payTransfer.getChannelCode(), withdraw.getTransferChannelCode())) {
            log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账渠道不匹配,请进行处理!withdraw 数据是:{},payTransfer 数据是:{}]",
                    withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer));
            throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_CHANNEL_NOT_MATCH);
        }
        return payTransfer;
    }
 
}