mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-10-31 18:49:06 +08:00 
			
		
		
		
	Pay Client 新增转账接口
This commit is contained in:
		| @ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | |||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; | ||||||
|  |  | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
|  |  | ||||||
| @ -76,4 +78,12 @@ public interface PayClient { | |||||||
|      */ |      */ | ||||||
|     PayRefundRespDTO getRefund(String outTradeNo, String outRefundNo); |     PayRefundRespDTO getRefund(String outTradeNo, String outRefundNo); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 调用渠道,进行转账 | ||||||
|  |      * | ||||||
|  |      * @param reqDTO 统一转账请求信息 | ||||||
|  |      * @return 转账信息 | ||||||
|  |      */ | ||||||
|  |     PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO); | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,91 @@ | |||||||
|  | package cn.iocoder.yudao.framework.pay.core.client.dto.transfer; | ||||||
|  |  | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum; | ||||||
|  | import lombok.Data; | ||||||
|  |  | ||||||
|  | import java.time.LocalDateTime; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 统一转账 Response DTO | ||||||
|  |  * | ||||||
|  |  * @author jason | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | public class PayTransferRespDTO { | ||||||
|  |  | ||||||
|  |     private Integer status; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 外部转账单号 | ||||||
|  |      * | ||||||
|  |      */ | ||||||
|  |     private String outTransferNo; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 支付渠道编号 | ||||||
|  |      */ | ||||||
|  |     private String channelOrderNo; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 支付成功时间 | ||||||
|  |      */ | ||||||
|  |     private LocalDateTime successTime; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 原始的返回结果 | ||||||
|  |      */ | ||||||
|  |     private Object rawData; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 调用渠道的错误码 | ||||||
|  |      */ | ||||||
|  |     private String channelErrorCode; | ||||||
|  |     /** | ||||||
|  |      * 调用渠道报错时,错误信息 | ||||||
|  |      */ | ||||||
|  |     private String channelErrorMsg; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 创建【WAITING】状态的转账返回 | ||||||
|  |      */ | ||||||
|  |     public static PayTransferRespDTO waitingOf(String channelOrderNo, | ||||||
|  |                                              String outTransferNo, Object rawData) { | ||||||
|  |         PayTransferRespDTO respDTO = new PayTransferRespDTO(); | ||||||
|  |         respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus(); | ||||||
|  |         respDTO.channelOrderNo = channelOrderNo; | ||||||
|  |         respDTO.outTransferNo = outTransferNo; | ||||||
|  |         respDTO.rawData = rawData; | ||||||
|  |         return respDTO; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 创建【FAILURE】状态的转账返回 | ||||||
|  |      */ | ||||||
|  |     public static PayTransferRespDTO failureOf(String channelErrorCode, String channelErrorMsg, | ||||||
|  |                                              String outTransferNo, Object rawData) { | ||||||
|  |         PayTransferRespDTO respDTO = new PayTransferRespDTO(); | ||||||
|  |         respDTO.status = PayTransferStatusRespEnum.FAILURE.getStatus(); | ||||||
|  |         respDTO.channelErrorCode = channelErrorCode; | ||||||
|  |         respDTO.channelErrorMsg = channelErrorMsg; | ||||||
|  |         // 相对通用的字段 | ||||||
|  |         respDTO.outTransferNo = outTransferNo; | ||||||
|  |         respDTO.rawData = rawData; | ||||||
|  |         return respDTO; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 创建【SUCCESS】状态的转账返回 | ||||||
|  |      */ | ||||||
|  |     public static PayTransferRespDTO successOf(String channelTransferNo, LocalDateTime successTime, | ||||||
|  |                                              String outTransferNo, Object rawData) { | ||||||
|  |         PayTransferRespDTO respDTO = new PayTransferRespDTO(); | ||||||
|  |         respDTO.status = PayTransferStatusRespEnum.SUCCESS.getStatus(); | ||||||
|  |         respDTO.channelOrderNo = channelTransferNo; | ||||||
|  |         respDTO.successTime = successTime; | ||||||
|  |         // 相对通用的字段 | ||||||
|  |         respDTO.outTransferNo = outTransferNo; | ||||||
|  |         respDTO.rawData = rawData; | ||||||
|  |         return respDTO; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -0,0 +1,65 @@ | |||||||
|  | package cn.iocoder.yudao.framework.pay.core.client.dto.transfer; | ||||||
|  |  | ||||||
|  | import cn.iocoder.yudao.framework.common.validation.InEnum; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum; | ||||||
|  | import lombok.Data; | ||||||
|  | import org.hibernate.validator.constraints.Length; | ||||||
|  |  | ||||||
|  | import javax.validation.constraints.Min; | ||||||
|  | import javax.validation.constraints.NotEmpty; | ||||||
|  | import javax.validation.constraints.NotNull; | ||||||
|  | import java.util.Map; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 统一转账 Request DTO | ||||||
|  |  * | ||||||
|  |  * @author jason | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | public class PayTransferUnifiedReqDTO { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 转账类型 | ||||||
|  |      * | ||||||
|  |      * 关联 {@link PayTransferTypeEnum#getType()} | ||||||
|  |      */ | ||||||
|  |     @NotNull(message = "转账类型不能为空") | ||||||
|  |     @InEnum(PayTransferTypeEnum.class) | ||||||
|  |     private Integer type; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 用户 IP | ||||||
|  |      */ | ||||||
|  |     @NotEmpty(message = "用户 IP 不能为空") | ||||||
|  |     private String userIp; | ||||||
|  |  | ||||||
|  |     @NotEmpty(message = "外部转账单编号不能为空") | ||||||
|  |     private String outTransferNo; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 转账金额,单位:分 | ||||||
|  |      */ | ||||||
|  |     @NotNull(message = "转账金额不能为空") | ||||||
|  |     @Min(value = 1, message = "转账金额必须大于零") | ||||||
|  |     private Integer price; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 转账标题 | ||||||
|  |      */ | ||||||
|  |     @NotEmpty(message = "转账标题不能为空") | ||||||
|  |     @Length(max = 128, message = "转账标题不能超过 128") | ||||||
|  |     private String title; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 收款方信息,转账类型不同信息不同 | ||||||
|  |      */ | ||||||
|  |     @NotEmpty(message = "收款方信息 不能为空") | ||||||
|  |     private Map<String, String> payeeInfo; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 支付渠道的额外参数 | ||||||
|  |      * | ||||||
|  |      */ | ||||||
|  |     private Map<String, String> channelExtras; | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -8,6 +8,8 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | |||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.exception.PayException; | import cn.iocoder.yudao.framework.pay.core.client.exception.PayException; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
|  |  | ||||||
| @ -181,6 +183,26 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen | |||||||
|     protected abstract PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) |     protected abstract PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) | ||||||
|             throws Throwable; |             throws Throwable; | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { | ||||||
|  |         ValidationUtils.validate(reqDTO); | ||||||
|  |         PayTransferRespDTO resp; | ||||||
|  |         try{ | ||||||
|  |             resp = doUnifiedTransfer(reqDTO); | ||||||
|  |         }catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 | ||||||
|  |             throw ex; | ||||||
|  |         } catch (Throwable ex) { | ||||||
|  |             // 系统异常,则包装成 PayException 异常抛出 | ||||||
|  |             log.error("[unifiedTransfer][客户端({}) request({}) 发起转账异常]", | ||||||
|  |                     getId(), toJsonString(reqDTO), ex); | ||||||
|  |             throw buildPayException(ex); | ||||||
|  |         } | ||||||
|  |         return resp; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) | ||||||
|  |             throws Throwable; | ||||||
|  |  | ||||||
|     // ========== 各种工具方法 ========== |     // ========== 各种工具方法 ========== | ||||||
|  |  | ||||||
|     private PayException buildPayException(Throwable ex) { |     private PayException buildPayException(Throwable ex) { | ||||||
|  | |||||||
| @ -150,6 +150,10 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa | |||||||
|         // 2.1 执行请求 |         // 2.1 执行请求 | ||||||
|         AlipayTradeRefundResponse response = client.execute(request); |         AlipayTradeRefundResponse response = client.execute(request); | ||||||
|         if (!response.isSuccess()) { |         if (!response.isSuccess()) { | ||||||
|  |             // 当出现 ACQ.SYSTEM_ERROR, 退款可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询 | ||||||
|  |             if (Objects.equals(response.getSubCode(), "ACQ.SYSTEM_ERROR")) { | ||||||
|  |                 return PayRefundRespDTO.waitingOf(null, reqDTO.getOutRefundNo(), response); | ||||||
|  |             } | ||||||
|             return PayRefundRespDTO.failureOf(response.getSubCode(), response.getSubMsg(), reqDTO.getOutRefundNo(), response); |             return PayRefundRespDTO.failureOf(response.getSubCode(), response.getSubMsg(), reqDTO.getOutRefundNo(), response); | ||||||
|         } |         } | ||||||
|         // 2.2 创建返回结果 |         // 2.2 创建返回结果 | ||||||
| @ -198,6 +202,8 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa | |||||||
|         return PayRefundRespDTO.waitingOf(null, outRefundNo, response); |         return PayRefundRespDTO.waitingOf(null, outRefundNo, response); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     // ========== 各种工具方法 ========== |     // ========== 各种工具方法 ========== | ||||||
|  |  | ||||||
|     protected String formatAmount(Integer amount) { |     protected String formatAmount(Integer amount) { | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; | |||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; | import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; | ||||||
| import com.alipay.api.AlipayApiException; | import com.alipay.api.AlipayApiException; | ||||||
| @ -57,4 +59,8 @@ public class AlipayAppPayClient extends AbstractAlipayPayClient { | |||||||
|                 reqDTO.getOutTradeNo(), response); |                 reqDTO.getOutTradeNo(), response); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { | ||||||
|  |         throw new UnsupportedOperationException("支付宝【App 支付】不支持转账操作"); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,6 +5,8 @@ import cn.hutool.core.map.MapUtil; | |||||||
| import cn.hutool.core.util.StrUtil; | import cn.hutool.core.util.StrUtil; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; | import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; | ||||||
| import com.alipay.api.AlipayApiException; | import com.alipay.api.AlipayApiException; | ||||||
| @ -75,4 +77,8 @@ public class AlipayBarPayClient extends AbstractAlipayPayClient { | |||||||
|                 reqDTO.getOutTradeNo(), response); |                 reqDTO.getOutTradeNo(), response); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { | ||||||
|  |         throw new UnsupportedOperationException("支付宝【条码支付】不支持转账操作"); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,6 +4,8 @@ import cn.hutool.core.util.ObjectUtil; | |||||||
| import cn.hutool.http.Method; | import cn.hutool.http.Method; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; | import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; | ||||||
| import com.alipay.api.AlipayApiException; | import com.alipay.api.AlipayApiException; | ||||||
| @ -67,4 +69,8 @@ public class AlipayPcPayClient extends AbstractAlipayPayClient { | |||||||
|                 reqDTO.getOutTradeNo(), response); |                 reqDTO.getOutTradeNo(), response); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { | ||||||
|  |         throw new UnsupportedOperationException("支付宝【PC 网站】不支持转账操作"); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; | |||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; | import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; | ||||||
| import com.alipay.api.AlipayApiException; | import com.alipay.api.AlipayApiException; | ||||||
| @ -54,4 +56,8 @@ public class AlipayQrPayClient extends AbstractAlipayPayClient { | |||||||
|                 reqDTO.getOutTradeNo(), response); |                 reqDTO.getOutTradeNo(), response); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO)  { | ||||||
|  |         throw new UnsupportedOperationException("支付宝【扫码支付】不支持转账操作"); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,105 @@ | |||||||
|  | package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.map.MapUtil; | ||||||
|  | import cn.hutool.core.util.StrUtil; | ||||||
|  | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum; | ||||||
|  | import com.alipay.api.AlipayApiException; | ||||||
|  | import com.alipay.api.domain.AlipayFundTransUniTransferModel; | ||||||
|  | import com.alipay.api.domain.Participant; | ||||||
|  | import com.alipay.api.request.AlipayFundTransUniTransferRequest; | ||||||
|  | import com.alipay.api.response.AlipayFundTransUniTransferResponse; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  |  | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
|  | import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; | ||||||
|  | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 支付宝转账的 PayClient 实现类 | ||||||
|  |  * | ||||||
|  |  * @author jason | ||||||
|  |  */ | ||||||
|  | @Slf4j | ||||||
|  | public class AlipayTransferClient extends AbstractAlipayPayClient { | ||||||
|  |     public AlipayTransferClient(Long channelId, AlipayPayClientConfig config) { | ||||||
|  |         super(channelId, PayChannelEnum.ALIPAY_TRANSFER.getCode(), config); | ||||||
|  |     } | ||||||
|  |     @Override | ||||||
|  |     protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { | ||||||
|  |         throw new UnsupportedOperationException("支付宝转账不支持统一下单请求"); | ||||||
|  |     } | ||||||
|  |     @Override | ||||||
|  |     protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { | ||||||
|  |         throw new UnsupportedOperationException("支付宝转账不支持统一退款请求"); | ||||||
|  |     } | ||||||
|  |     @Override | ||||||
|  |     protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException { | ||||||
|  |         // 1.1 构建 AlipayFundTransUniTransferModel | ||||||
|  |         AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel(); | ||||||
|  |         // ① 通用的参数 | ||||||
|  |         model.setTransAmount(formatAmount(reqDTO.getPrice())); // 转账金额 | ||||||
|  |         model.setOrderTitle(reqDTO.getTitle());               // 转账业务的标题,用于在支付宝用户的账单里显示。 | ||||||
|  |         model.setOutBizNo(reqDTO.getOutTransferNo()); | ||||||
|  |         model.setProductCode("TRANS_ACCOUNT_NO_PWD");    // 销售产品码。单笔无密转账固定为 TRANS_ACCOUNT_NO_PWD | ||||||
|  |         model.setBizScene("DIRECT_TRANSFER");           // 业务场景 单笔无密转账固定为 DIRECT_TRANSFER。 | ||||||
|  |         model.setBusinessParams(JsonUtils.toJsonString(reqDTO.getChannelExtras())); | ||||||
|  |         PayTransferTypeEnum transferType = PayTransferTypeEnum.valueOf(reqDTO.getType()); | ||||||
|  |         switch(transferType){ | ||||||
|  |             case WX_BALANCE : | ||||||
|  |             case WALLET_BALANCE : { | ||||||
|  |                 log.error("[doUnifiedTransfer],支付宝转账不支持的转账类型{}", transferType); | ||||||
|  |                 throw new UnsupportedOperationException(String.format("支付宝转账不支持转账类型: %s",transferType.getName())); | ||||||
|  |             } | ||||||
|  |             case ALIPAY_BALANCE : { | ||||||
|  |                 // ② 个性化的参数 | ||||||
|  |                 Participant payeeInfo = new Participant(); | ||||||
|  |                 payeeInfo.setIdentityType("ALIPAY_LOGON_ID"); | ||||||
|  |                 String logonId = MapUtil.getStr(reqDTO.getPayeeInfo(), "ALIPAY_LOGON_ID"); | ||||||
|  |                 if (StrUtil.isEmpty(logonId)) { | ||||||
|  |                     throw exception0(BAD_REQUEST.getCode(), "支付包登录 ID 不能为空"); | ||||||
|  |                 } | ||||||
|  |                 String accountName = MapUtil.getStr(reqDTO.getPayeeInfo(), "ALIPAY_ACCOUNT_NAME"); | ||||||
|  |                 if (StrUtil.isEmpty(accountName)) { | ||||||
|  |                     throw exception0(BAD_REQUEST.getCode(), "支付包账户名称不能为空"); | ||||||
|  |                 } | ||||||
|  |                 payeeInfo.setIdentity(logonId); // 支付宝登录号 | ||||||
|  |                 payeeInfo.setName(accountName); // 支付宝账号姓名 | ||||||
|  |                 model.setPayeeInfo(payeeInfo); | ||||||
|  |                 // 1.2 构建 AlipayFundTransUniTransferRequest | ||||||
|  |                 AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest(); | ||||||
|  |                 request.setBizModel(model); | ||||||
|  |                 // 执行请求 | ||||||
|  |                 AlipayFundTransUniTransferResponse response = client.certificateExecute(request); | ||||||
|  |                 // 处理结果 | ||||||
|  |                 if (!response.isSuccess()) { | ||||||
|  |                     // 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询 | ||||||
|  |                     if (Objects.equals(response.getSubCode(), "SYSTEM_ERROR")) { | ||||||
|  |                         return PayTransferRespDTO.waitingOf(null, reqDTO.getOutTransferNo(), response); | ||||||
|  |                     } | ||||||
|  |                     return PayTransferRespDTO.failureOf(response.getSubCode(), response.getSubMsg(), | ||||||
|  |                             reqDTO.getOutTransferNo(), response); | ||||||
|  |  | ||||||
|  |                 } | ||||||
|  |                 return  PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()), | ||||||
|  |                         response.getOutBizNo(), response); | ||||||
|  |             } | ||||||
|  |             case BANK_CARD : { | ||||||
|  |                 Participant payeeInfo = new Participant(); | ||||||
|  |                 payeeInfo.setIdentityType("BANKCARD_ACCOUNT"); | ||||||
|  |                 throw new UnsupportedOperationException("待实现"); | ||||||
|  |             } | ||||||
|  |             default: { | ||||||
|  |                 throw new IllegalStateException("不正确的转账类型: " + transferType); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -3,6 +3,8 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; | |||||||
| import cn.hutool.http.Method; | import cn.hutool.http.Method; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; | import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; | ||||||
| import com.alipay.api.AlipayApiException; | import com.alipay.api.AlipayApiException; | ||||||
| @ -56,4 +58,8 @@ public class AlipayWapPayClient extends AbstractAlipayPayClient { | |||||||
|                 reqDTO.getOutTradeNo(), response); |                 reqDTO.getOutTradeNo(), response); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { | ||||||
|  |         throw new UnsupportedOperationException("支付宝【Wap 网站】不支持转账操作"); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | |||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; | import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; | import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | ||||||
| @ -64,4 +66,8 @@ public class MockPayClient extends AbstractPayClient<NonePayClientConfig> { | |||||||
|         throw new UnsupportedOperationException("模拟支付无支付回调"); |         throw new UnsupportedOperationException("模拟支付无支付回调"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { | ||||||
|  |         throw new UnsupportedOperationException("待实现"); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -12,6 +12,8 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | |||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; | import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; | import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; | ||||||
| import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; | import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; | ||||||
| @ -425,6 +427,10 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { | ||||||
|  |        throw new UnsupportedOperationException("待实现"); | ||||||
|  |     } | ||||||
|     // ========== 各种工具方法 ========== |     // ========== 各种工具方法 ========== | ||||||
|  |  | ||||||
|     static String formatDateV2(LocalDateTime time) { |     static String formatDateV2(LocalDateTime time) { | ||||||
|  | |||||||
| @ -28,6 +28,7 @@ public enum PayChannelEnum { | |||||||
|     ALIPAY_APP("alipay_app", "支付宝App 支付", AlipayPayClientConfig.class), |     ALIPAY_APP("alipay_app", "支付宝App 支付", AlipayPayClientConfig.class), | ||||||
|     ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class), |     ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class), | ||||||
|     ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class), |     ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class), | ||||||
|  |     ALIPAY_TRANSFER("alipay_transfer", "支付宝转账", AlipayPayClientConfig.class), | ||||||
|  |  | ||||||
|     MOCK("mock", "模拟支付", NonePayClientConfig.class), |     MOCK("mock", "模拟支付", NonePayClientConfig.class), | ||||||
|  |  | ||||||
|  | |||||||
| @ -0,0 +1,21 @@ | |||||||
|  | package cn.iocoder.yudao.framework.pay.core.enums.transfer; | ||||||
|  |  | ||||||
|  | import lombok.AllArgsConstructor; | ||||||
|  | import lombok.Getter; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 渠道的转账状态枚举 | ||||||
|  |  * | ||||||
|  |  * @author jason | ||||||
|  |  */ | ||||||
|  | @Getter | ||||||
|  | @AllArgsConstructor | ||||||
|  | public enum PayTransferStatusRespEnum { | ||||||
|  |  | ||||||
|  |     WAITING(0, "等待转账"), | ||||||
|  |     SUCCESS(10, "转账成功"), | ||||||
|  |     FAILURE(20, "转账失败"); | ||||||
|  |  | ||||||
|  |     private final Integer status; | ||||||
|  |     private final String name; | ||||||
|  | } | ||||||
| @ -0,0 +1,36 @@ | |||||||
|  | package cn.iocoder.yudao.framework.pay.core.enums.transfer; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.util.ArrayUtil; | ||||||
|  | import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | ||||||
|  | import lombok.AllArgsConstructor; | ||||||
|  | import lombok.Getter; | ||||||
|  |  | ||||||
|  | import java.util.Arrays; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 转账类型枚举 | ||||||
|  |  * | ||||||
|  |  * @author jason | ||||||
|  |  */ | ||||||
|  | @AllArgsConstructor | ||||||
|  | @Getter | ||||||
|  | public enum PayTransferTypeEnum implements IntArrayValuable { | ||||||
|  |     ALIPAY_BALANCE(1, "支付宝余额"), | ||||||
|  |     WX_BALANCE(2, "微信余额"), | ||||||
|  |     BANK_CARD(3, "银行卡"), | ||||||
|  |     WALLET_BALANCE(4, "钱包余额"); | ||||||
|  |  | ||||||
|  |     private final Integer type; | ||||||
|  |     private final String name; | ||||||
|  |  | ||||||
|  |     public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PayTransferTypeEnum::getType).toArray(); | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public int[] array() { | ||||||
|  |         return ARRAYS; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static PayTransferTypeEnum valueOf(Integer type) { | ||||||
|  |         return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -8,6 +8,8 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | |||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; | import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; | import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | ||||||
| @ -174,4 +176,8 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> { | |||||||
|         throw new IllegalStateException(String.format("支付退款单[%s] 状态不正确", outRefundNo)); |         throw new IllegalStateException(String.format("支付退款单[%s] 状态不正确", outRefundNo)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { | ||||||
|  |         throw new UnsupportedOperationException("待实现"); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 jason
					jason