分账功能

This commit is contained in:
zengtao01
2024-07-19 09:42:18 +08:00
parent 2f8a2ae032
commit de023eb5e3
50 changed files with 2227 additions and 8 deletions

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.framework.pay.core.client;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideRespDto;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideUnifiedDto;
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;
@ -95,4 +97,32 @@ public interface PayClient {
* @return 转账信息
*/
PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type);
/**
* 发起分账
*
* @param reqDTO 分账参数
* @return 分账
*/
PayDivideRespDto unifiedDivide(PayDivideUnifiedDto reqDTO);
/**
* 分账结果查询
*
* @param reqDTO 分账参数
* @return 分账
*/
PayDivideRespDto unifiedDivideResult(PayDivideUnifiedDto reqDTO);
/**
* 解冻分账剩余资金
*
* @param reqDTO 分账参数
* @return 分账
*/
PayDivideRespDto unifiedDivideFreeze(PayDivideUnifiedDto reqDTO);
}

View File

@ -0,0 +1,57 @@
package cn.iocoder.yudao.framework.pay.core.client.dto.divide;
import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
import com.github.binarywang.wxpay.bean.profitsharing.result.ProfitSharingV3Result;
import lombok.Data;
import java.util.List;
/**
* @author zt
* @description <description class purpose>
* @since 2024/7/17
*/
@Data
public class PayDivideRespDto {
private String subMchId;
private String transactionId;
private String outOrderNo;
private String orderId;
private String state;
private List<ProfitSharingV3Result.Receiver> receivers;
/**
* 调用渠道的错误码
*
* 注意:这里返回的是业务异常,而是不系统异常。
* 如果是系统异常,则会抛出 {@link PayException}
*/
private String channelErrorCode;
/**
* 调用渠道报错时,错误信息
*/
private String channelErrorMsg;
/**
* 原始的异步通知结果
*/
private Object rawData;
/**
* 创建【FAILURE】状态的退款返回
*/
public static PayDivideRespDto failureOf(String channelErrorCode, String channelErrorMsg,
String transactionId, Object rawData) {
PayDivideRespDto respDTO = new PayDivideRespDto();
//respDTO.status = PayRefundStatusRespEnum.FAILURE.getStatus();
respDTO.channelErrorCode = channelErrorCode;
respDTO.channelErrorMsg = channelErrorMsg;
// 相对通用的字段
respDTO.transactionId = transactionId;
respDTO.rawData = rawData;
return respDTO;
}
}

View File

@ -0,0 +1,41 @@
package cn.iocoder.yudao.framework.pay.core.client.dto.divide;
import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingV3Request;
import lombok.Data;
import java.util.List;
/**
* @author zt
* @description <description class purpose>
* @since 2024/7/17
*/
@Data
public class PayDivideUnifiedDto {
/**
* 应用ID
*/
private String appid;
/**
* 微信订单号
*/
private String transactionId;
/**
* 商户分账单号
*/
private String outOrderNo;
/**
* 是否解冻剩余未分资金
*/
private boolean unfreezeUnsplit;
/**
* 分账接收方列表
*/
private List<ProfitSharingV3Request.Receiver> receivers;
}

View File

@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideRespDto;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideUnifiedDto;
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;
@ -238,6 +240,8 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
protected abstract PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type)
throws Throwable;
// ========== 各种工具方法 ==========
private PayException buildPayException(Throwable ex) {
@ -247,4 +251,66 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
throw new PayException(ex);
}
@Override
public PayDivideRespDto unifiedDivide(PayDivideUnifiedDto reqDTO) {
ValidationUtils.validate(reqDTO);
// 执行统一下单
PayDivideRespDto resp;
try {
resp = doUnifiedDivide(reqDTO);
} catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
throw ex;
} catch (Throwable ex) {
// 系统异常,则包装成 PayException 异常抛出
log.error("[unifiedOrder][客户端({}) request({}) 发起支付异常]",
getId(), toJsonString(reqDTO), ex);
throw buildPayException(ex);
}
return resp;
}
protected abstract PayDivideRespDto doUnifiedDivide(PayDivideUnifiedDto reqDTO)
throws Throwable;
@Override
public PayDivideRespDto unifiedDivideResult(PayDivideUnifiedDto reqDTO) {
ValidationUtils.validate(reqDTO);
// 执行统一下单
PayDivideRespDto resp;
try {
resp = doUnifiedDivideResult(reqDTO);
} catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
throw ex;
} catch (Throwable ex) {
// 系统异常,则包装成 PayException 异常抛出
log.error("[unifiedOrder][客户端({}) request({}) 发起支付异常]",
getId(), toJsonString(reqDTO), ex);
throw buildPayException(ex);
}
return resp;
}
protected abstract PayDivideRespDto doUnifiedDivideResult(PayDivideUnifiedDto reqDTO)
throws Throwable;
@Override
public PayDivideRespDto unifiedDivideFreeze(PayDivideUnifiedDto reqDTO) {
ValidationUtils.validate(reqDTO);
// 执行统一下单
PayDivideRespDto resp;
try {
resp = dounifiedDivideFreeze(reqDTO);
} catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
throw ex;
} catch (Throwable ex) {
// 系统异常,则包装成 PayException 异常抛出
log.error("[unifiedOrder][客户端({}) request({}) 发起支付异常]",
getId(), toJsonString(reqDTO), ex);
throw buildPayException(ex);
}
return resp;
}
protected abstract PayDivideRespDto dounifiedDivideFreeze(PayDivideUnifiedDto reqDTO)
throws Throwable;
}

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideRespDto;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideUnifiedDto;
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.enums.channel.PayChannelEnum;
@ -56,4 +58,19 @@ public class AlipayAppPayClient extends AbstractAlipayPayClient {
return PayOrderRespDTO.waitingOf(displayMode, response.getBody(),
reqDTO.getOutTradeNo(), response);
}
@Override
protected PayDivideRespDto doUnifiedDivide(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
@Override
protected PayDivideRespDto doUnifiedDivideResult(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
@Override
protected PayDivideRespDto dounifiedDivideFreeze(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
}

View File

@ -3,6 +3,8 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideRespDto;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideUnifiedDto;
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.enums.channel.PayChannelEnum;
@ -82,4 +84,19 @@ public class AlipayBarPayClient extends AbstractAlipayPayClient {
return PayOrderRespDTO.waitingOf(displayMode, "",
reqDTO.getOutTradeNo(), response);
}
@Override
protected PayDivideRespDto doUnifiedDivide(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
@Override
protected PayDivideRespDto doUnifiedDivideResult(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
@Override
protected PayDivideRespDto dounifiedDivideFreeze(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
}

View File

@ -2,6 +2,8 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.Method;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideRespDto;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideUnifiedDto;
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.enums.channel.PayChannelEnum;
@ -66,4 +68,19 @@ public class AlipayPcPayClient extends AbstractAlipayPayClient {
return PayOrderRespDTO.waitingOf(displayMode, response.getBody(),
reqDTO.getOutTradeNo(), response);
}
@Override
protected PayDivideRespDto doUnifiedDivide(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
@Override
protected PayDivideRespDto doUnifiedDivideResult(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
@Override
protected PayDivideRespDto dounifiedDivideFreeze(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
}

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideRespDto;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideUnifiedDto;
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.enums.channel.PayChannelEnum;
@ -63,4 +65,19 @@ public class AlipayQrPayClient extends AbstractAlipayPayClient {
return PayOrderRespDTO.waitingOf(displayMode, response.getQrCode(),
reqDTO.getOutTradeNo(), response);
}
@Override
protected PayDivideRespDto doUnifiedDivide(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
@Override
protected PayDivideRespDto doUnifiedDivideResult(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
@Override
protected PayDivideRespDto dounifiedDivideFreeze(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
}

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.http.Method;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideRespDto;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideUnifiedDto;
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.enums.channel.PayChannelEnum;
@ -56,4 +58,19 @@ public class AlipayWapPayClient extends AbstractAlipayPayClient {
return PayOrderRespDTO.waitingOf(displayMode, response.getBody(),
reqDTO.getOutTradeNo(), response);
}
@Override
protected PayDivideRespDto doUnifiedDivide(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
@Override
protected PayDivideRespDto doUnifiedDivideResult(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
@Override
protected PayDivideRespDto dounifiedDivideFreeze(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
}

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.mock;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideRespDto;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideUnifiedDto;
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;
@ -77,4 +79,19 @@ public class MockPayClient extends AbstractPayClient<NonePayClientConfig> {
throw new UnsupportedOperationException("待实现");
}
@Override
protected PayDivideRespDto doUnifiedDivide(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
@Override
protected PayDivideRespDto doUnifiedDivideResult(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
@Override
protected PayDivideRespDto dounifiedDivideFreeze(PayDivideUnifiedDto reqDTO) throws Throwable {
return null;
}
}

View File

@ -8,6 +8,8 @@ import cn.hutool.core.date.TemporalAccessorUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideRespDto;
import cn.iocoder.yudao.framework.pay.core.client.dto.divide.PayDivideUnifiedDto;
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;
@ -21,8 +23,24 @@ import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
import com.github.binarywang.wxpay.bean.request.*;
import com.github.binarywang.wxpay.bean.result.*;
import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingUnfreezeV3Request;
import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingV3Request;
import com.github.binarywang.wxpay.bean.profitsharing.result.ProfitSharingUnfreezeV3Result;
import com.github.binarywang.wxpay.bean.profitsharing.result.ProfitSharingV3Result;
import com.github.binarywang.wxpay.bean.request.WxPayOrderQueryRequest;
import com.github.binarywang.wxpay.bean.request.WxPayOrderQueryV3Request;
import com.github.binarywang.wxpay.bean.request.WxPayRefundQueryRequest;
import com.github.binarywang.wxpay.bean.request.WxPayRefundQueryV3Request;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryResult;
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayRefundQueryResult;
import com.github.binarywang.wxpay.bean.result.WxPayRefundQueryV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
@ -481,4 +499,115 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
return e.getReturnMsg();
}
@Override
protected PayDivideRespDto doUnifiedDivide(PayDivideUnifiedDto reqDTO) throws Throwable {
try {
switch (config.getApiVersion()) {
case API_VERSION_V2:
return doUnifiedDivideV2(reqDTO);
case WxPayClientConfig.API_VERSION_V3:
return doUnifiedDivideV3(reqDTO);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
} catch (WxPayException e) {
String errorCode = getErrorCode(e);
String errorMessage = getErrorMessage(e);
return PayDivideRespDto.failureOf(errorCode, errorMessage,
reqDTO.getTransactionId(), e.getXmlString());
}
}
@Override
protected PayDivideRespDto doUnifiedDivideResult(PayDivideUnifiedDto reqDTO) throws Throwable {
try {
switch (config.getApiVersion()) {
case API_VERSION_V2:
return doUnifiedDivideResultV2(reqDTO);
case WxPayClientConfig.API_VERSION_V3:
return doUnifiedDivideResultV3(reqDTO);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
} catch (WxPayException e) {
String errorCode = getErrorCode(e);
String errorMessage = getErrorMessage(e);
return PayDivideRespDto.failureOf(errorCode, errorMessage,
reqDTO.getTransactionId(), e.getXmlString());
}
}
private PayDivideRespDto doUnifiedDivideV2(PayDivideUnifiedDto reqDTO) throws Throwable {
throw new UnsupportedOperationException("待实现");
}
private PayDivideRespDto doUnifiedDivideV3(PayDivideUnifiedDto reqDTO) throws Throwable {
// 1. 构建 WxPayRefundRequest 请求
ProfitSharingV3Request request = new ProfitSharingV3Request();
request.setAppid(config.getAppId());
request.setTransactionId(reqDTO.getTransactionId());
request.setUnfreezeUnsplit(reqDTO.isUnfreezeUnsplit());
request.setOutOrderNo(reqDTO.getOutOrderNo());
request.setReceivers(reqDTO.getReceivers());
// 2.1 执行请求
ProfitSharingV3Result response = client.getProfitSharingService().profitSharingV3(request);
PayDivideRespDto payDivideRespDto = new PayDivideRespDto();
BeanUtil.copyProperties(response,payDivideRespDto);
payDivideRespDto.setReceivers(response.getReceivers());
return payDivideRespDto;
}
private PayDivideRespDto doUnifiedDivideResultV2(PayDivideUnifiedDto reqDTO) throws Throwable {
throw new UnsupportedOperationException("待实现");
}
private PayDivideRespDto doUnifiedDivideResultV3(PayDivideUnifiedDto reqDTO) throws Throwable {
// 1. 构建 WxPayRefundRequest 请求
//ProfitSharingQueryV3Request request = new ProfitSharingQueryV3Request();
//request.setTransactionId(reqDTO.getTransactionId());
//request.setOutOrderNo(reqDTO.getOutOrderNo());
// 2.1 执行请求
ProfitSharingV3Result response = client.getProfitSharingService().profitSharingQueryV3(reqDTO.getOutOrderNo(),reqDTO.getTransactionId());
PayDivideRespDto payDivideRespDto = new PayDivideRespDto();
BeanUtil.copyProperties(response,payDivideRespDto);
payDivideRespDto.setReceivers(response.getReceivers());
return payDivideRespDto;
}
@Override
protected PayDivideRespDto dounifiedDivideFreeze(PayDivideUnifiedDto reqDTO) throws Throwable {
try {
switch (config.getApiVersion()) {
case API_VERSION_V2:
return doUnifiedDivideFreezeV2(reqDTO);
case WxPayClientConfig.API_VERSION_V3:
return doUnifiedDivideFreezeV3(reqDTO);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
} catch (WxPayException e) {
String errorCode = getErrorCode(e);
String errorMessage = getErrorMessage(e);
return PayDivideRespDto.failureOf(errorCode, errorMessage,
reqDTO.getTransactionId(), e.getXmlString());
}
}
private PayDivideRespDto doUnifiedDivideFreezeV2(PayDivideUnifiedDto reqDTO) throws Throwable {
throw new UnsupportedOperationException("待实现");
}
private PayDivideRespDto doUnifiedDivideFreezeV3(PayDivideUnifiedDto reqDTO) throws Throwable {
// 1. 构建 WxPayRefundRequest 请求
ProfitSharingUnfreezeV3Request request = new ProfitSharingUnfreezeV3Request();
request.setTransactionId(reqDTO.getTransactionId());
request.setOutOrderNo(reqDTO.getOutOrderNo());
request.setDescription("解冻全部剩余资金");
// 2.1 执行请求
ProfitSharingUnfreezeV3Result response = client.getProfitSharingService().profitSharingUnfreeze(request);
PayDivideRespDto payDivideRespDto = new PayDivideRespDto();
BeanUtil.copyProperties(response,payDivideRespDto);
return payDivideRespDto;
}
}

View File

@ -59,6 +59,8 @@ public class WxPubPayClient extends AbstractWxPayClient {
// 构建 WxPayUnifiedOrderRequest 对象
WxPayUnifiedOrderV3Request request = buildPayUnifiedOrderRequestV3(reqDTO)
.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
//添加分账
request.setSettleInfo(new WxPayUnifiedOrderV3Request.SettleInfo().setProfitSharing(true));
// 执行请求
WxPayUnifiedOrderV3Result.JsapiResult response = client.createOrderV3(TradeTypeEnum.JSAPI, request);

View File

@ -0,0 +1,24 @@
package cn.iocoder.yudao.framework.pay.core.enums.divide;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 渠道的退款状态枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum PayDivideStatusRespEnum {
WAITING("0", "等待分账"),
PROGRESS("10", "分账进行中"),
COMPLETE("20", "分账完成"),
FINISH("30", "分账结束"),
;
private final String status;
private final String name;
}