feat 分账订单和分账操作

This commit is contained in:
bootx
2024-04-06 23:11:32 +08:00
parent e9b62c17f6
commit a92d54dfb7
26 changed files with 323 additions and 34 deletions

View File

@@ -1,19 +1,33 @@
2.0.5:
2.0.5: 分账初版
- [ ] 资金流水优化
- [x] 支付通道配置是否支持分账
- [ ] 分账接收方管理
- [x] 分账接收方管理
- [x] 管理
- [x] 同步
- [ ] 分账组管理
- [ ] 管理
- [ ] 绑定
- [x] 分账组管理
- [x] 管理
- [x] 绑定
- [ ] 分账订单管理
- [ ] 统计报表功能
- [ ] 订单创建
- [ ] 发起分账
- [ ] 订单完结
- [ ] 分账处理
- [ ] 发起分账
- [ ] 分账回调处理
- [ ] 分账情况查询, 分账结果/剩余可分账金额
- [ ]
- [x] 修复创建支付订单报错时, 订单保存数据不完整
2.0.6: 分账完善
- [ ] 支持分账组分账和自己传接收方进行分账
- [ ] DEMO增加获取微信OpenID和支付宝OpenId功能
- [ ] 分账接收方管理提供接口调用
- [ ] 分账组管理提供接口调用
- [ ] 对账回退及退款
2.0.x 版本内容
- [ ] 三方支付外部订单号规则优化: 支付P、退款R、分账A根据环境加前缀DEV_、DEMO_、PRE_
- [ ] 统计报表功能
- [ ] 微信新增V3版本接口
- [ ] 付款码支付自动路由到V2接口
- [ ] 增加各类日志记录,例如钱包的各项操作

View File

@@ -41,6 +41,9 @@ public class SimplePayParam extends DaxPayRequest<PayOrderModel> {
/** 过期时间 */
private Long expiredTime;
/** 是否开启分账 */
private boolean allocation;
/** 用户付款中途退出返回商户网站的地址(部分支付场景中可用) */
private String quitUrl;

View File

@@ -0,0 +1,55 @@
package cn.bootx.platform.daxpay.sdk.payment;
import cn.bootx.platform.daxpay.sdk.code.PayChannelEnum;
import cn.bootx.platform.daxpay.sdk.code.PayWayEnum;
import cn.bootx.platform.daxpay.sdk.code.SignTypeEnum;
import cn.bootx.platform.daxpay.sdk.model.pay.PayOrderModel;
import cn.bootx.platform.daxpay.sdk.net.DaxPayConfig;
import cn.bootx.platform.daxpay.sdk.net.DaxPayKit;
import cn.bootx.platform.daxpay.sdk.param.pay.SimplePayParam;
import cn.bootx.platform.daxpay.sdk.response.DaxPayResult;
import cn.hutool.core.util.RandomUtil;
import org.junit.Before;
import org.junit.Test;
/**
* 支付分账测试
* @author xxm
* @since 2024/4/6
*/
public class PayAllocationTest {
@Before
public void init() {
// 初始化支付配置
DaxPayConfig config = DaxPayConfig.builder()
.serviceUrl("http://127.0.0.1:9000")
.signSecret("123456")
.signType(SignTypeEnum.HMAC_SHA256)
.build();
DaxPayKit.initConfig(config);
}
/**
* 异步通道测试
*/
@Test
public void simplePay() {
// 简单支付参数
SimplePayParam param = new SimplePayParam();
param.setBusinessNo("P"+ RandomUtil.randomNumbers(5));
param.setAmount(10);
param.setTitle("测试分账支付");
param.setChannel(PayChannelEnum.ALI.getCode());
param.setPayWay(PayWayEnum.QRCODE.getCode());
param.setClientIp("127.0.0.1");
param.setNotNotify(true);
param.setAllocation(true);
DaxPayResult<PayOrderModel> execute = DaxPayKit.execute(param);
System.out.println(execute);
PayOrderModel data = execute.getData();
System.out.println(data);
}
}

View File

@@ -8,9 +8,9 @@ import cn.bootx.platform.common.core.util.ValidationUtil;
import cn.bootx.platform.daxpay.service.core.payment.allocation.service.AllocationGroupService;
import cn.bootx.platform.daxpay.service.dto.allocation.AllocationGroupDto;
import cn.bootx.platform.daxpay.service.dto.allocation.AllocationGroupReceiverResult;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationGroupBindParam;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationGroupParam;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationGroupUnbindParam;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationGroupBindParam;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationGroupParam;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationGroupUnbindParam;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

View File

@@ -7,8 +7,8 @@ import cn.bootx.platform.common.core.rest.dto.LabelValue;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.daxpay.service.core.payment.allocation.service.AllocationReceiverService;
import cn.bootx.platform.daxpay.service.dto.allocation.AllocationReceiverDto;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationReceiverParam;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationReceiverQuery;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationReceiverParam;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationReceiverQuery;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

View File

@@ -0,0 +1,32 @@
package cn.bootx.platform.daxpay.param.pay;
import cn.bootx.platform.daxpay.param.PaymentCommonParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
/**
* 分账请求参数
* @author xxm
* @since 2024/4/6
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "分账请求参数")
public class AllocationParam extends PaymentCommonParam {
@Schema(description = "支付单ID")
private Long paymentId;
@Schema(description = "业务号")
private String businessNo;
@Schema(description = "分账组ID")
@NotNull(message = "分账组ID不可为空")
private Long allocationGroupId;
}

View File

@@ -40,6 +40,9 @@ public class SimplePayParam extends PaymentCommonParam {
@Schema(description = "支付描述")
private String description;
@Schema(description = "是否开启分账")
private boolean allocation;
@Schema(description = "过期时间")
@JsonDeserialize(using = TimestampToLocalDateTimeDeserializer.class)
private LocalDateTime expiredTime;

View File

@@ -42,7 +42,7 @@ public class PayUtil {
return;
}
// 只有异步支付方式支持分账
if (!isNotSync(payParam.getPayChannels())) {
if (isNotSync(payParam.getPayChannels())) {
throw new PayFailureException("分账只支持包含异步支付通道的订单");
}
}

View File

@@ -4,6 +4,7 @@ import cn.bootx.platform.common.core.annotation.IgnoreAuth;
import cn.bootx.platform.common.core.exception.BizException;
import cn.bootx.platform.common.core.rest.Res;
import cn.bootx.platform.common.core.rest.ResResult;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayAllocationService;
import cn.hutool.core.thread.ThreadUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
@@ -27,6 +28,7 @@ import java.util.Objects;
@RequiredArgsConstructor
public class TestController {
private final LockTemplate lockTemplate;
private final AliPayAllocationService allocationService;
@Operation(summary = "锁测试1")
@GetMapping("/lock1")
@@ -50,11 +52,13 @@ public class TestController {
return Res.ok(name);
}
@IgnoreAuth
@Operation(summary = "微信回调测试")
@GetMapping(value = {"/wxcs/","wxcs"})
public String wxcs(){
return "ok";
@Operation(summary = "支付宝分账")
@GetMapping("/ali")
public ResResult<String> ali(){
allocationService.allocation(null,null);
return Res.ok();
}

View File

@@ -0,0 +1,66 @@
package cn.bootx.platform.daxpay.service.core.channel.alipay.service;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.AllocationOrder;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.AllocationOrderDetail;
import cn.hutool.core.util.IdUtil;
import com.alipay.api.domain.AlipayTradeOrderSettleModel;
import com.alipay.api.domain.OpenApiRoyaltyDetailInfoPojo;
import com.alipay.api.response.AlipayTradeOrderSettleResponse;
import com.ijpay.alipay.AliPayApi;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
/**
* 支付宝分账服务
* @author xxm
* @since 2024/4/6
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AliPayAllocationService {
private final AliPayConfigService aliPayConfigService;
/**
* 发起分账
*/
@SneakyThrows
public void allocation(AllocationOrder allocationOrder, List<AllocationOrderDetail> orderDetails){
aliPayConfigService.initConfig(aliPayConfigService.getConfig());
AlipayTradeOrderSettleModel model = new AlipayTradeOrderSettleModel();
// model.setOutRequestNo(String.valueOf(allocationOrder.getId()));
// model.setTradeNo(allocationOrder.getGatewayOrderNo());
model.setOutRequestNo(IdUtil.getSnowflakeNextIdStr());
model.setTradeNo("");
// model.setRoyaltyMode("async");
OpenApiRoyaltyDetailInfoPojo detailInfoPojo = new OpenApiRoyaltyDetailInfoPojo();
detailInfoPojo.setTransIn("");
detailInfoPojo.setAmount("0.01");
List<OpenApiRoyaltyDetailInfoPojo> royaltyParameters = Collections.singletonList(detailInfoPojo);
model.setRoyaltyParameters(royaltyParameters);
AlipayTradeOrderSettleResponse response = AliPayApi.tradeOrderSettleToResponse(model);
System.out.println(response);
}
/**
* 分账完结
*/
public void x2(){
}
}

View File

@@ -0,0 +1,38 @@
package cn.bootx.platform.daxpay.service.core.order.allocation.entity;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
import cn.bootx.table.modify.annotation.DbColumn;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 分账订单
* @author xxm
* @since 2024/4/6
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class AllocationOrder extends MpBaseEntity {
/**
* 支付订单ID
*/
@DbColumn(comment = "支付订单ID")
private Long paymentId;
/**
* 网关订单号
*/
@DbColumn(comment = "网关订单号")
private String gatewayOrderNo;
/**
* 总分账金额
*/
@DbColumn(comment = "总分账金额")
private Integer amount;
}

View File

@@ -0,0 +1,19 @@
package cn.bootx.platform.daxpay.service.core.order.allocation.entity;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 分账订单明细
* @author xxm
* @since 2024/4/6
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class AllocationOrderDetail extends MpBaseEntity {
}

View File

@@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.service.core.payment.allocation.convert;
import cn.bootx.platform.daxpay.service.core.payment.allocation.entity.AllocationGroup;
import cn.bootx.platform.daxpay.service.dto.allocation.AllocationGroupDto;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationGroupParam;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationGroupParam;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

View File

@@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.service.core.payment.allocation.convert;
import cn.bootx.platform.daxpay.service.core.payment.allocation.entity.AllocationGroupReceiver;
import cn.bootx.platform.daxpay.service.dto.allocation.AllocationGroupReceiverResult;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationGroupReceiverParam;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationGroupReceiverParam;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

View File

@@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.service.core.payment.allocation.convert;
import cn.bootx.platform.daxpay.service.core.payment.allocation.entity.AllocationReceiver;
import cn.bootx.platform.daxpay.service.dto.allocation.AllocationReceiverDto;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationReceiverParam;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationReceiverParam;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

View File

@@ -5,7 +5,7 @@ import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.common.query.generator.QueryGenerator;
import cn.bootx.platform.daxpay.service.core.payment.allocation.entity.AllocationGroup;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationGroupParam;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationGroupParam;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;

View File

@@ -5,7 +5,7 @@ import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.common.query.generator.QueryGenerator;
import cn.bootx.platform.daxpay.service.core.payment.allocation.entity.AllocationReceiver;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationReceiverQuery;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationReceiverQuery;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;

View File

@@ -16,10 +16,10 @@ import cn.bootx.platform.daxpay.service.core.payment.allocation.entity.Allocatio
import cn.bootx.platform.daxpay.service.core.payment.allocation.entity.AllocationReceiver;
import cn.bootx.platform.daxpay.service.dto.allocation.AllocationGroupDto;
import cn.bootx.platform.daxpay.service.dto.allocation.AllocationGroupReceiverResult;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationGroupBindParam;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationGroupParam;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationGroupReceiverParam;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationGroupUnbindParam;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationGroupBindParam;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationGroupParam;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationGroupReceiverParam;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationGroupUnbindParam;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import lombok.RequiredArgsConstructor;

View File

@@ -16,8 +16,8 @@ import cn.bootx.platform.daxpay.service.core.payment.allocation.entity.Allocatio
import cn.bootx.platform.daxpay.service.core.payment.allocation.factory.AllocationReceiverFactory;
import cn.bootx.platform.daxpay.service.dto.allocation.AllocationReceiverDto;
import cn.bootx.platform.daxpay.service.func.AbsAllocationReceiverStrategy;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationReceiverParam;
import cn.bootx.platform.daxpay.service.param.allocation.AllocationReceiverQuery;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationReceiverParam;
import cn.bootx.platform.daxpay.service.param.allocation.group.AllocationReceiverQuery;
import cn.hutool.core.bean.BeanUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -157,7 +157,7 @@ public class AllocationReceiverService {
*/
public void removeByGateway(Long id){
if (groupReceiverManager.isUsed(id)){
throw new PayFailureException("该接收方已被使用,无法删除");
throw new PayFailureException("该接收方已被使用无法从第三方支付平台中删除");
}
AllocationReceiver receiver = manager.findById(id).orElseThrow(() -> new PayFailureException("未找到分账接收方"));

View File

@@ -0,0 +1,55 @@
package cn.bootx.platform.daxpay.service.core.payment.allocation.service;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.daxpay.param.pay.AllocationParam;
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.payment.allocation.dao.AllocationGroupManager;
import cn.bootx.platform.daxpay.service.core.payment.allocation.entity.AllocationGroup;
import cn.bootx.platform.daxpay.service.dto.allocation.AllocationGroupReceiverResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
/**
* 分账服务
* @author xxm
* @since 2024/4/6
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AllocationService {
private final PayOrderManager payOrderManager;
private final AllocationGroupManager groupManager;
private final AllocationGroupService allocationGroupService;
/**
* 开启分账
*/
@Transactional(rollbackFor = Exception.class)
public void allocation(AllocationParam param) {
// 查询支付单
PayOrder payOrder = null;
if (Objects.nonNull(param.getPaymentId())){
payOrder = payOrderManager.findById(param.getPaymentId())
.orElseThrow(() -> new DataNotExistException("未查询到支付订单"));
}
if (Objects.isNull(payOrder)){
payOrder = payOrderManager.findByBusinessNo(param.getBusinessNo())
.orElseThrow(() -> new DataNotExistException("未查询到支付订单"));
}
AllocationGroup allocationGroup = groupManager.findById(param.getAllocationGroupId())
.orElseThrow(() -> new DataNotExistException("未查询到分账组"));
List<AllocationGroupReceiverResult> receiversByGroups = allocationGroupService.findReceiversByGroups(param.getAllocationGroupId());
}
}

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.service.param.allocation;
package cn.bootx.platform.daxpay.service.param.allocation.group;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.service.param.allocation;
package cn.bootx.platform.daxpay.service.param.allocation.group;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.service.param.allocation;
package cn.bootx.platform.daxpay.service.param.allocation.group;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.service.param.allocation;
package cn.bootx.platform.daxpay.service.param.allocation.group;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.service.param.allocation;
package cn.bootx.platform.daxpay.service.param.allocation.group;
import cn.bootx.platform.daxpay.code.AllocationReceiverTypeEnum;
import cn.bootx.platform.daxpay.code.AllocationRelationTypeEnum;

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.service.param.allocation;
package cn.bootx.platform.daxpay.service.param.allocation.group;
import cn.bootx.platform.common.core.annotation.QueryParam;
import cn.bootx.platform.daxpay.code.AllocationRelationTypeEnum;