feat 分账功能开发

This commit is contained in:
xxm1995
2024-04-07 19:04:13 +08:00
parent a92d54dfb7
commit 6825ce4cfe
37 changed files with 1027 additions and 69 deletions

View File

@@ -0,0 +1,29 @@
package cn.bootx.platform.daxpay.service.code;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 分账状态枚举
* @author xxm
* @since 2024/4/7
*/
@Getter
@AllArgsConstructor
public enum AllocationStatusEnum {
// 待分账
WAITING("waiting", "待分账"),
PARTIAL_PROCESSING("partial_processing", "分账处理中"),
PARTIAL_SUCCESS("partial_success", "部分分账完成"),
FINISH_PROCESSING("finish_processing", "分账完结处理中"),
FINISH_SUCCESS("finish_success", "分账完成"),
PARTIAL_FAILED("partial_failed", "部分分账完成"),
FINISH_FAILED("finish_failed", "分账失败"),
CLOSED("closed", "分账关闭"),
UNKNOWN("unknown", "分账状态未知");
final String code;
final String name;
}

View File

@@ -2,9 +2,9 @@ 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.domain.SettleExtendParams;
import com.alipay.api.response.AlipayTradeOrderSettleResponse;
import com.ijpay.alipay.AliPayApi;
import lombok.RequiredArgsConstructor;
@@ -12,8 +12,8 @@ import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* 支付宝分账服务
@@ -25,32 +25,27 @@ import java.util.List;
@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.getOutReqNo()));
model.setTradeNo(allocationOrder.getGatewayPayOrderNo());
model.setRoyaltyMode("async");
// 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);
// 分账子参数
List<OpenApiRoyaltyDetailInfoPojo> royaltyParameters = orderDetails.stream()
.map(o -> {
OpenApiRoyaltyDetailInfoPojo infoPojo = new OpenApiRoyaltyDetailInfoPojo();
infoPojo.setAmount(String.valueOf(o.getAmount() / 100.0));
infoPojo.setTransIn(o.getReceiverAccount());
return infoPojo;
})
.collect(Collectors.toList());
model.setRoyaltyParameters(royaltyParameters);
AlipayTradeOrderSettleResponse response = AliPayApi.tradeOrderSettleToResponse(model);
@@ -60,7 +55,18 @@ public class AliPayAllocationService {
/**
* 分账完结
*/
public void x2(){
@SneakyThrows
public void finish(AllocationOrder allocationOrder){
// 分账主体参数
AlipayTradeOrderSettleModel model = new AlipayTradeOrderSettleModel();
model.setOutRequestNo(String.valueOf(allocationOrder.getOutReqNo()));
model.setTradeNo(allocationOrder.getGatewayPayOrderNo());
// 分账完结参数
SettleExtendParams extendParams = new SettleExtendParams();
extendParams.setRoyaltyFinish("true");
model.setExtendParams(extendParams);
AlipayTradeOrderSettleResponse response = AliPayApi.tradeOrderSettleToResponse(model);
System.out.println(response);
}
}

View File

@@ -0,0 +1,106 @@
package cn.bootx.platform.daxpay.service.core.channel.wechat.service;
import cn.bootx.platform.daxpay.code.AllocationReceiverTypeEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.code.WeChatPayCode;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig;
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.codec.Base64;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.model.ProfitSharingModel;
import com.ijpay.wxpay.model.ReceiverModel;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 微信分账服务
* @author xxm
* @since 2024/4/7
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatPayAllocationService {
/**
* 发起分账
*/
@SneakyThrows
public void allocation(AllocationOrder allocationOrder, List<AllocationOrderDetail> orderDetails, WeChatPayConfig config){
List<ReceiverModel> list = orderDetails.stream().map(o->{
AllocationReceiverTypeEnum receiverTypeEnum = AllocationReceiverTypeEnum.findByCode(o.getReceiverType());
return ReceiverModel.builder()
.type(receiverTypeEnum.getOutCode())
.account(o.getReceiverAccount())
.amount(o.getAmount())
.description(allocationOrder.getDescription())
.build();
}).collect(Collectors.toList());
Map<String, String> params = ProfitSharingModel.builder()
.mch_id(config.getWxMchId())
.appid(config.getWxAppId())
.nonce_str(WxPayKit.generateStr())
.transaction_id(allocationOrder.getGatewayPayOrderNo())
.out_order_no(WxPayKit.generateStr())
.receivers(JSON.toJSONString(list))
.build()
.createSign(config.getApiKeyV2(), SignType.HMACSHA256);
byte[] fileBytes = Base64.decode(config.getP12());
ByteArrayInputStream inputStream = new ByteArrayInputStream(fileBytes);
String xmlResult = WxPayApi.multiProfitSharing(params, inputStream, config.getWxMchId());
Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
this.verifyErrorMsg(result);
}
/**
* 完成分账
*/
public void finish(AllocationOrder allocationOrder, WeChatPayConfig config){
Map<String, String> params = ProfitSharingModel.builder()
.mch_id(config.getWxMchId())
.appid(config.getWxAppId())
.nonce_str(WxPayKit.generateStr())
.transaction_id(allocationOrder.getGatewayPayOrderNo())
.out_order_no(allocationOrder.getOutReqNo())
.description("分账完成")
.build()
.createSign(config.getApiKeyV2(), SignType.HMACSHA256);
byte[] fileBytes = Base64.decode(config.getP12());
ByteArrayInputStream inputStream = new ByteArrayInputStream(fileBytes);
String xmlResult = WxPayApi.profitSharingFinish(params, inputStream, config.getWxMchId());
Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
this.verifyErrorMsg(result);
}
/**
* 验证错误信息
*/
private void verifyErrorMsg(Map<String, String> result) {
String returnCode = result.get(WeChatPayCode.RETURN_CODE);
String resultCode = result.get(WeChatPayCode.RESULT_CODE);
if (!WxPayKit.codeIsOk(returnCode) || !WxPayKit.codeIsOk(resultCode)) {
String errorMsg = result.get(WeChatPayCode.ERR_CODE_DES);
if (StrUtil.isBlank(errorMsg)) {
errorMsg = result.get(WeChatPayCode.RETURN_MSG);
}
log.error("分账操作失败 {}", errorMsg);
throw new PayFailureException(errorMsg);
}
}
}

View File

@@ -0,0 +1,24 @@
package cn.bootx.platform.daxpay.service.core.order.allocation.convert;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.AllocationOrder;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.AllocationOrderDetail;
import cn.bootx.platform.daxpay.service.dto.order.allocation.AllocationOrderDetailDto;
import cn.bootx.platform.daxpay.service.dto.order.allocation.AllocationOrderDto;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 分账实体类转换
* @author xxm
* @since 2024/4/7
*/
@Mapper
public interface AllocationConvert {
AllocationConvert CONVERT = Mappers.getMapper(AllocationConvert.class);
AllocationOrderDto convert(AllocationOrder in);
AllocationOrderDetailDto convert(AllocationOrderDetail in);
}

View File

@@ -0,0 +1,18 @@
package cn.bootx.platform.daxpay.service.core.order.allocation.dao;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.AllocationOrderDetail;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
/**
*
* @author xxm
* @since 2024/4/7
*/
@Slf4j
@Repository
@RequiredArgsConstructor
public class AllocationOrderDetailManager extends BaseManager<AllocationOrderDetailMapper, AllocationOrderDetail> {
}

View File

@@ -0,0 +1,14 @@
package cn.bootx.platform.daxpay.service.core.order.allocation.dao;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.AllocationOrderDetail;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
*
* @author xxm
* @since 2024/4/7
*/
@Mapper
public interface AllocationOrderDetailMapper extends BaseMapper<AllocationOrderDetail> {
}

View File

@@ -0,0 +1,27 @@
package cn.bootx.platform.daxpay.service.core.order.allocation.dao;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.AllocationOrder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
*
* @author xxm
* @since 2024/4/7
*/
@Slf4j
@Repository
@RequiredArgsConstructor
public class AllocationOrderManager extends BaseManager<AllocationOrderMapper, AllocationOrder> {
/**
* 根据分账单号查询
*/
public Optional<AllocationOrder> findByAllocationNo(String allocationNo){
return findByField(AllocationOrder::getAllocationNo, allocationNo);
}
}

View File

@@ -0,0 +1,14 @@
package cn.bootx.platform.daxpay.service.core.order.allocation.dao;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.AllocationOrder;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
*
* @author xxm
* @since 2024/4/7
*/
@Mapper
public interface AllocationOrderMapper extends BaseMapper<AllocationOrder> {
}

View File

@@ -1,11 +1,20 @@
package cn.bootx.platform.daxpay.service.core.order.allocation.entity;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
import cn.bootx.platform.daxpay.service.core.order.allocation.convert.AllocationConvert;
import cn.bootx.platform.daxpay.service.dto.order.allocation.AllocationOrderDto;
import cn.bootx.table.modify.annotation.DbColumn;
import cn.bootx.table.modify.mysql.annotation.DbMySqlIndex;
import cn.bootx.table.modify.mysql.constants.MySqlIndexType;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 分账订单
* @author xxm
@@ -14,7 +23,7 @@ import lombok.experimental.Accessors;
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class AllocationOrder extends MpBaseEntity {
public class AllocationOrder extends MpBaseEntity implements EntityBaseFunction<AllocationOrderDto> {
/**
* 支付订单ID
@@ -23,11 +32,36 @@ public class AllocationOrder extends MpBaseEntity {
private Long paymentId;
/**
* 网关订单号
* 网关支付订单号
*/
@DbColumn(comment = "网关订单号")
private String gatewayOrderNo;
@DbColumn(comment = "网关支付订单号")
private String gatewayPayOrderNo;
/**
* 网关分账单号
*/
@DbColumn(comment = "网关分账单号")
private String gatewayAllocationNo;
/**
* 分账单号
*/
@DbMySqlIndex(comment = "分账单号索引", type = MySqlIndexType.UNIQUE)
@DbColumn(comment = "分账单号")
private String allocationNo;
/**
* 外部请求号
*/
@DbColumn(comment = "外部请求号")
private String outReqNo;
/**
* 所属通道
*/
@DbColumn(comment = "所属通道")
private String channel;
/**
* 总分账金额
@@ -35,4 +69,36 @@ public class AllocationOrder extends MpBaseEntity {
@DbColumn(comment = "总分账金额")
private Integer amount;
/**
* 分账描述
*/
@DbColumn(comment = "分账描述")
private String description;
/**
* 状态
*/
@DbColumn(comment = "状态")
private String status;
/**
* 错误原因
*/
@DbColumn(comment = "错误原因")
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String errorMsg;
/**
* 完成时间
*/
@DbColumn(comment = "完成时间")
private LocalDateTime finishTime;
/**
* 转换
*/
@Override
public AllocationOrderDto toDto() {
return AllocationConvert.CONVERT.convert(this);
}
}

View File

@@ -1,6 +1,12 @@
package cn.bootx.platform.daxpay.service.core.order.allocation.entity;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
import cn.bootx.platform.daxpay.code.AllocationReceiverTypeEnum;
import cn.bootx.platform.daxpay.service.core.order.allocation.convert.AllocationConvert;
import cn.bootx.platform.daxpay.service.dto.order.allocation.AllocationOrderDetailDto;
import cn.bootx.platform.starter.data.perm.sensitive.SensitiveInfo;
import cn.bootx.table.modify.annotation.DbColumn;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@@ -13,7 +19,49 @@ import lombok.experimental.Accessors;
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class AllocationOrderDetail extends MpBaseEntity {
public class AllocationOrderDetail extends MpBaseEntity implements EntityBaseFunction<AllocationOrderDetailDto> {
/** 分账订单ID */
@DbColumn(comment = "分账订单ID")
private Long orderId;
/** 接收者ID */
@DbColumn(comment = "接收者ID")
private Long receiverId;
/** 分账比例 */
@DbColumn(comment = "分账比例(万分之多少)")
private Integer rate;
/** 分账金额 */
@DbColumn(comment = "分账金额")
private Integer amount;
/**
* 分账接收方类型
* @see AllocationReceiverTypeEnum
*/
@DbColumn(comment = "分账接收方类型")
private String receiverType;
/** 接收方账号 */
@DbColumn(comment = "接收方账号")
@SensitiveInfo
private String receiverAccount;
/** 接收方姓名 */
@DbColumn(comment = "接收方姓名")
private String receiverName;
/** 状态 */
@DbColumn(comment = "状态")
private String status;
/**
* 转换
*/
@Override
public AllocationOrderDetailDto toDto() {
return AllocationConvert.CONVERT.convert(this);
}
}

View File

@@ -0,0 +1,21 @@
package cn.bootx.platform.daxpay.service.core.order.allocation.entity;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 分账订单和明细
* @author xxm
* @since 2024/4/7
*/
@Data
@Accessors(chain = true)
public class OrderAndDetail {
/** 分账订单 */
private AllocationOrder order;
/** 分账订单明细 */
private List<AllocationOrderDetail> details;
}

View File

@@ -0,0 +1,96 @@
package cn.bootx.platform.daxpay.service.core.order.allocation.service;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.daxpay.param.pay.allocation.AllocationStartParam;
import cn.bootx.platform.daxpay.service.code.AllocationStatusEnum;
import cn.bootx.platform.daxpay.service.core.order.allocation.dao.AllocationOrderDetailManager;
import cn.bootx.platform.daxpay.service.core.order.allocation.dao.AllocationOrderManager;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.AllocationOrder;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.AllocationOrderDetail;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.OrderAndDetail;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.dto.allocation.AllocationGroupReceiverResult;
import cn.bootx.platform.daxpay.service.param.order.AllocationOrderQuery;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
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.stream.Collectors;
/**
* 分账订单服务
* @author xxm
* @since 2024/4/7
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AllocationOrderService {
private final AllocationOrderManager allocationOrderManager;
private final AllocationOrderDetailManager allocationOrderDetailManager;
/**
* 分页查询分账订单
*/
public void page(PageParam pageParam, AllocationOrderQuery param){
}
/**
* 生成分账订单
*/
@Transactional(rollbackFor = Exception.class)
public OrderAndDetail create(AllocationStartParam param, PayOrder payOrder, int orderAmount, List<AllocationGroupReceiverResult> receiversByGroups){
long orderId = IdUtil.getSnowflakeNextId();
// 请求号不存在使用订单ID
String allocationNo = param.getAllocationNo();
if (StrUtil.isBlank(allocationNo)){
allocationNo = String.valueOf(orderId);
}
// 订单明细
List<AllocationOrderDetail> details = receiversByGroups.stream()
.map(o -> {
// 计算分账金额, 小数不分直接舍弃, 防止分账金额超过上限
Integer rate = o.getRate();
Integer amount = orderAmount * rate / 10000;
AllocationOrderDetail detail = new AllocationOrderDetail();
detail.setOrderId(orderId)
.setReceiverId(o.getId())
.setStatus(AllocationStatusEnum.WAITING.getCode())
.setAmount(amount)
.setRate(rate)
.setReceiverType(o.getReceiverType())
.setReceiverName(o.getReceiverName())
.setReceiverAccount(o.getReceiverAccount());
return detail;
})
.collect(Collectors.toList());
// 求分账的总额
Integer sumAmount = details.stream()
.map(AllocationOrderDetail::getAmount)
.reduce(0, Integer::sum);
// 分账订单
AllocationOrder allocationOrder = new AllocationOrder()
.setPaymentId(payOrder.getId())
.setAllocationNo(allocationNo)
.setChannel(payOrder.getAsyncChannel())
.setGatewayPayOrderNo(payOrder.getGatewayOrderNo())
.setOutReqNo(String.valueOf(orderId))
.setDescription(param.getDescription())
.setStatus(AllocationStatusEnum.WAITING.getCode())
.setAmount(sumAmount);
allocationOrder.setId(orderId);
// 保存
allocationOrderDetailManager.saveAll(details);
allocationOrderManager.save(allocationOrder);
return new OrderAndDetail().setOrder(allocationOrder).setDetails(details);
}
}

View File

@@ -12,6 +12,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
*
* @author xxm
@@ -27,4 +29,21 @@ public class AllocationGroupManager extends BaseManager<AllocationGroupMapper,Al
QueryWrapper<AllocationGroup> generator = QueryGenerator.generator(query);
return page(mpPage,generator);
}
/**
* 清除默认分账组
*/
public void clearDefault(String channel) {
lambdaUpdate()
.set(AllocationGroup::isDefaultGroup,false)
.eq(AllocationGroup::getChannel,channel)
.update();
}
/**
* 获取默认分账组
*/
public Optional<AllocationGroup> findDefaultGroup(String asyncChannel) {
return findByField(AllocationGroup::getChannel,asyncChannel);
}
}

View File

@@ -33,6 +33,9 @@ public class AllocationGroup extends MpBaseEntity implements EntityBaseFunction<
@TableField(updateStrategy = FieldStrategy.NEVER)
private String channel;
@DbColumn(comment = "默认分账组")
private boolean defaultGroup;
@DbColumn(comment = "总分账比例(万分之多少)")
private Integer totalRate;

View File

@@ -0,0 +1,37 @@
package cn.bootx.platform.daxpay.service.core.payment.allocation.factory;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;
import cn.bootx.platform.daxpay.service.func.AbsAllocationStrategy;
import cn.hutool.extra.spring.SpringUtil;
import lombok.experimental.UtilityClass;
/**
* 分账策略工厂
* @author xxm
* @since 2024/4/7
*/
@UtilityClass
public class AllocationFactory {
/**
* 根据传入的支付通道创建策略
* @return 支付策略
*/
public static AbsAllocationStrategy create(String channel) {
PayChannelEnum channelEnum = PayChannelEnum.findByCode(channel);
AbsAllocationStrategy strategy;
switch (channelEnum) {
case ALI:
strategy = SpringUtil.getBean(AbsAllocationStrategy.class);
break;
case WECHAT:
strategy = SpringUtil.getBean(AbsAllocationStrategy.class);
break;
default:
throw new PayUnsupportedMethodException();
}
return strategy;
}
}

View File

@@ -100,6 +100,19 @@ public class AllocationGroupService {
groupManager.save(group);
}
/**
* 设置默认分账组
*/
@Transactional(rollbackFor = Exception.class)
public void setDefault(Long id){
// 分账组
AllocationGroup group = groupManager.findById(id)
.orElseThrow(() -> new DataNotExistException("未找到分账组"));
groupManager.clearDefault(group.getChannel());
group.setDefaultGroup(true);
groupManager.updateById(group);
}
/**
* 更新分账组
*/

View File

@@ -0,0 +1,16 @@
package cn.bootx.platform.daxpay.service.core.payment.allocation.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 分账订单同步服务
* @author xxm
* @since 2024/4/7
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AllocationOrderSyncService {
}

View File

@@ -1,16 +1,28 @@
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.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.pay.allocation.AllocationFinishParam;
import cn.bootx.platform.daxpay.param.pay.allocation.AllocationStartParam;
import cn.bootx.platform.daxpay.result.allocation.AllocationResult;
import cn.bootx.platform.daxpay.service.code.AllocationStatusEnum;
import cn.bootx.platform.daxpay.service.core.order.allocation.dao.AllocationOrderManager;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.AllocationOrder;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.AllocationOrderDetail;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.OrderAndDetail;
import cn.bootx.platform.daxpay.service.core.order.allocation.service.AllocationOrderService;
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManager;
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayChannelOrder;
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.core.payment.allocation.factory.AllocationFactory;
import cn.bootx.platform.daxpay.service.dto.allocation.AllocationGroupReceiverResult;
import cn.bootx.platform.daxpay.service.func.AbsAllocationStrategy;
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;
@@ -27,15 +39,21 @@ public class AllocationService {
private final PayOrderManager payOrderManager;
private final PayChannelOrderManager payChannelOrderManager;
private final AllocationGroupManager groupManager;
private final AllocationOrderManager allocationOrderManager;
private final AllocationGroupService allocationGroupService;
private final AllocationOrderService allocationOrderService;
/**
* 开启分账
* 开启分账, 使用分账组进行分账
*/
@Transactional(rollbackFor = Exception.class)
public void allocation(AllocationParam param) {
public AllocationResult allocation(AllocationStartParam param) {
// 查询支付单
PayOrder payOrder = null;
if (Objects.nonNull(param.getPaymentId())){
@@ -46,10 +64,84 @@ public class AllocationService {
payOrder = payOrderManager.findByBusinessNo(param.getBusinessNo())
.orElseThrow(() -> new DataNotExistException("未查询到支付订单"));
}
AllocationGroup allocationGroup = groupManager.findById(param.getAllocationGroupId())
.orElseThrow(() -> new DataNotExistException("未查询到分账组"));
List<AllocationGroupReceiverResult> receiversByGroups = allocationGroupService.findReceiversByGroups(param.getAllocationGroupId());
// 判断订单是否可以分账
if (!payOrder.isAllocation()){
throw new PayFailureException("该订单不允许分账");
}
// 查询待分账的通道支付订单
PayChannelOrder channelOrder = payChannelOrderManager.findByAsyncChannel(payOrder.getId())
.orElseThrow(() -> new DataNotExistException("未查询到支付通道订单"));
// 查询分账组 未传输使用默认该通道默认分账组
AllocationGroup allocationGroup;
if (Objects.nonNull(param.getAllocationGroupId())) {
allocationGroup = groupManager.findById(param.getAllocationGroupId()).orElseThrow(() -> new DataNotExistException("未查询到分账组"));
} else {
allocationGroup = groupManager.findDefaultGroup(payOrder.getAsyncChannel()).orElseThrow(() -> new DataNotExistException("未查询到默认分账组"));
}
List<AllocationGroupReceiverResult> receiversByGroups = allocationGroupService.findReceiversByGroups(allocationGroup.getId());
// 创建分账单和明细并保存, 使用事务
OrderAndDetail orderAndDetail = allocationOrderService.create(param ,payOrder, channelOrder.getAmount(), receiversByGroups);
// 创建分账策略并初始化
AbsAllocationStrategy allocationStrategy = AllocationFactory.create(payOrder.getAsyncChannel());
AllocationOrder order = orderAndDetail.getOrder();
List<AllocationOrderDetail> details = orderAndDetail.getDetails();
allocationStrategy.initParam(order, details);
// 分账预处理
allocationStrategy.doBeforeHandler();
try {
// 分账处理
allocationStrategy.allocation();
// 执行中
order.setStatus(AllocationStatusEnum.PARTIAL_PROCESSING.getCode())
.setErrorMsg(null);
} catch (Exception e) {
// 失败
order.setStatus(AllocationStatusEnum.PARTIAL_FAILED.getCode())
.setErrorMsg(e.getMessage());
}
allocationOrderManager.updateById(order);
return new AllocationResult().setOrderId(order.getId())
.setAllocationNo(order.getAllocationNo())
.setStatus(order.getStatus());
}
/**
* 分账完结
*/
public void finish(AllocationFinishParam param) {
AllocationOrder allocationOrder;
if (Objects.nonNull(param.getOrderId())){
allocationOrder = allocationOrderManager.findById(param.getOrderId())
.orElseThrow(() -> new DataNotExistException("未查询到分账单信息"));
} else {
allocationOrder = allocationOrderManager.findByAllocationNo(param.getAllocationNo())
.orElseThrow(() -> new DataNotExistException("未查询到分账单信息"));
}
// 创建分账策略并初始化
AbsAllocationStrategy allocationStrategy = AllocationFactory.create(allocationOrder.getChannel());
allocationStrategy.initParam(allocationOrder, null);
// 分账完结预处理
allocationStrategy.doBeforeHandler();
try {
// 分账处理
allocationStrategy.allocation();
// 执行中
allocationOrder.setStatus(AllocationStatusEnum.FINISH_PROCESSING.getCode())
.setErrorMsg(null);
} catch (Exception e) {
// 失败
allocationOrder.setStatus(AllocationStatusEnum.FINISH_FAILED.getCode())
.setErrorMsg(e.getMessage());
}
allocationOrderManager.updateById(allocationOrder);
}
}

View File

@@ -1,10 +1,18 @@
package cn.bootx.platform.daxpay.service.core.payment.allocation.strategy.allocation;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.core.channel.alipay.entity.AliPayConfig;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayAllocationService;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayConfigService;
import cn.bootx.platform.daxpay.service.func.AbsAllocationStrategy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import java.util.Objects;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
/**
@@ -16,5 +24,50 @@ import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROT
@Service
@Scope(SCOPE_PROTOTYPE)
@RequiredArgsConstructor
public class AliPayAllocationStrategy {
public class AliPayAllocationStrategy extends AbsAllocationStrategy {
private final AliPayAllocationService aliPayAllocationService;
private final AliPayConfigService aliPayConfigService;
private AliPayConfig aliPayConfig;;
/**
* 策略标识
*/
@Override
public PayChannelEnum getChannel() {
return PayChannelEnum.ALI;
}
/**
* 操作前处理, 校验和初始化支付配置
*/
@Override
public void doBeforeHandler() {
this.aliPayConfig = aliPayConfigService.getConfig();
// 判断是否支持分账
if (Objects.equals(aliPayConfig.getAllocation(),false)){
throw new PayFailureException("微信支付配置不支持分账");
}
aliPayConfigService.initConfig(this.aliPayConfig);
}
/**
* 分账操作
*/
@Override
public void allocation() {
aliPayAllocationService.allocation(this.getAllocationOrder(), this.getAllocationOrderDetails());
}
/**
* 分账完结
*/
@Override
public void finish() {
aliPayAllocationService.finish(this.getAllocationOrder());
}
}

View File

@@ -1,10 +1,18 @@
package cn.bootx.platform.daxpay.service.core.payment.allocation.strategy.allocation;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPayAllocationService;
import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPayConfigService;
import cn.bootx.platform.daxpay.service.func.AbsAllocationStrategy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import java.util.Objects;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
/**
@@ -16,5 +24,48 @@ import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROT
@Service
@Scope(SCOPE_PROTOTYPE)
@RequiredArgsConstructor
public class WeChatPayAllocationStrategy {
public class WeChatPayAllocationStrategy extends AbsAllocationStrategy {
private final WeChatPayAllocationService weChatPayAllocationService;
private final WeChatPayConfigService weChatPayConfigService;
private WeChatPayConfig weChatPayConfig;
/**
* 策略标识
*/
@Override
public PayChannelEnum getChannel() {
return PayChannelEnum.WECHAT;
}
/**
* 操作前处理, 校验和初始化支付配置
*/
@Override
public void doBeforeHandler() {
weChatPayConfig = weChatPayConfigService.getAndCheckConfig();
// 判断是否支持分账
if (Objects.equals(weChatPayConfig.getAllocation(),false)){
throw new PayFailureException("微信支付配置不支持分账");
}
}
/**
* 分账操作
*/
@Override
public void allocation() {
weChatPayAllocationService.allocation(getAllocationOrder(), getAllocationOrderDetails(), weChatPayConfig);
}
/**
* 分账完结
*/
@Override
public void finish() {
weChatPayAllocationService.finish(getAllocationOrder(), weChatPayConfig);
}
}

View File

@@ -1,6 +1,7 @@
package cn.bootx.platform.daxpay.service.dto.allocation;
import cn.bootx.platform.common.core.rest.dto.BaseDto;
import cn.bootx.table.modify.annotation.DbColumn;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -23,6 +24,9 @@ public class AllocationGroupDto extends BaseDto {
@Schema(description = "通道")
private String channel;
@DbColumn(comment = "默认分账组")
private Boolean defaultGroup;
@Schema(description = "总分账比例(万分之多少)")
private Integer totalRate;

View File

@@ -0,0 +1,19 @@
package cn.bootx.platform.daxpay.service.dto.order.allocation;
import cn.bootx.platform.common.core.rest.dto.BaseDto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 分账订单详情
* @author xxm
* @since 2024/4/7
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "")
public class AllocationOrderDetailDto extends BaseDto {
}

View File

@@ -0,0 +1,21 @@
package cn.bootx.platform.daxpay.service.dto.order.allocation;
import cn.bootx.platform.common.core.rest.dto.BaseDto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 分账订单
* @author xxm
* @since 2024/4/7
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "分账订单")
public class AllocationOrderDto extends BaseDto {
}

View File

@@ -6,7 +6,7 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
/**
* 分账接收方管理抽象策略
* 分账抽象策略
* @author xxm
* @since 2024/4/1
*/
@@ -17,10 +17,13 @@ public abstract class AbsAllocationReceiverStrategy implements PayStrategy{
private AllocationReceiver allocationReceiver;
/**
* 操作前校验
*/
public abstract boolean validation();
/**
* 支付前处理, 校验和初始化支付配置
* 操作前处理, 校验和初始化支付配置
*/
public abstract void doBeforeHandler();

View File

@@ -0,0 +1,47 @@
package cn.bootx.platform.daxpay.service.func;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.AllocationOrder;
import cn.bootx.platform.daxpay.service.core.order.allocation.entity.AllocationOrderDetail;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
/**
* 分账接收方管理抽象策略
* @author xxm
* @since 2024/4/1
*/
@Slf4j
@Getter
@Setter
public abstract class AbsAllocationStrategy implements PayStrategy{
private AllocationOrder allocationOrder;
private List<AllocationOrderDetail> allocationOrderDetails;
/**
* 初始化参数
*/
public void initParam(AllocationOrder allocationOrder, List<AllocationOrderDetail> allocationOrderDetails) {
this.allocationOrder = allocationOrder;
this.allocationOrderDetails = allocationOrderDetails;
}
/**
* 操作前处理, 校验和初始化支付配置
*/
public abstract void doBeforeHandler();
/**
* 分账操作
*/
public abstract void allocation();
/**
* 分账完结
*/
public abstract void finish();
}

View File

@@ -11,7 +11,7 @@ import lombok.Getter;
import java.util.Objects;
/**
* 支付退款修复策略, 只异步支付订单的退款才有修复选项
* 支付退款修复策略, 只异步支付订单的退款才有修复选项
* @author xxm
* @since 2024/1/25
*/

View File

@@ -14,7 +14,7 @@ import lombok.Setter;
import java.time.LocalDateTime;
/**
* 抽象支付退款策略基类
* 抽象支付退款策略
*
* @author xxm
* @since 2020/12/11

View File

@@ -0,0 +1,18 @@
package cn.bootx.platform.daxpay.service.param.order;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 分账订单查询参数
* @author xxm
* @since 2024/4/7
*/
@Data
@Accessors(chain = true)
@Schema(title = "分账订单查询参数")
public class AllocationOrderQuery {
}