Commit 15313c2992258c248433b73b6d921189ad775d4f

Authored by jason
1 parent 09d45e63

转账 - 增加转账定时任务

Showing 15 changed files with 313 additions and 82 deletions
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
6 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; 6 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
7 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; 7 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
8 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; 8 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
  9 +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
9 10
10 import java.util.Map; 11 import java.util.Map;
11 12
@@ -86,4 +87,12 @@ public interface PayClient { @@ -86,4 +87,12 @@ public interface PayClient {
86 */ 87 */
87 PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO); 88 PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO);
88 89
  90 + /**
  91 + * 获得转账订单信息
  92 + *
  93 + * @param outTradeNo 外部订单号
  94 + * @param type 转账类型
  95 + * @return 转账信息
  96 + */
  97 + PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type);
89 } 98 }
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java
@@ -53,11 +53,24 @@ public class PayTransferRespDTO { @@ -53,11 +53,24 @@ public class PayTransferRespDTO {
53 /** 53 /**
54 * 创建【WAITING】状态的转账返回 54 * 创建【WAITING】状态的转账返回
55 */ 55 */
56 - public static PayTransferRespDTO waitingOf(String channelOrderNo, 56 + public static PayTransferRespDTO waitingOf(String channelTransferNo,
57 String outTransferNo, Object rawData) { 57 String outTransferNo, Object rawData) {
58 PayTransferRespDTO respDTO = new PayTransferRespDTO(); 58 PayTransferRespDTO respDTO = new PayTransferRespDTO();
59 respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus(); 59 respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus();
60 - respDTO.channelTransferNo = channelOrderNo; 60 + respDTO.channelTransferNo = channelTransferNo;
  61 + respDTO.outTransferNo = outTransferNo;
  62 + respDTO.rawData = rawData;
  63 + return respDTO;
  64 + }
  65 +
  66 + /**
  67 + * 创建【IN_PROGRESS】状态的转账返回
  68 + */
  69 + public static PayTransferRespDTO dealingOf(String channelTransferNo,
  70 + String outTransferNo, Object rawData) {
  71 + PayTransferRespDTO respDTO = new PayTransferRespDTO();
  72 + respDTO.status = PayTransferStatusRespEnum.IN_PROGRESS.getStatus();
  73 + respDTO.channelTransferNo = channelTransferNo;
61 respDTO.outTransferNo = outTransferNo; 74 respDTO.outTransferNo = outTransferNo;
62 respDTO.rawData = rawData; 75 respDTO.rawData = rawData;
63 return respDTO; 76 return respDTO;
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
@@ -188,11 +188,11 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen @@ -188,11 +188,11 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
188 188
189 @Override 189 @Override
190 public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { 190 public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
  191 + validatePayTransferReqDTO(reqDTO);
191 PayTransferRespDTO resp; 192 PayTransferRespDTO resp;
192 - try{  
193 - validatePayTransferReqDTO(reqDTO); 193 + try {
194 resp = doUnifiedTransfer(reqDTO); 194 resp = doUnifiedTransfer(reqDTO);
195 - }catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 195 + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
196 throw ex; 196 throw ex;
197 } catch (Throwable ex) { 197 } catch (Throwable ex) {
198 // 系统异常,则包装成 PayException 异常抛出 198 // 系统异常,则包装成 PayException 异常抛出
@@ -219,9 +219,25 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen @@ -219,9 +219,25 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
219 } 219 }
220 } 220 }
221 221
  222 + @Override
  223 + public final PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type) {
  224 + try {
  225 + return doGetTransfer(outTradeNo, type);
  226 + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
  227 + throw ex;
  228 + } catch (Throwable ex) {
  229 + log.error("[getTransfer][客户端({}) outTradeNo({}) type({}) 查询转账单异常]",
  230 + getId(), outTradeNo, type, ex);
  231 + throw buildPayException(ex);
  232 + }
  233 + }
  234 +
222 protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) 235 protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO)
223 throws Throwable; 236 throws Throwable;
224 237
  238 + protected abstract PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type)
  239 + throws Throwable;
  240 +
225 // ========== 各种工具方法 ========== 241 // ========== 各种工具方法 ==========
226 242
227 private PayException buildPayException(Throwable ex) { 243 private PayException buildPayException(Throwable ex) {
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java
@@ -23,14 +23,8 @@ import com.alipay.api.AlipayResponse; @@ -23,14 +23,8 @@ import com.alipay.api.AlipayResponse;
23 import com.alipay.api.DefaultAlipayClient; 23 import com.alipay.api.DefaultAlipayClient;
24 import com.alipay.api.domain.*; 24 import com.alipay.api.domain.*;
25 import com.alipay.api.internal.util.AlipaySignature; 25 import com.alipay.api.internal.util.AlipaySignature;
26 -import com.alipay.api.request.AlipayFundTransUniTransferRequest;  
27 -import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest;  
28 -import com.alipay.api.request.AlipayTradeQueryRequest;  
29 -import com.alipay.api.request.AlipayTradeRefundRequest;  
30 -import com.alipay.api.response.AlipayFundTransUniTransferResponse;  
31 -import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;  
32 -import com.alipay.api.response.AlipayTradeQueryResponse;  
33 -import com.alipay.api.response.AlipayTradeRefundResponse; 26 +import com.alipay.api.request.*;
  27 +import com.alipay.api.response.*;
34 import lombok.Getter; 28 import lombok.Getter;
35 import lombok.SneakyThrows; 29 import lombok.SneakyThrows;
36 import lombok.extern.slf4j.Slf4j; 30 import lombok.extern.slf4j.Slf4j;
@@ -126,7 +120,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa @@ -126,7 +120,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
126 } 120 }
127 // 2.2 解析订单的状态 121 // 2.2 解析订单的状态
128 Integer status = parseStatus(response.getTradeStatus()); 122 Integer status = parseStatus(response.getTradeStatus());
129 - Assert.notNull(status, () -> { 123 + Assert.notNull(status, () -> {
130 throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", response.getBody())); 124 throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", response.getBody()));
131 }); 125 });
132 return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()), 126 return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()),
@@ -228,7 +222,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa @@ -228,7 +222,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
228 protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException { 222 protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException {
229 // 1.1 校验公钥类型 必须使用公钥证书模式 223 // 1.1 校验公钥类型 必须使用公钥证书模式
230 if (!Objects.equals(config.getMode(), MODE_CERTIFICATE)) { 224 if (!Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
231 - throw exception0(ERROR_CONFIGURATION.getCode(),"支付宝单笔转账必须使用公钥证书模式"); 225 + throw exception0(ERROR_CONFIGURATION.getCode(), "支付宝单笔转账必须使用公钥证书模式");
232 } 226 }
233 // 1.2 构建 AlipayFundTransUniTransferModel 227 // 1.2 构建 AlipayFundTransUniTransferModel
234 AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel(); 228 AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel();
@@ -238,44 +232,96 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa @@ -238,44 +232,96 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
238 model.setOutBizNo(reqDTO.getOutTransferNo()); 232 model.setOutBizNo(reqDTO.getOutTransferNo());
239 model.setProductCode("TRANS_ACCOUNT_NO_PWD"); // 销售产品码。单笔无密转账固定为 TRANS_ACCOUNT_NO_PWD 233 model.setProductCode("TRANS_ACCOUNT_NO_PWD"); // 销售产品码。单笔无密转账固定为 TRANS_ACCOUNT_NO_PWD
240 model.setBizScene("DIRECT_TRANSFER"); // 业务场景 单笔无密转账固定为 DIRECT_TRANSFER 234 model.setBizScene("DIRECT_TRANSFER"); // 业务场景 单笔无密转账固定为 DIRECT_TRANSFER
241 - model.setBusinessParams(JsonUtils.toJsonString(reqDTO.getChannelExtras())); 235 + if (reqDTO.getChannelExtras() != null) {
  236 + model.setBusinessParams(JsonUtils.toJsonString(reqDTO.getChannelExtras()));
  237 + }
  238 + // ② 个性化的参数
  239 + Participant payeeInfo = new Participant();
242 PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(reqDTO.getType()); 240 PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(reqDTO.getType());
243 switch (transferType) { 241 switch (transferType) {
244 // TODO @jason:是不是不用传递 transferType 参数哈?因为应该已经明确是支付宝啦? 242 // TODO @jason:是不是不用传递 transferType 参数哈?因为应该已经明确是支付宝啦?
245 // @芋艿。 是不是还要考虑转账到银行卡。所以传 transferType 但是转账到银行卡不知道要如何测试?? 243 // @芋艿。 是不是还要考虑转账到银行卡。所以传 transferType 但是转账到银行卡不知道要如何测试??
246 case ALIPAY_BALANCE: { 244 case ALIPAY_BALANCE: {
247 - // ② 个性化的参数  
248 - Participant payeeInfo = new Participant();  
249 payeeInfo.setIdentityType("ALIPAY_LOGON_ID"); 245 payeeInfo.setIdentityType("ALIPAY_LOGON_ID");
250 payeeInfo.setIdentity(reqDTO.getAlipayLogonId()); // 支付宝登录号 246 payeeInfo.setIdentity(reqDTO.getAlipayLogonId()); // 支付宝登录号
251 payeeInfo.setName(reqDTO.getUserName()); // 支付宝账号姓名 247 payeeInfo.setName(reqDTO.getUserName()); // 支付宝账号姓名
252 model.setPayeeInfo(payeeInfo); 248 model.setPayeeInfo(payeeInfo);
253 - // 1.3 构建 AlipayFundTransUniTransferRequest  
254 - AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();  
255 - request.setBizModel(model);  
256 - // 执行请求  
257 - AlipayFundTransUniTransferResponse response = client.certificateExecute(request);  
258 - // 处理结果  
259 - if (!response.isSuccess()) {  
260 - // 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询  
261 - if (ObjectUtils.equalsAny(response.getSubCode(), "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) {  
262 - return PayTransferRespDTO.waitingOf(null, reqDTO.getOutTransferNo(), response);  
263 - }  
264 - return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),  
265 - reqDTO.getOutTransferNo(), response);  
266 - }  
267 - return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()),  
268 - response.getOutBizNo(), response); 249 + break;
269 } 250 }
270 case BANK_CARD: { 251 case BANK_CARD: {
271 - Participant payeeInfo = new Participant();  
272 payeeInfo.setIdentityType("BANKCARD_ACCOUNT"); 252 payeeInfo.setIdentityType("BANKCARD_ACCOUNT");
273 // TODO 待实现 253 // TODO 待实现
274 throw exception(NOT_IMPLEMENTED); 254 throw exception(NOT_IMPLEMENTED);
275 } 255 }
276 default: { 256 default: {
277 - throw exception0(BAD_REQUEST.getCode(),"不正确的转账类型: {}",transferType); 257 + throw exception0(BAD_REQUEST.getCode(), "不正确的转账类型: {}", transferType);
  258 + }
  259 + }
  260 + // 1.3 构建 AlipayFundTransUniTransferRequest
  261 + AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();
  262 + request.setBizModel(model);
  263 + // 执行请求
  264 + AlipayFundTransUniTransferResponse response = client.certificateExecute(request);
  265 + // 处理结果
  266 + if (!response.isSuccess()) {
  267 + // 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询,或相同 outBizNo 重新发起转账
  268 + // 发现 outBizNo 相同 两次请求参数相同. 会返回 "PAYMENT_INFO_INCONSISTENCY", 不知道哪里的问题. 暂时返回 WAIT. 后续job 会轮询
  269 + if (ObjectUtils.equalsAny(response.getSubCode(),"PAYMENT_INFO_INCONSISTENCY", "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) {
  270 + return PayTransferRespDTO.waitingOf(null, reqDTO.getOutTransferNo(), response);
  271 + }
  272 + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
  273 + reqDTO.getOutTransferNo(), response);
  274 + } else {
  275 + if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL"
  276 + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
  277 + reqDTO.getOutTransferNo(), response);
  278 + }
  279 + if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中
  280 + return PayTransferRespDTO.dealingOf(response.getOrderId(), reqDTO.getOutTransferNo(), response);
  281 + }
  282 + return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()),
  283 + response.getOutBizNo(), response);
  284 + }
  285 +
  286 + }
  287 +
  288 + @Override
  289 + protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) throws Throwable {
  290 + // 1.1 构建 AlipayFundTransCommonQueryModel
  291 + AlipayFundTransCommonQueryModel model = new AlipayFundTransCommonQueryModel();
  292 + model.setProductCode(type == PayTransferTypeEnum.BANK_CARD ? "TRANS_BANKCARD_NO_PWD" : "TRANS_ACCOUNT_NO_PWD");
  293 + model.setBizScene("DIRECT_TRANSFER"); //业务场景
  294 + model.setOutBizNo(outTradeNo);
  295 + // 1.2 构建 AlipayFundTransCommonQueryRequest
  296 + AlipayFundTransCommonQueryRequest request = new AlipayFundTransCommonQueryRequest();
  297 + request.setBizModel(model);
  298 +
  299 + // 2.1 执行请求
  300 + AlipayFundTransCommonQueryResponse response;
  301 + if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式
  302 + response = client.certificateExecute(request);
  303 + } else {
  304 + response = client.execute(request);
  305 + }
  306 + // 2.2 处理返回结果
  307 + if (response.isSuccess()) {
  308 + if (ObjectUtils.equalsAny(response.getStatus(), "REFUND", "FAIL")) { // 转账到银行卡会出现 "REFUND" "FAIL"
  309 + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
  310 + outTradeNo, response);
  311 + }
  312 + if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中
  313 + return PayTransferRespDTO.dealingOf(response.getOrderId(), outTradeNo, response);
278 } 314 }
  315 + return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getPayDate()),
  316 + response.getOutBizNo(), response);
  317 + } else {
  318 + // 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询, 或相同 outBizNo 重新发起转账
  319 + // 当出现 ORDER_NOT_EXIST 可能是转账还在处理中,也可能是转账处理失败. 返回 WAIT 状态. 后续 job 会轮询, 或相同 outBizNo 重新发起转账
  320 + if (ObjectUtils.equalsAny(response.getSubCode(), "ORDER_NOT_EXIST", "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) {
  321 + return PayTransferRespDTO.waitingOf(null, outTradeNo, response);
  322 + }
  323 + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
  324 + outTradeNo, response);
279 } 325 }
280 } 326 }
281 327
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java
@@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifie @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifie
9 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; 9 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
10 import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; 10 import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
11 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; 11 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
  12 +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
12 13
13 import java.time.LocalDateTime; 14 import java.time.LocalDateTime;
14 import java.util.Map; 15 import java.util.Map;
@@ -71,4 +72,9 @@ public class MockPayClient extends AbstractPayClient<NonePayClientConfig> { @@ -71,4 +72,9 @@ public class MockPayClient extends AbstractPayClient<NonePayClientConfig> {
71 throw new UnsupportedOperationException("待实现"); 72 throw new UnsupportedOperationException("待实现");
72 } 73 }
73 74
  75 + @Override
  76 + protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) {
  77 + throw new UnsupportedOperationException("待实现");
  78 + }
  79 +
74 } 80 }
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java
@@ -16,6 +16,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDT @@ -16,6 +16,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDT
16 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; 16 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
17 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; 17 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
18 import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; 18 import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
  19 +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
19 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; 20 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
20 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result; 21 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
21 import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult; 22 import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
@@ -431,6 +432,12 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC @@ -431,6 +432,12 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
431 protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { 432 protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
432 throw new UnsupportedOperationException("待实现"); 433 throw new UnsupportedOperationException("待实现");
433 } 434 }
  435 +
  436 + @Override
  437 + protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) {
  438 + throw new UnsupportedOperationException("待实现");
  439 + }
  440 +
434 // ========== 各种工具方法 ========== 441 // ========== 各种工具方法 ==========
435 442
436 static String formatDateV2(LocalDateTime time) { 443 static String formatDateV2(LocalDateTime time) {
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferStatusRespEnum.java
@@ -38,4 +38,8 @@ public enum PayTransferStatusRespEnum { @@ -38,4 +38,8 @@ public enum PayTransferStatusRespEnum {
38 public static boolean isClosed(Integer status) { 38 public static boolean isClosed(Integer status) {
39 return Objects.equals(status, CLOSED.getStatus()); 39 return Objects.equals(status, CLOSED.getStatus());
40 } 40 }
  41 +
  42 + public static boolean isInProgress(Integer status) {
  43 + return Objects.equals(status, IN_PROGRESS.getStatus());
  44 + }
41 } 45 }
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
@@ -66,9 +66,9 @@ public interface ErrorCodeConstants { @@ -66,9 +66,9 @@ public interface ErrorCodeConstants {
66 // ========== 转账模块 1-007-009-000 ========== 66 // ========== 转账模块 1-007-009-000 ==========
67 ErrorCode PAY_TRANSFER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_009_000, "发起转账报错,错误码:{},错误提示:{}"); 67 ErrorCode PAY_TRANSFER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_009_000, "发起转账报错,错误码:{},错误提示:{}");
68 ErrorCode PAY_TRANSFER_NOT_FOUND = new ErrorCode(1_007_009_001, "转账单不存在"); 68 ErrorCode PAY_TRANSFER_NOT_FOUND = new ErrorCode(1_007_009_001, "转账单不存在");
69 - ErrorCode PAY_TRANSFER_STATUS_IS_SUCCESS = new ErrorCode(1_007_009_002, "转账单已成功转账");  
70 - ErrorCode PAY_TRANSFER_EXISTS = new ErrorCode(1_007_009_003, "已经存在转账单");  
71 - ErrorCode PAY_MERCHANT_TRANSFER_EXISTS = new ErrorCode(1_007_009_004, "该笔业务的转账已经存在,请查询转账订单相关状态"); 69 + ErrorCode PAY_SAME_MERCHANT_TRANSFER_TYPE_NOT_MATCH = new ErrorCode(1_007_009_002, "两次相同转账请求的类型不匹配");
  70 + ErrorCode PAY_SAME_MERCHANT_TRANSFER_PRICE_NOT_MATCH = new ErrorCode(1_007_009_003, "两次相同转账请求的金额不匹配");
  71 + ErrorCode PAY_MERCHANT_TRANSFER_EXISTS = new ErrorCode(1_007_009_004, "该笔业务的转账已经发起,请查询转账订单相关状态");
72 ErrorCode PAY_TRANSFER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_005, "转账单不处于待转账"); 72 ErrorCode PAY_TRANSFER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_005, "转账单不处于待转账");
73 ErrorCode PAY_TRANSFER_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_006, "转账单不处于待转账或转账中"); 73 ErrorCode PAY_TRANSFER_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_006, "转账单不处于待转账或转账中");
74 74
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java
@@ -40,9 +40,13 @@ public enum PayTransferStatusEnum { @@ -40,9 +40,13 @@ public enum PayTransferStatusEnum {
40 public static boolean isClosed(Integer status) { 40 public static boolean isClosed(Integer status) {
41 return Objects.equals(status, CLOSED.getStatus()); 41 return Objects.equals(status, CLOSED.getStatus());
42 } 42 }
  43 +
43 public static boolean isWaiting(Integer status) { 44 public static boolean isWaiting(Integer status) {
44 return Objects.equals(status, WAITING.getStatus()); 45 return Objects.equals(status, WAITING.getStatus());
45 } 46 }
  47 + public static boolean isInProgress(Integer status) {
  48 + return Objects.equals(status, IN_PROGRESS.getStatus());
  49 + }
46 50
47 /** 51 /**
48 * 是否处于待转账或者转账中的状态 52 * 是否处于待转账或者转账中的状态
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/transfer/PayTransferConvert.java
@@ -18,7 +18,7 @@ public interface PayTransferConvert { @@ -18,7 +18,7 @@ public interface PayTransferConvert {
18 18
19 PayTransferDO convert(PayTransferCreateReqDTO dto); 19 PayTransferDO convert(PayTransferCreateReqDTO dto);
20 20
21 - PayTransferUnifiedReqDTO convert2(PayTransferCreateReqDTO dto); 21 + PayTransferUnifiedReqDTO convert2(PayTransferDO dto);
22 22
23 PayTransferCreateReqDTO convert(PayTransferCreateReqVO vo); 23 PayTransferCreateReqDTO convert(PayTransferCreateReqVO vo);
24 24
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java
1 package cn.iocoder.yudao.module.pay.dal.mysql.transfer; 1 package cn.iocoder.yudao.module.pay.dal.mysql.transfer;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO; 6 import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO;
6 import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; 7 import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
7 -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;  
8 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 8 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
9 import org.apache.ibatis.annotations.Mapper; 9 import org.apache.ibatis.annotations.Mapper;
10 10
@@ -40,6 +40,10 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> { @@ -40,6 +40,10 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
40 .betweenIfPresent(PayTransferDO::getCreateTime, reqVO.getCreateTime()) 40 .betweenIfPresent(PayTransferDO::getCreateTime, reqVO.getCreateTime())
41 .orderByDesc(PayTransferDO::getId)); 41 .orderByDesc(PayTransferDO::getId));
42 } 42 }
  43 +
  44 + default List<PayTransferDO> selectListByStatus(Integer status){
  45 + return selectList(PayTransferDO::getStatus, status);
  46 + }
43 } 47 }
44 48
45 49
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java
@@ -14,6 +14,7 @@ import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; @@ -14,6 +14,7 @@ import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
14 import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; 14 import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
15 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; 15 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
16 import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; 16 import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
  17 +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
17 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; 18 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
18 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; 19 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
19 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; 20 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
@@ -181,4 +182,9 @@ public class WalletPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; { @@ -181,4 +182,9 @@ public class WalletPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; {
181 throw new UnsupportedOperationException("待实现"); 182 throw new UnsupportedOperationException("待实现");
182 } 183 }
183 184
  185 + @Override
  186 + protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) {
  187 + throw new UnsupportedOperationException("待实现");
  188 + }
  189 +
184 } 190 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.job.transfer;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
  5 +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
  6 +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 转账订单的同步 Job
  13 + *
  14 + * 由于转账订单的转账结果,有些渠道是异步通知进行同步的,考虑到异步通知可能会失败(小概率),所以需要定时进行同步。
  15 + *
  16 + * @author jason
  17 + */
  18 +@Component
  19 +public class PayTransferSyncJob implements JobHandler {
  20 +
  21 + @Resource
  22 + private PayTransferService transferService;
  23 +
  24 + @Override
  25 + @TenantJob
  26 + public String execute(String param) {
  27 + int count = transferService.syncTransfer();
  28 + return StrUtil.format("同步转账订单 {} 个", count);
  29 + }
  30 +}
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java
@@ -48,4 +48,10 @@ public interface PayTransferService { @@ -48,4 +48,10 @@ public interface PayTransferService {
48 */ 48 */
49 PageResult<PayTransferDO> getTransferPage(PayTransferPageReqVO pageReqVO); 49 PageResult<PayTransferDO> getTransferPage(PayTransferPageReqVO pageReqVO);
50 50
  51 + /**
  52 + * 同步渠道转账单状态
  53 + *
  54 + * @return 同步到状态的转账数量,包括转账成功、转账失败、转账中的
  55 + */
  56 + int syncTransfer();
51 } 57 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java
1 package cn.iocoder.yudao.module.pay.service.transfer; 1 package cn.iocoder.yudao.module.pay.service.transfer;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
4 import cn.hutool.extra.spring.SpringUtil; 5 import cn.hutool.extra.spring.SpringUtil;
5 -import cn.iocoder.yudao.framework.common.exception.ServiceException;  
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
8 import cn.iocoder.yudao.framework.pay.core.client.PayClient; 8 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
9 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; 9 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
10 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; 10 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
11 import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum; 11 import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum;
  12 +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
  13 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; 14 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
13 import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO; 15 import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO;
14 import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO; 16 import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO;
@@ -18,6 +20,7 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; @@ -18,6 +20,7 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
18 import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferMapper; 20 import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferMapper;
19 import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; 21 import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
20 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; 22 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
  23 +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
21 import cn.iocoder.yudao.module.pay.service.app.PayAppService; 24 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
22 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; 25 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
23 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; 26 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
@@ -27,7 +30,7 @@ import org.springframework.transaction.annotation.Transactional; @@ -27,7 +30,7 @@ import org.springframework.transaction.annotation.Transactional;
27 30
28 import javax.annotation.Resource; 31 import javax.annotation.Resource;
29 import javax.validation.Validator; 32 import javax.validation.Validator;
30 -import java.util.Objects; 33 +import java.util.List;
31 34
32 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
33 import static cn.iocoder.yudao.module.pay.convert.transfer.PayTransferConvert.INSTANCE; 36 import static cn.iocoder.yudao.module.pay.convert.transfer.PayTransferConvert.INSTANCE;
@@ -75,56 +78,60 @@ public class PayTransferServiceImpl implements PayTransferService { @@ -75,56 +78,60 @@ public class PayTransferServiceImpl implements PayTransferService {
75 78
76 @Override 79 @Override
77 public Long createTransfer(PayTransferCreateReqDTO reqDTO) { 80 public Long createTransfer(PayTransferCreateReqDTO reqDTO) {
78 - // 1.1 校验转账单是否可以提交  
79 - validateTransferCanCreate(reqDTO.getAppId(), reqDTO.getMerchantTransferId());  
80 - // 1.2 校验 App 81 + // 1.1 校验 App
81 PayAppDO payApp = appService.validPayApp(reqDTO.getAppId()); 82 PayAppDO payApp = appService.validPayApp(reqDTO.getAppId());
82 - // 1.3 校验支付渠道是否有效 83 + // 1.2 校验支付渠道是否有效
83 PayChannelDO channel = channelService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode()); 84 PayChannelDO channel = channelService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode());
84 PayClient client = channelService.getPayClient(channel.getId()); 85 PayClient client = channelService.getPayClient(channel.getId());
85 if (client == null) { 86 if (client == null) {
86 log.error("[createTransfer][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); 87 log.error("[createTransfer][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
87 throw exception(CHANNEL_NOT_FOUND); 88 throw exception(CHANNEL_NOT_FOUND);
88 } 89 }
89 - // 2.创建转账单  
90 - String no = noRedisDAO.generate(TRANSFER_NO_PREFIX);  
91 - PayTransferDO transfer = INSTANCE.convert(reqDTO)  
92 - .setChannelId(channel.getId())  
93 - .setNo(no).setStatus(WAITING.getStatus())  
94 - .setNotifyUrl(payApp.getTransferNotifyUrl());  
95 - transferMapper.insert(transfer);  
96 - PayTransferRespDTO unifiedTransferResp = null; 90 + // 1.3 校验转账单已经发起过转账。
  91 + PayTransferDO transfer = validateTransferCanCreate(reqDTO);
  92 +
  93 + if (transfer == null) {
  94 + // 2.不存在创建转账单. 否则允许使用相同的 no 再次发起转账
  95 + String no = noRedisDAO.generate(TRANSFER_NO_PREFIX);
  96 + transfer = INSTANCE.convert(reqDTO)
  97 + .setChannelId(channel.getId())
  98 + .setNo(no).setStatus(WAITING.getStatus())
  99 + .setNotifyUrl(payApp.getTransferNotifyUrl());
  100 + transferMapper.insert(transfer);
  101 + }
97 try { 102 try {
98 // 3. 调用三方渠道发起转账 103 // 3. 调用三方渠道发起转账
99 - PayTransferUnifiedReqDTO transferUnifiedReq = INSTANCE.convert2(reqDTO)  
100 - .setOutTransferNo(no);  
101 - unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq);  
102 - } catch (ServiceException ex) {  
103 - // 业务异常.直接返回转账失败的结果  
104 - log.error("[createTransfer][转账 id({}) requestDTO({}) 发生业务异常]", transfer.getId(), reqDTO, ex);  
105 - unifiedTransferResp = PayTransferRespDTO.closedOf("", "", no, ex); 104 + PayTransferUnifiedReqDTO transferUnifiedReq = INSTANCE.convert2(transfer)
  105 + .setOutTransferNo(transfer.getNo());
  106 + PayTransferRespDTO unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq);
  107 + // 4. 通知转账结果
  108 + getSelf().notifyTransfer(channel, unifiedTransferResp);
106 } catch (Throwable e) { 109 } catch (Throwable e) {
107 // 注意这里仅打印异常,不进行抛出。 110 // 注意这里仅打印异常,不进行抛出。
108 - // 原因是:虽然调用支付渠道进行转账发生异常(网络请求超时),实际转账成功。这个结果,后续通过转账回调、或者转账轮询可以拿到。  
109 - // TODO 需要加转账回调业务接口 和 转账轮询未实现  
110 - // 最终,在异常的情况下,支付中心会异步回调业务的转账回调接口,提供转账结果 111 + // 原因是:虽然调用支付渠道进行转账发生异常(网络请求超时),实际转账成功。这个结果,后续转账轮询可以拿到。
  112 + // 或者使用相同 no 再次发起转账请求
111 log.error("[createTransfer][转账 id({}) requestDTO({}) 发生异常]", transfer.getId(), reqDTO, e); 113 log.error("[createTransfer][转账 id({}) requestDTO({}) 发生异常]", transfer.getId(), reqDTO, e);
112 } 114 }
113 - if (Objects.nonNull(unifiedTransferResp)) {  
114 - // 4. 通知转账结果  
115 - getSelf().notifyTransfer(channel, unifiedTransferResp);  
116 - }  
117 - return transfer.getId();  
118 - }  
119 115
120 - @Override  
121 - public PayTransferDO getTransfer(Long id) {  
122 - return transferMapper.selectById(id); 116 + return transfer.getId();
123 } 117 }
124 118
125 - @Override  
126 - public PageResult<PayTransferDO> getTransferPage(PayTransferPageReqVO pageReqVO) {  
127 - return transferMapper.selectPage(pageReqVO); 119 + private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO dto) {
  120 + PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(dto.getAppId(), dto.getMerchantTransferId());
  121 + if (transfer != null) {
  122 + // 已经存在,并且状态不为等待状态。说明已经调用渠道转账并返回结果.
  123 + if (!PayTransferStatusEnum.isWaiting(transfer.getStatus())) {
  124 + throw exception(PAY_MERCHANT_TRANSFER_EXISTS);
  125 + }
  126 + if (ObjectUtil.notEqual(dto.getPrice(), transfer.getPrice())) {
  127 + throw exception(PAY_SAME_MERCHANT_TRANSFER_PRICE_NOT_MATCH);
  128 + }
  129 + if (ObjectUtil.notEqual(dto.getType(), transfer.getType())) {
  130 + throw exception(PAY_SAME_MERCHANT_TRANSFER_TYPE_NOT_MATCH);
  131 + }
  132 + }
  133 + // 如果状态为等待状态。不知道渠道转账是否发起成功。 允许使用相同的 no 再次发起转账,渠道会保证幂等
  134 + return transfer;
128 } 135 }
129 136
130 @Transactional(rollbackFor = Exception.class) 137 @Transactional(rollbackFor = Exception.class)
@@ -138,17 +145,40 @@ public class PayTransferServiceImpl implements PayTransferService { @@ -138,17 +145,40 @@ public class PayTransferServiceImpl implements PayTransferService {
138 if (PayTransferStatusRespEnum.isClosed(notify.getStatus())) { 145 if (PayTransferStatusRespEnum.isClosed(notify.getStatus())) {
139 notifyTransferClosed(channel, notify); 146 notifyTransferClosed(channel, notify);
140 } 147 }
  148 + // 转账处理中的回调
  149 + if (PayTransferStatusRespEnum.isInProgress(notify.getStatus())) {
  150 + notifyTransferInProgress(channel, notify);
  151 + }
141 // WAITING 状态无需处理 152 // WAITING 状态无需处理
142 - // TODO IN_PROGRESS 待处理  
143 } 153 }
144 154
145 - private void validateTransferCanCreate(Long appId, String merchantTransferId) {  
146 - PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(appId, merchantTransferId);  
147 - if (transfer != null) { // 是否存在  
148 - throw exception(PAY_MERCHANT_TRANSFER_EXISTS); 155 + private void notifyTransferInProgress(PayChannelDO channel, PayTransferRespDTO notify) {
  156 + // 1.校验
  157 + PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
  158 + if (transfer == null) {
  159 + throw exception(PAY_TRANSFER_NOT_FOUND);
  160 + }
  161 + if (isInProgress(transfer.getStatus())) { // 如果已经是转账中,直接返回,不用重复更新
  162 + return;
  163 + }
  164 + if (!isWaiting(transfer.getStatus())) {
  165 + throw exception(PAY_TRANSFER_STATUS_IS_NOT_WAITING);
149 } 166 }
  167 + // 2.更新
  168 + int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(),
  169 + CollUtil.newArrayList(WAITING.getStatus()),
  170 + new PayTransferDO().setStatus(IN_PROGRESS.getStatus()));
  171 + if (updateCounts == 0) {
  172 + throw exception(PAY_TRANSFER_STATUS_IS_NOT_WAITING);
  173 + }
  174 + log.info("[notifyTransferInProgress][transfer({}) 更新为转账进行中状态]", transfer.getId());
  175 +
  176 + // 3. 插入转账通知记录
  177 + notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(),
  178 + transfer.getId());
150 } 179 }
151 180
  181 +
152 private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) { 182 private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) {
153 // 1.校验 183 // 1.校验
154 PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo()); 184 PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
@@ -210,6 +240,56 @@ public class PayTransferServiceImpl implements PayTransferService { @@ -210,6 +240,56 @@ public class PayTransferServiceImpl implements PayTransferService {
210 240
211 } 241 }
212 242
  243 + @Override
  244 + public PayTransferDO getTransfer(Long id) {
  245 + return transferMapper.selectById(id);
  246 + }
  247 +
  248 + @Override
  249 + public PageResult<PayTransferDO> getTransferPage(PayTransferPageReqVO pageReqVO) {
  250 + return transferMapper.selectPage(pageReqVO);
  251 + }
  252 +
  253 + @Override
  254 + public int syncTransfer() {
  255 + List<PayTransferDO> list = transferMapper.selectListByStatus(WAITING.getStatus());
  256 + if (CollUtil.isEmpty(list)) {
  257 + return 0;
  258 + }
  259 + int count = 0;
  260 + for (PayTransferDO transfer : list) {
  261 + count += syncTransfer(transfer) ? 1 : 0;
  262 + }
  263 + return count;
  264 + }
  265 +
  266 + private boolean syncTransfer(PayTransferDO transfer) {
  267 + try {
  268 + // 1. 查询转账订单信息
  269 + PayClient payClient = channelService.getPayClient(transfer.getChannelId());
  270 + if (payClient == null) {
  271 + log.error("[syncTransfer][渠道编号({}) 找不到对应的支付客户端]", transfer.getChannelId());
  272 + return false;
  273 + }
  274 + PayTransferRespDTO resp = payClient.getTransfer(transfer.getNo(),
  275 + PayTransferTypeEnum.typeOf(transfer.getType()));
  276 +
  277 + // 2. 回调转账结果
  278 + notifyTransfer(transfer.getChannelId(), resp);
  279 + return true;
  280 + } catch (Throwable ex) {
  281 + log.error("[syncTransfer][transfer({}) 同步转账单状态异常]", transfer.getId(), ex);
  282 + return false;
  283 + }
  284 + }
  285 +
  286 + private void notifyTransfer(Long channelId, PayTransferRespDTO notify) {
  287 + // 校验渠道是否有效
  288 + PayChannelDO channel = channelService.validPayChannel(channelId);
  289 + // 通知转账结果给对应的业务
  290 + TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyTransfer(channel, notify));
  291 + }
  292 +
213 /** 293 /**
214 * 获得自身的代理对象,解决 AOP 生效问题 294 * 获得自身的代理对象,解决 AOP 生效问题
215 * 295 *