feat(allocation): 实现分账接口并重构相关代码

- 新增分账请求、完结、查询接口
- 重构分账相关实体、DTO、Mapper等类- 更新数据库表结构,适应新的分账流程
- 优化代码组织,提高可维护性
This commit is contained in:
DaxPay
2024-11-15 15:53:29 +08:00
parent 2c94841e4f
commit 5bec6513f0
43 changed files with 1172 additions and 198 deletions

View File

@@ -5,12 +5,17 @@
- [ ] 分账功能 - [ ] 分账功能
- [x] 分账接收方配置 - [x] 分账接收方配置
- [ ] 分账单管理 - [ ] 分账单管理
- [ ] 分账接口开发
- [ ] 网关配套移动端开发 - [ ] 网关配套移动端开发
- [ ] 同步回调页 - [ ] 同步回调页
- [ ] 商户应用增加启停用状态
- [ ] 商户应用应该要有一个类似删除的功能, 实现停用冻结, 但不影响数据的关联
- [x] 微信通道添加单独的认证跳转地址, 处理它的特殊情况 - [x] 微信通道添加单独的认证跳转地址, 处理它的特殊情况
## bugs
- [ ] 修复 BigDecimal 类型数据序列化和签名异常问题
## 任务池 ## 任务池
- [ ] 定时同步任务频次不要太高, 预防产生过多的数据 - [ ] 定时同步任务频次不要太高, 预防产生过多的数据
- [ ] 退款支持以撤销的方式进行退款 - [ ] 退款支持以撤销的方式进行退款
- [ ] 商户应用增加启停用状态
- [ ] 商户应用应该要有一个类似删除的功能, 实现停用冻结, 但不影响数据的关联

View File

@@ -17,7 +17,6 @@ import lombok.experimental.UtilityClass;
public class JsonUtil { public class JsonUtil {
private final JSONConfig JSON_CONFIG = JSONConfig.create() private final JSONConfig JSON_CONFIG = JSONConfig.create()
.setDateFormat(DatePattern.NORM_DATETIME_PATTERN); .setDateFormat(DatePattern.NORM_DATETIME_PATTERN);
// .setStripTrailingZeros(false);
/** /**
* 转换为实体 * 转换为实体

View File

@@ -10,7 +10,7 @@ import lombok.Getter;
*/ */
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum AllocOrderResultEnum { public enum AllocTransactionResultEnum {
ALL_PENDING("all_pending", "处理中"), ALL_PENDING("all_pending", "处理中"),
ALL_SUCCESS("all_success", "全部成功"), ALL_SUCCESS("all_success", "全部成功"),

View File

@@ -10,13 +10,14 @@ import lombok.Getter;
*/ */
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum AllocOrderStatusEnum { public enum AllocTransactionStatusEnum {
ALLOC_PROCESSING("alloc_processing", "分账处理中"), ALLOC_PROCESSING("alloc_processing", "分账处理中"),
ALLOC_END("alloc_end", "分账完成"), ALLOC_END("alloc_end", "分账完成"),
ALLOC_FAILED("alloc_failed", "分账失败"), ALLOC_FAILED("alloc_failed", "分账失败"),
FINISH("finish", "完结"), FINISH("finish", "完结"),
FINISH_FAILED("finish_failed", "完结失败"), FINISH_FAILED("finish_failed", "完结失败"),
IGNORE("ignore", "忽略"),
; ;
final String code; final String code;

View File

@@ -0,0 +1,23 @@
package org.dromara.daxpay.core.param.allocation.transaction;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.daxpay.core.param.PaymentCommonParam;
/**
* 分账完结参数
* @author xxm
* @since 2024/4/7
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Schema(title = "分账请求参数")
public class AllocFinishParam extends PaymentCommonParam {
@Schema(description = "商户分账单号")
private String bizAllocNo;
@Schema(description = "分账单号")
private String allocNo;
}

View File

@@ -0,0 +1,67 @@
package org.dromara.daxpay.core.param.allocation.transaction;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.dromara.daxpay.core.param.PaymentCommonParam;
import java.util.List;
/**
* 分账请求参数
* @author xxm
* @since 2024/11/14
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "分账请求参数")
public class AllocationParam extends PaymentCommonParam {
/** 商户分账单号 */
@NotBlank(message = "商户分账单号不可为空")
@Size(max = 100, message = "商户分账单号不可超过100位")
@Schema(description = "商户分账单号")
private String bizAllocNo;
/** 支付订单号 */
@Size(max = 32, message = "支付订单号不可超过32位")
@Schema(description = "支付订单号")
private String orderNo;
/** 商户支付订单号 */
@Size(max = 100, message = "商户支付订单号不可超过100位")
@Schema(description = "商户支付订单号")
private String bizOrderNo;
/** 分账描述 */
@Size(max = 150, message = "分账描述不可超过150位")
@Schema(description = "分账描述")
private String description;
/**
* 优先级 分账接收方列表 > 分账组编号 > 默认分账组
*/
@Size(max = 20, message = "分账组编号不可超过20位")
@Schema(description = "分账组编号")
private String groupNo;
/** 分账接收方列表 */
@Schema(description = "分账接收方列表")
@Valid
private List<ReceiverParam> receivers;
/** 回调通知地址 */
@Size(max = 200, message = "回调通知地址不可超过200位")
@Schema(description = "回调通知地址")
private String notifyUrl;
/** 商户扩展参数,回调时会原样返回 */
@Size(max = 500, message = "商户扩展参数不可超过500位")
@Schema(description = "商户扩展参数")
private String attach;
}

View File

@@ -0,0 +1,23 @@
package org.dromara.daxpay.core.param.allocation.transaction;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.daxpay.core.param.PaymentCommonParam;
/**
* 分账订单查询参数
* @author xxm
* @since 2024/4/7
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Schema(title = "支付订单查询参数")
public class QueryAllocTransactionParam extends PaymentCommonParam {
@Schema(description = "分账单号")
private String allocNo;
@Schema(description = "商户分账单号")
private String bizAllocNo;
}

View File

@@ -0,0 +1,34 @@
package org.dromara.daxpay.core.param.allocation.transaction;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
/**
* 分账接收方列表
* @author xxm
* @since 2024/5/21
*/
@Data
@Accessors(chain = true)
@Schema(title = "分账接收方列表")
public class ReceiverParam {
/** 分账接收方编号 */
@Schema(description = "分账接收方编号")
@NotBlank(message = "分账接收方编号必填")
@Size(max = 32, message = "分账接收方编号不可超过32位")
private String receiverNo;
/** 分账金额 */
@Schema(description = "分账金额")
@NotNull(message = "分账金额必填")
@Min(value = 1,message = "分账金额至少为0.01元")
private BigDecimal amount;
}

View File

@@ -2,10 +2,8 @@ package org.dromara.daxpay.core.result.allocation.receiver;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.dromara.daxpay.core.enums.ChannelEnum; import org.dromara.daxpay.core.enums.ChannelEnum;
import org.dromara.daxpay.core.result.MchAppResult;
import java.util.List; import java.util.List;
@@ -14,11 +12,10 @@ import java.util.List;
* @author xxm * @author xxm
* @since 2024/3/28 * @since 2024/3/28
*/ */
@EqualsAndHashCode(callSuper = true)
@Data @Data
@Accessors(chain = true) @Accessors(chain = true)
@Schema(title = "分账接收方") @Schema(title = "分账接收方")
public class AllocReceiverResult extends MchAppResult { public class AllocReceiverResult {
@Schema(description = "接收方列表") @Schema(description = "接收方列表")
private List<Receiver> receivers; private List<Receiver> receivers;

View File

@@ -0,0 +1,65 @@
package org.dromara.daxpay.core.result.allocation.transaction;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.daxpay.core.enums.AllocDetailResultEnum;
import org.dromara.daxpay.core.enums.AllocReceiverTypeEnum;
import java.time.LocalDateTime;
/**
* 分账订单明细
* @author xxm
* @since 2024/5/21
*/
@Data
@Accessors(chain = true)
@Schema(title = "分账订单明细")
public class AllocDetailResult {
@Schema(description = "分账接收方编号")
private String receiverNo;
/** 分账金额 */
@Schema(description = "分账金额")
private Integer amount;
/** 分账比例 */
@Schema(description = "分账比例(万分之多少)")
private Integer rate;
/**
* 分账接收方类型
* @see AllocReceiverTypeEnum
*/
@Schema(description = "分账接收方类型")
private String receiverType;
/** 接收方账号 */
@Schema(description = "接收方账号")
private String receiverAccount;
/** 接收方姓名 */
@Schema(description = "接收方姓名")
private String receiverName;
/**
* 分账结果
* @see AllocDetailResultEnum
*/
@Schema(description = "分账结果")
private String result;
/** 错误代码 */
@Schema(description = "错误代码")
private String errorCode;
/** 错误原因 */
@Schema(description = "错误原因")
private String errorMsg;
/** 分账完成时间 */
@Schema(description = "分账完成时间")
private LocalDateTime finishTime;
}

View File

@@ -0,0 +1,31 @@
package org.dromara.daxpay.core.result.allocation.transaction;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.daxpay.core.enums.AllocTransactionStatusEnum;
/**
* 分账请求结果
* @author xxm
* @since 2024/4/7
*/
@Data
@Accessors(chain = true)
@Schema(title = "分账请求结果")
public class AllocResult {
/** 分账订单号 */
@Schema(description = "分账订单号")
private String allocNo;
/** 商户分账订单号 */
@Schema(description = "商户分账订单号")
private String bizAllocNo;
/**
* 分账状态
* @see AllocTransactionStatusEnum
*/
@Schema(description = "分账状态")
private String status;
}

View File

@@ -0,0 +1,108 @@
package org.dromara.daxpay.core.result.allocation.transaction;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.daxpay.core.enums.AllocTransactionResultEnum;
import org.dromara.daxpay.core.enums.AllocTransactionStatusEnum;
import org.dromara.daxpay.core.enums.ChannelEnum;
import java.time.LocalDateTime;
import java.util.List;
/**
* 分账订单返回结果
* @author xxm
* @since 2024/5/21
*/
@Data
@Accessors(chain = true)
@Schema(title = "分账订单返回结果")
public class AllocTransactionResult {
/** 分账单号 */
@Schema(description = "分账单号")
private String allocNo;
/** 商户分账单号 */
@Schema(description = "商户分账单号")
private String bizAllocNo;
/** 通道分账号 */
@Schema(description = "通道分账号")
private String outAllocNo;
/**
* 支付订单号
*/
@Schema(description = "支付订单号")
private String orderNo;
/** 商户支付订单号 */
@Schema(description = "商户支付订单号")
private String bizOrderNo;
/** 通道支付订单号 */
@Schema(description = "通道支付订单号")
private String outOrderNo;
/**
* 支付订单标题
*/
@Schema(description = "支付订单标题")
private String title;
/**
* 所属通道
* @see ChannelEnum
*/
@Schema(description = "所属通道")
private String channel;
/**
* 总分账金额
*/
@Schema(description = "总分账金额")
private Integer amount;
/**
* 分账描述
*/
@Schema(description = "分账描述")
private String description;
/**
* 状态
* @see AllocTransactionStatusEnum
*/
@Schema(description = "状态")
private String status;
/**
* 处理结果
* @see AllocTransactionResultEnum
*/
@Schema(description = "处理结果")
private String result;
/**
* 错误码
*/
@Schema(description = "错误码")
private String errorCode;
/**
* 错误信息
*/
@Schema(description = "错误原因")
private String errorMsg;
/** 分账订单完成时间 */
@Schema(description = "分账订单完成时间")
private LocalDateTime finishTime;
/** 分账明细 */
@Schema(description = "分账明细")
private List<AllocDetailResult> details;
}

View File

@@ -1,4 +1,4 @@
package org.dromara.daxpay.service.bo.allocation; package org.dromara.daxpay.service.bo.allocation.receiver;
import cn.bootx.platform.common.jackson.sensitive.SensitiveInfo; import cn.bootx.platform.common.jackson.sensitive.SensitiveInfo;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;

View File

@@ -1,4 +1,4 @@
package org.dromara.daxpay.service.bo.allocation; package org.dromara.daxpay.service.bo.allocation.receiver;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;

View File

@@ -1,4 +1,4 @@
package org.dromara.daxpay.service.bo.allocation; package org.dromara.daxpay.service.bo.allocation.receiver;
import cn.bootx.platform.common.jackson.sensitive.SensitiveInfo; import cn.bootx.platform.common.jackson.sensitive.SensitiveInfo;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;

View File

@@ -0,0 +1,17 @@
package org.dromara.daxpay.service.bo.allocation.receiver;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 分账操作结果
* @author xxm
* @since 2024/11/15
*/
@Data
@Accessors(chain = true)
public class AllocStartResultBo {
/** 通道分账号 */
private String outAllocNo;
}

View File

@@ -10,13 +10,13 @@ import cn.bootx.platform.core.util.ValidationUtil;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.dromara.daxpay.service.bo.allocation.AllocGroupResultBo; import org.dromara.daxpay.service.bo.allocation.receiver.AllocGroupResultBo;
import org.dromara.daxpay.service.param.allocation.group.AllocGroupBindParam; import org.dromara.daxpay.service.param.allocation.group.AllocGroupBindParam;
import org.dromara.daxpay.service.param.allocation.group.AllocGroupParam; import org.dromara.daxpay.service.param.allocation.group.AllocGroupParam;
import org.dromara.daxpay.service.param.allocation.group.AllocGroupQuery; import org.dromara.daxpay.service.param.allocation.group.AllocGroupQuery;
import org.dromara.daxpay.service.param.allocation.group.AllocGroupUnbindParam; import org.dromara.daxpay.service.param.allocation.group.AllocGroupUnbindParam;
import org.dromara.daxpay.service.bo.allocation.AllocGroupReceiverResultBo; import org.dromara.daxpay.service.bo.allocation.receiver.AllocGroupReceiverResultBo;
import org.dromara.daxpay.service.service.allocation.AllocGroupService; import org.dromara.daxpay.service.service.allocation.receiver.AllocGroupService;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;

View File

@@ -13,9 +13,9 @@ import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverAddParam; import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverAddParam;
import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverRemoveParam; import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverRemoveParam;
import org.dromara.daxpay.service.bo.allocation.AllocReceiverResultBo; import org.dromara.daxpay.service.bo.allocation.receiver.AllocReceiverResultBo;
import org.dromara.daxpay.service.param.allocation.receiver.AllocReceiverQuery; import org.dromara.daxpay.service.param.allocation.receiver.AllocReceiverQuery;
import org.dromara.daxpay.service.service.allocation.AllocReceiverService; import org.dromara.daxpay.service.service.allocation.receiver.AllocReceiverService;
import org.dromara.daxpay.service.service.assist.PaymentAssistService; import org.dromara.daxpay.service.service.assist.PaymentAssistService;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;

View File

@@ -7,11 +7,17 @@ import lombok.RequiredArgsConstructor;
import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverAddParam; import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverAddParam;
import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverQueryParam; import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverQueryParam;
import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverRemoveParam; import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverRemoveParam;
import org.dromara.daxpay.core.param.allocation.transaction.AllocFinishParam;
import org.dromara.daxpay.core.param.allocation.transaction.AllocationParam;
import org.dromara.daxpay.core.param.allocation.transaction.QueryAllocTransactionParam;
import org.dromara.daxpay.core.result.DaxResult; import org.dromara.daxpay.core.result.DaxResult;
import org.dromara.daxpay.core.result.allocation.receiver.AllocReceiverResult; import org.dromara.daxpay.core.result.allocation.receiver.AllocReceiverResult;
import org.dromara.daxpay.core.result.allocation.transaction.AllocResult;
import org.dromara.daxpay.core.result.allocation.transaction.AllocTransactionResult;
import org.dromara.daxpay.core.util.DaxRes; import org.dromara.daxpay.core.util.DaxRes;
import org.dromara.daxpay.service.common.anno.PaymentVerify; import org.dromara.daxpay.service.common.anno.PaymentVerify;
import org.dromara.daxpay.service.service.allocation.AllocReceiverService; import org.dromara.daxpay.service.service.allocation.AllocationService;
import org.dromara.daxpay.service.service.allocation.receiver.AllocReceiverService;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -30,17 +36,24 @@ import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor @RequiredArgsConstructor
public class UniAllocationController { public class UniAllocationController {
private final AllocReceiverService allocReceiverService; private final AllocReceiverService allocReceiverService;
private final AllocationService allocationService;
@Operation(summary = "发起分账接口") @Operation(summary = "发起分账接口")
@PostMapping("/start") @PostMapping("/start")
public DaxResult<Void> start(){ public DaxResult<AllocResult> start(@RequestBody AllocationParam param){
return DaxRes.ok(); return DaxRes.ok(allocationService.allocation(param));
} }
@Operation(summary = "分账完结接口") @Operation(summary = "分账完结接口")
@PostMapping("/finish") @PostMapping("/finish")
public DaxResult<Void> finish(){ public DaxResult<AllocResult> finish(AllocFinishParam param){
return DaxRes.ok(); return DaxRes.ok(allocationService.finish(param));
}
@Operation(summary = "分账写你查询接口")
@PostMapping("/query")
public DaxResult<AllocTransactionResult> query(@RequestBody QueryAllocTransactionParam param){
return DaxRes.ok(allocationService.queryAllocTransaction(param));
} }
@Operation(summary = "分账接收方查询接口") @Operation(summary = "分账接收方查询接口")

View File

@@ -2,7 +2,7 @@ package org.dromara.daxpay.service.convert.allocation;
import org.dromara.daxpay.service.entity.allocation.receiver.AllocGroup; import org.dromara.daxpay.service.entity.allocation.receiver.AllocGroup;
import org.dromara.daxpay.service.param.allocation.group.AllocGroupParam; import org.dromara.daxpay.service.param.allocation.group.AllocGroupParam;
import org.dromara.daxpay.service.bo.allocation.AllocGroupResultBo; import org.dromara.daxpay.service.bo.allocation.receiver.AllocGroupResultBo;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;

View File

@@ -1,6 +1,6 @@
package org.dromara.daxpay.service.convert.allocation; package org.dromara.daxpay.service.convert.allocation;
import org.dromara.daxpay.service.bo.allocation.AllocGroupReceiverResultBo; import org.dromara.daxpay.service.bo.allocation.receiver.AllocGroupReceiverResultBo;
import org.dromara.daxpay.service.entity.allocation.receiver.AllocGroupReceiver; import org.dromara.daxpay.service.entity.allocation.receiver.AllocGroupReceiver;
import org.dromara.daxpay.service.param.allocation.group.AllocGroupReceiverParam; import org.dromara.daxpay.service.param.allocation.group.AllocGroupReceiverParam;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;

View File

@@ -2,7 +2,7 @@ package org.dromara.daxpay.service.convert.allocation;
import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverAddParam; import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverAddParam;
import org.dromara.daxpay.core.result.allocation.receiver.AllocReceiverResult; import org.dromara.daxpay.core.result.allocation.receiver.AllocReceiverResult;
import org.dromara.daxpay.service.bo.allocation.AllocReceiverResultBo; import org.dromara.daxpay.service.bo.allocation.receiver.AllocReceiverResultBo;
import org.dromara.daxpay.service.entity.allocation.receiver.AllocReceiver; import org.dromara.daxpay.service.entity.allocation.receiver.AllocReceiver;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;

View File

@@ -0,0 +1,22 @@
package org.dromara.daxpay.service.convert.allocation;
import org.dromara.daxpay.core.result.allocation.transaction.AllocDetailResult;
import org.dromara.daxpay.core.result.allocation.transaction.AllocTransactionResult;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocTransaction;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
*
* @author xxm
* @since 2024/11/15
*/
@Mapper
public interface AllocTransactionConvert {
AllocTransactionConvert CONVERT = Mappers.getMapper(AllocTransactionConvert.class);
AllocTransactionResult toResult(AllocTransaction in);
AllocDetailResult toResult(AllocDetail in);
}

View File

@@ -0,0 +1,23 @@
package org.dromara.daxpay.service.dao.allocation.transaction;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 分账明细
* @author xxm
* @since 2024/11/14
*/
@Slf4j
@Repository
@RequiredArgsConstructor
public class AllocDetailManager extends BaseManager<AllocDetailMapper, AllocDetail> {
public List<AllocDetail> findAllByOrderId(Long id) {
return findAllByField(AllocDetail::getAllocationId, id);
}
}

View File

@@ -0,0 +1,14 @@
package org.dromara.daxpay.service.dao.allocation.transaction;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
/**
*
* @author xxm
* @since 2024/11/14
*/
@Mapper
public interface AllocDetailMapper extends MPJBaseMapper<AllocDetail> {
}

View File

@@ -0,0 +1,40 @@
package org.dromara.daxpay.service.dao.allocation.transaction;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.daxpay.service.common.entity.MchAppBaseEntity;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocTransaction;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* 分账交易
* @author xxm
* @since 2024/11/14
*/
@Slf4j
@Repository
@RequiredArgsConstructor
public class AllocTransactionManager extends BaseManager<AllocTransactionMapper, AllocTransaction> {
/**
* 根据商户分账单号查询数据
*/
public Optional<AllocTransaction> findByBizAllocNo(String bizAllocNo, String appId) {
return lambdaQuery()
.eq(AllocTransaction::getBizAllocNo, bizAllocNo)
.eq(MchAppBaseEntity::getAppId, appId)
.oneOpt();
}
/**
* 根据分账单号查询数据
*/
public Optional<AllocTransaction> findByAllocNo(String allocNo, String appId) {
return lambdaQuery()
.eq(AllocTransaction::getAllocNo, allocNo)
.eq(MchAppBaseEntity::getAppId, appId)
.oneOpt();
}
}

View File

@@ -0,0 +1,14 @@
package org.dromara.daxpay.service.dao.allocation.transaction;
import com.github.yulichang.base.MPJBaseMapper;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocTransaction;
import org.mapstruct.Mapper;
/**
*
* @author xxm
* @since 2024/11/14
*/
@Mapper
public interface AllocTransactionMapper extends MPJBaseMapper<AllocTransaction> {
}

View File

@@ -1,27 +0,0 @@
package org.dromara.daxpay.service.dao.order.allocation;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import org.dromara.daxpay.service.entity.order.allocation.AllocOrderDetail;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
*
* @author xxm
* @since 2024/4/7
*/
@Slf4j
@Repository
@RequiredArgsConstructor
public class AllocOrderDetailManager extends BaseManager<AllocOrderDetailMapper, AllocOrderDetail> {
/**
* 根据订单ID查询
*/
public List<AllocOrderDetail> findAllByOrderId(Long orderId) {
return findAllByField(AllocOrderDetail::getAllocId, orderId);
}
}

View File

@@ -1,14 +0,0 @@
package org.dromara.daxpay.service.dao.order.allocation;
import org.dromara.daxpay.service.entity.order.allocation.AllocOrderDetail;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
*
* @author xxm
* @since 2024/4/7
*/
@Mapper
public interface AllocOrderDetailMapper extends MPJBaseMapper<AllocOrderDetail> {
}

View File

@@ -1,61 +0,0 @@
package org.dromara.daxpay.service.dao.order.allocation;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.common.mybatisplus.query.generator.QueryGenerator;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.core.rest.param.PageParam;
import org.dromara.daxpay.core.enums.AllocOrderStatusEnum;
import org.dromara.daxpay.service.entity.order.allocation.AllocOrder;
import org.dromara.daxpay.service.param.order.allocation.AllocOrderQuery;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
*
* @author xxm
* @since 2024/4/7
*/
@Slf4j
@Repository
@RequiredArgsConstructor
public class AllocOrderManager extends BaseManager<AllocOrderMapper, AllocOrder> {
/**
* 根据分账单号查询
*/
public Optional<AllocOrder> findByAllocNo(String allocNo){
return findByField(AllocOrder::getAllocNo, allocNo);
}
/**
* 根据商户分账号查询
*/
public Optional<AllocOrder> findByBizAllocNo(String bizAllocNo){
return findByField(AllocOrder::getBizAllocNo, bizAllocNo);
}
/**
* 分页
*/
public Page<AllocOrder> page(PageParam pageParam, AllocOrderQuery param){
Page<AllocOrder> mpPage = MpUtil.getMpPage(pageParam);
QueryWrapper<AllocOrder> generator = QueryGenerator.generator(param);
return this.page(mpPage, generator);
}
/**
* 查询待同步的分账单
*/
public List<AllocOrder> findSyncOrder(){
List<String> statusList = List.of(AllocOrderStatusEnum.ALLOC_PROCESSING.getCode(), AllocOrderStatusEnum.ALLOC_END.getCode());
return lambdaQuery()
.in(AllocOrder::getStatus, statusList)
.list();
}
}

View File

@@ -1,14 +0,0 @@
package org.dromara.daxpay.service.dao.order.allocation;
import org.dromara.daxpay.service.entity.order.allocation.AllocOrder;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
*
* @author xxm
* @since 2024/4/7
*/
@Mapper
public interface AllocOrderMapper extends MPJBaseMapper<AllocOrder> {
}

View File

@@ -7,7 +7,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.dromara.daxpay.service.bo.allocation.AllocGroupResultBo; import org.dromara.daxpay.service.bo.allocation.receiver.AllocGroupResultBo;
import org.dromara.daxpay.service.common.entity.MchAppBaseEntity; import org.dromara.daxpay.service.common.entity.MchAppBaseEntity;
import org.dromara.daxpay.service.convert.allocation.AllocGroupConvert; import org.dromara.daxpay.service.convert.allocation.AllocGroupConvert;

View File

@@ -5,7 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.dromara.daxpay.service.bo.allocation.AllocGroupReceiverResultBo; import org.dromara.daxpay.service.bo.allocation.receiver.AllocGroupReceiverResultBo;
import org.dromara.daxpay.service.common.entity.MchAppBaseEntity; import org.dromara.daxpay.service.common.entity.MchAppBaseEntity;
import org.dromara.daxpay.service.convert.allocation.AllocGroupReceiverConvert; import org.dromara.daxpay.service.convert.allocation.AllocGroupReceiverConvert;

View File

@@ -12,7 +12,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.dromara.daxpay.service.convert.allocation.AllocReceiverConvert; import org.dromara.daxpay.service.convert.allocation.AllocReceiverConvert;
import org.dromara.daxpay.service.bo.allocation.AllocReceiverResultBo; import org.dromara.daxpay.service.bo.allocation.receiver.AllocReceiverResultBo;
/** /**
* 分账接收方 * 分账接收方

View File

@@ -1,29 +1,27 @@
package org.dromara.daxpay.service.entity.order.allocation; package org.dromara.daxpay.service.entity.allocation.transaction;
import org.dromara.daxpay.core.enums.AllocDetailResultEnum;
import org.dromara.daxpay.core.enums.AllocReceiverTypeEnum;
import org.dromara.daxpay.service.common.entity.MchAppBaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.dromara.daxpay.core.enums.AllocDetailResultEnum;
import org.dromara.daxpay.core.enums.AllocReceiverTypeEnum;
import org.dromara.daxpay.service.common.entity.MchAppBaseEntity;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
/** /**
* 分账订单明细 * 分账明细
* @author xxm * @author xxm
* @since 2024/6/1 * @since 2024/11/14
*/ */
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
@Accessors(chain = true) @Accessors(chain = true)
@TableName("pay_alloc_order_detail") public class AllocDetail extends MchAppBaseEntity {
public class AllocOrderDetail extends MchAppBaseEntity {
/** 分账订单ID */ /** 分账订单ID */
private Long allocId; private Long allocationId;
/** 接收者ID */ /** 接收者ID */
private Long receiverId; private Long receiverId;
@@ -31,7 +29,7 @@ public class AllocOrderDetail extends MchAppBaseEntity {
/** 分账接收方编号 */ /** 分账接收方编号 */
private String receiverNo; private String receiverNo;
/** 分账比例(百分之多少) */ /** 分账比例 */
private BigDecimal rate; private BigDecimal rate;
/** 分账金额 */ /** 分账金额 */

View File

@@ -1,66 +1,52 @@
package org.dromara.daxpay.service.entity.order.allocation; package org.dromara.daxpay.service.entity.allocation.transaction;
import org.dromara.daxpay.core.enums.AllocOrderResultEnum;
import org.dromara.daxpay.core.enums.AllocOrderStatusEnum;
import org.dromara.daxpay.core.enums.ChannelEnum;
import org.dromara.daxpay.service.common.entity.MchAppBaseEntity;
import com.baomidou.mybatisplus.annotation.FieldStrategy; import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.dromara.daxpay.core.enums.AllocTransactionResultEnum;
import org.dromara.daxpay.core.enums.AllocTransactionStatusEnum;
import org.dromara.daxpay.core.enums.ChannelEnum;
import org.dromara.daxpay.service.common.entity.MchAppBaseEntity;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
/** /**
* 分账订单 * 分账交易记录
* @author xxm * @author xxm
* @since 2024/6/1 * @since 2024/11/14
*/ */
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
@Accessors(chain = true) @Accessors(chain = true)
@TableName("pay_alloc_order") @TableName("pay_alloc_transaction")
public class AllocOrder extends MchAppBaseEntity { public class AllocTransaction extends MchAppBaseEntity {
/** /** 分账单号 */
* 分账单号
*/
private String allocNo; private String allocNo;
/** /** 商户分账单号 */
* 商户分账单号
*/
private String bizAllocNo; private String bizAllocNo;
/** /** 通道分账号 */
* 通道分账号
*/
private String outAllocNo; private String outAllocNo;
/** 支付订单ID */ /** 支付订单ID */
private Long orderId; private Long orderId;
/** /** 支付订单号 */
* 支付订单号
*/
private String orderNo; private String orderNo;
/** /** 商户支付订单号 */
* 商户支付订单号
*/
private String bizOrderNo; private String bizOrderNo;
/** /** 通道支付订单号 */
* 通道系统支付订单号
*/
private String outOrderNo; private String outOrderNo;
/** /** 支付标题 */
* 支付订单标题
*/
private String title; private String title;
/** /**
@@ -81,33 +67,35 @@ public class AllocOrder extends MchAppBaseEntity {
/** /**
* 状态 * 状态
* @see AllocOrderStatusEnum * @see AllocTransactionStatusEnum
*/ */
private String status; private String status;
/** /**
* 处理结果 * 处理结果
* @see AllocOrderResultEnum * @see AllocTransactionResultEnum
*/ */
private String result; private String result;
/** 分账完成时间 */ /** 分账完成时间 */
private LocalDateTime finishTime; private LocalDateTime finishTime;
/** 商户扩展参数,回调时会原样返回, 以最后一次为准 */ /** 异步通知地址 */
@TableField(updateStrategy = FieldStrategy.ALWAYS) @TableField(updateStrategy = FieldStrategy.ALWAYS)
private String notifyUrl;
/** 商户扩展参数,回调时会原样返回, 以最后一次为准 */
private String attach; private String attach;
/** 支付终端ip 以最后一次为准 */ /** 请求时间,时间戳转时间 */
@TableField(updateStrategy = FieldStrategy.ALWAYS) private LocalDateTime reqTime;
/** 终端ip */
private String clientIp; private String clientIp;
/** 错误码 */ /** 错误码 */
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String errorCode; private String errorCode;
/** 错误信息 */ /** 错误信息 */
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String errorMsg; private String errorMsg;
} }

View File

@@ -0,0 +1,11 @@
package org.dromara.daxpay.service.entity.allocation.transaction;
import java.util.List;
/**
* 分账订单和分账明细
* @author xxm
* @since 2024/11/14
*/
public record TransactionAndDetail(AllocTransaction transaction,
List<AllocDetail> details) {}

View File

@@ -0,0 +1,240 @@
package org.dromara.daxpay.service.service.allocation;
import cn.bootx.platform.core.exception.DataNotExistException;
import cn.bootx.platform.core.exception.RepetitiveOperationException;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.daxpay.core.enums.AllocTransactionResultEnum;
import org.dromara.daxpay.core.enums.AllocTransactionStatusEnum;
import org.dromara.daxpay.core.exception.DataErrorException;
import org.dromara.daxpay.core.exception.TradeStatusErrorException;
import org.dromara.daxpay.core.param.allocation.transaction.AllocFinishParam;
import org.dromara.daxpay.core.param.allocation.transaction.AllocationParam;
import org.dromara.daxpay.core.param.allocation.transaction.QueryAllocTransactionParam;
import org.dromara.daxpay.core.result.allocation.transaction.AllocResult;
import org.dromara.daxpay.core.result.allocation.transaction.AllocTransactionResult;
import org.dromara.daxpay.service.bo.allocation.receiver.AllocStartResultBo;
import org.dromara.daxpay.service.convert.allocation.AllocTransactionConvert;
import org.dromara.daxpay.service.dao.allocation.transaction.AllocDetailManager;
import org.dromara.daxpay.service.dao.allocation.transaction.AllocTransactionManager;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocTransaction;
import org.dromara.daxpay.service.entity.allocation.transaction.TransactionAndDetail;
import org.dromara.daxpay.service.entity.order.pay.PayOrder;
import org.dromara.daxpay.service.service.allocation.transaction.AllocAssistService;
import org.dromara.daxpay.service.strategy.AbsAllocationStrategy;
import org.dromara.daxpay.service.util.PaymentStrategyFactory;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import static org.dromara.daxpay.core.enums.AllocTransactionStatusEnum.ALLOC_END;
import static org.dromara.daxpay.core.enums.AllocTransactionStatusEnum.FINISH_FAILED;
/**
* 分账处理
* @author xxm
* @since 2024/11/14
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AllocationService {
private final AllocTransactionManager allocationOrderManager;
private final AllocDetailManager allocOrderDetailManager;
private final AllocAssistService allocationAssistService;
private final LockTemplate lockTemplate;
/**
* 开启分账 多次请求只会分账一次
* 优先级 分账接收方列表 > 分账组编号 > 默认分账组
*/
public AllocResult allocation(AllocationParam param) {
// 判断是否已经有分账订单
var allocOrder = allocationOrderManager.findByBizAllocNo(param.getBizAllocNo(), param.getAppId()).orElse(null);
if (Objects.nonNull(allocOrder)){
// 重复分账
return this.retryAlloc(param, allocOrder);
} else {
// 首次分账
PayOrder payOrder = allocationAssistService.getAndCheckPayOrder(param);
return this.allocation(param, payOrder);
}
}
/**
* 开启分账 优先级 分账接收方列表 > 分账组编号 > 默认分账组
*/
public AllocResult allocation(AllocationParam param, PayOrder payOrder) {
LockInfo lock = lockTemplate.lock("payment:allocation:" + payOrder.getId(),10000,200);
if (Objects.isNull(lock)){
throw new RepetitiveOperationException("分账发起处理中,请勿重复操作");
}
try {
// 构建分账订单相关信息
TransactionAndDetail orderAndDetail = allocationAssistService.checkAndCreateAlloc(param, payOrder);
// 检查是否需要进行分账
var order = orderAndDetail.transaction();
List<AllocDetail> details = orderAndDetail.details();
// 无需进行分账,
if (Objects.equals(order.getStatus(), AllocTransactionStatusEnum.IGNORE.getCode())){
return new AllocResult()
.setAllocNo(order.getAllocNo())
.setBizAllocNo(order.getBizAllocNo())
.setStatus(order.getStatus());
}
// 创建分账策略并初始化
var allocationStrategy = PaymentStrategyFactory.create(payOrder.getChannel(),AbsAllocationStrategy.class);
allocationStrategy.initParam(order, details);
try {
// 分账预处理
allocationStrategy.doBeforeHandler();
// 分账处理
AllocStartResultBo result = allocationStrategy.start();
// 执行中
order.setStatus(AllocTransactionStatusEnum.ALLOC_PROCESSING.getCode())
.setResult(AllocTransactionResultEnum.ALL_PENDING.getCode())
.setOutAllocNo(result.getOutAllocNo())
.setErrorMsg(null);
} catch (Exception e) {
log.error("分账出现错误:", e);
// 失败
order.setStatus(AllocTransactionStatusEnum.ALLOC_FAILED.getCode())
.setErrorMsg(e.getMessage());
// TODO 返回异常处理
}
allocationOrderManager.updateById(order);
return new AllocResult()
.setAllocNo(order.getAllocNo())
.setBizAllocNo(order.getBizAllocNo())
.setStatus(order.getStatus());
} finally {
lockTemplate.releaseLock(lock);
}
}
/**
* 重新分账
*/
private AllocResult retryAlloc(AllocationParam param, AllocTransaction order){
LockInfo lock = lockTemplate.lock("payment:allocation:" + order.getOrderId(),10000,200);
if (Objects.isNull(lock)){
throw new RepetitiveOperationException("分账发起处理中,请勿重复操作");
}
try {
// 需要是分账中分账中或者完成状态才能重新分账
List<String> list = Arrays.asList(ALLOC_END.getCode(),
AllocTransactionStatusEnum.ALLOC_FAILED.getCode(),
AllocTransactionStatusEnum.ALLOC_PROCESSING.getCode());
if (!list.contains(order.getStatus())){
throw new TradeStatusErrorException("分账单状态错误,无法重试");
}
List<AllocDetail> details = allocationAssistService.getDetails(order.getId());
// 创建分账策略并初始化
var allocationStrategy = PaymentStrategyFactory.create(order.getChannel(),AbsAllocationStrategy.class);
allocationStrategy.initParam(order, details);
// 分账预处理
allocationStrategy.doBeforeHandler();
// 更新分账单信息
allocationAssistService.updateOrder(param, order);
try {
// 重复分账处理
allocationStrategy.start();
order.setStatus(AllocTransactionStatusEnum.ALLOC_PROCESSING.getCode())
.setErrorMsg(null);
} catch (Exception e) {
log.error("重新分账出现错误:", e);
order.setStatus(AllocTransactionStatusEnum.ALLOC_FAILED.getCode())
.setErrorMsg(e.getMessage());
}
allocationOrderManager.updateById(order);
return new AllocResult()
.setAllocNo(order.getAllocNo())
.setBizAllocNo(order.getBizAllocNo())
.setStatus(order.getStatus());
} finally {
lockTemplate.releaseLock(lock);
}
}
/**
* 分账完结
*/
public AllocResult finish(AllocFinishParam param) {
AllocTransaction allocOrder;
if (Objects.nonNull(param.getAllocNo())){
allocOrder = allocationOrderManager.findByAllocNo(param.getAllocNo(), param.getAppId())
.orElseThrow(() -> new DataNotExistException("未查询到分账单信息"));
} else {
allocOrder = allocationOrderManager.findByBizAllocNo(param.getBizAllocNo(), param.getAppId())
.orElseThrow(() -> new DataNotExistException("未查询到分账单信息"));
}
return this.finish(allocOrder);
}
/**
* 分账完结
*/
public AllocResult finish(AllocTransaction allocOrder) {
// 只有分账结束后才可以完结
if (!Arrays.asList(ALLOC_END.getCode(),FINISH_FAILED.getCode()).contains(allocOrder.getStatus())) {
throw new TradeStatusErrorException("分账单状态错误");
}
List<AllocDetail> details = allocationAssistService.getDetails(allocOrder.getId());
// 创建分账策略并初始化
AbsAllocationStrategy allocationStrategy = PaymentStrategyFactory.create(allocOrder.getChannel(),AbsAllocationStrategy.class);
allocationStrategy.initParam(allocOrder, details);
// 分账完结预处理
allocationStrategy.doBeforeHandler();
try {
// 完结处理
allocationStrategy.finish();
// 完结状态
allocOrder.setStatus(AllocTransactionStatusEnum.FINISH.getCode())
.setFinishTime(LocalDateTime.now())
.setErrorMsg(null);
} catch (Exception e) {
log.error("分账完结错误:", e);
// 失败
allocOrder.setStatus(FINISH_FAILED.getCode())
.setErrorMsg(e.getMessage());
}
allocationOrderManager.updateById(allocOrder);
return new AllocResult()
.setAllocNo(allocOrder.getAllocNo())
.setBizAllocNo(allocOrder.getBizAllocNo())
.setStatus(allocOrder.getStatus());
}
/**
* 查询分账结果
*/
public AllocTransactionResult queryAllocTransaction(QueryAllocTransactionParam param) {
// 查询分账单
var allocOrder = allocationOrderManager.findByAllocNo(param.getAllocNo(), param.getAppId())
.orElseThrow(() -> new DataErrorException("分账单不存在"));
var result = AllocTransactionConvert.CONVERT.toResult(allocOrder);
// 查询分账单明细
var details = allocOrderDetailManager.findAllByOrderId(allocOrder.getId()).stream()
.map(AllocTransactionConvert.CONVERT::toResult)
.collect(Collectors.toList());
result.setDetails(details);
return result;
}
}

View File

@@ -1,4 +1,4 @@
package org.dromara.daxpay.service.service.allocation; package org.dromara.daxpay.service.service.allocation.receiver;
import cn.bootx.platform.common.mybatisplus.function.CollectorsFunction; import cn.bootx.platform.common.mybatisplus.function.CollectorsFunction;
import cn.bootx.platform.common.mybatisplus.util.MpUtil; import cn.bootx.platform.common.mybatisplus.util.MpUtil;
@@ -10,7 +10,7 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.bean.copier.CopyOptions;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.daxpay.service.bo.allocation.AllocGroupReceiverResultBo; import org.dromara.daxpay.service.bo.allocation.receiver.AllocGroupReceiverResultBo;
import org.dromara.daxpay.service.convert.allocation.AllocGroupConvert; import org.dromara.daxpay.service.convert.allocation.AllocGroupConvert;
import org.dromara.daxpay.service.dao.allocation.receiver.AllocGroupManager; import org.dromara.daxpay.service.dao.allocation.receiver.AllocGroupManager;
import org.dromara.daxpay.service.dao.allocation.receiver.AllocGroupReceiverManager; import org.dromara.daxpay.service.dao.allocation.receiver.AllocGroupReceiverManager;
@@ -19,7 +19,7 @@ import org.dromara.daxpay.service.entity.allocation.receiver.AllocGroup;
import org.dromara.daxpay.service.entity.allocation.receiver.AllocGroupReceiver; import org.dromara.daxpay.service.entity.allocation.receiver.AllocGroupReceiver;
import org.dromara.daxpay.service.entity.allocation.receiver.AllocReceiver; import org.dromara.daxpay.service.entity.allocation.receiver.AllocReceiver;
import org.dromara.daxpay.service.param.allocation.group.*; import org.dromara.daxpay.service.param.allocation.group.*;
import org.dromara.daxpay.service.bo.allocation.AllocGroupResultBo; import org.dromara.daxpay.service.bo.allocation.receiver.AllocGroupResultBo;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;

View File

@@ -1,4 +1,4 @@
package org.dromara.daxpay.service.service.allocation; package org.dromara.daxpay.service.service.allocation.receiver;
import cn.bootx.platform.baseapi.service.dict.DictionaryItemService; import cn.bootx.platform.baseapi.service.dict.DictionaryItemService;
import cn.bootx.platform.common.mybatisplus.util.MpUtil; import cn.bootx.platform.common.mybatisplus.util.MpUtil;
@@ -20,7 +20,7 @@ import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverAddParam;
import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverQueryParam; import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverQueryParam;
import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverRemoveParam; import org.dromara.daxpay.core.param.allocation.receiver.AllocReceiverRemoveParam;
import org.dromara.daxpay.core.result.allocation.receiver.AllocReceiverResult; import org.dromara.daxpay.core.result.allocation.receiver.AllocReceiverResult;
import org.dromara.daxpay.service.bo.allocation.AllocReceiverResultBo; import org.dromara.daxpay.service.bo.allocation.receiver.AllocReceiverResultBo;
import org.dromara.daxpay.service.convert.allocation.AllocReceiverConvert; import org.dromara.daxpay.service.convert.allocation.AllocReceiverConvert;
import org.dromara.daxpay.service.dao.allocation.receiver.AllocGroupReceiverManager; import org.dromara.daxpay.service.dao.allocation.receiver.AllocGroupReceiverManager;
import org.dromara.daxpay.service.dao.allocation.receiver.AllocReceiverManager; import org.dromara.daxpay.service.dao.allocation.receiver.AllocReceiverManager;

View File

@@ -0,0 +1,124 @@
package org.dromara.daxpay.service.service.allocation.transaction;
import cn.bootx.platform.core.exception.ValidationFailedException;
import cn.hutool.core.collection.CollUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.daxpay.core.enums.AllocDetailResultEnum;
import org.dromara.daxpay.core.enums.PayAllocStatusEnum;
import org.dromara.daxpay.core.exception.DataErrorException;
import org.dromara.daxpay.core.exception.OperationUnsupportedException;
import org.dromara.daxpay.core.exception.TradeNotExistException;
import org.dromara.daxpay.core.exception.TradeStatusErrorException;
import org.dromara.daxpay.core.param.allocation.transaction.AllocationParam;
import org.dromara.daxpay.service.dao.allocation.receiver.AllocGroupManager;
import org.dromara.daxpay.service.dao.allocation.transaction.AllocDetailManager;
import org.dromara.daxpay.service.dao.allocation.transaction.AllocTransactionManager;
import org.dromara.daxpay.service.entity.allocation.receiver.AllocGroup;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocTransaction;
import org.dromara.daxpay.service.entity.allocation.transaction.TransactionAndDetail;
import org.dromara.daxpay.service.entity.order.pay.PayOrder;
import org.dromara.daxpay.service.service.allocation.receiver.AllocGroupService;
import org.dromara.daxpay.service.service.order.pay.PayOrderQueryService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 分账支撑方法
* @author xxm
* @since 2024/5/22
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AllocAssistService {
private final AllocTransactionManager transactionManager;
private final PayOrderQueryService payOrderQueryService;
private final AllocGroupManager groupManager;
private final AllocGroupService allocationGroupService;
private final AllocTransactionService allocOrderService;
private final AllocDetailManager allocOrderDetailManager;
/**
* 根据新传入的分账订单更新订单信息
*/
@Transactional(rollbackFor = Exception.class)
public void updateOrder(AllocationParam allocationParam, AllocTransaction orderExtra) {
// 扩展信息
orderExtra.setClientIp(allocationParam.getClientIp())
.setNotifyUrl(allocationParam.getNotifyUrl())
.setAttach(allocationParam.getAttach())
.setReqTime(allocationParam.getReqTime());
transactionManager.updateById(orderExtra);
}
/**
* 获取并检查支付订单
*/
public PayOrder getAndCheckPayOrder(AllocationParam param) {
// 查询支付单
PayOrder payOrder = payOrderQueryService.findByBizOrOrderNo(param.getOrderNo(), param.getBizOrderNo(), param.getAppId())
.orElseThrow(() -> new TradeNotExistException("支付单不存在"));
// 判断订单是否可以分账
if (!payOrder.getAllocation()){
throw new OperationUnsupportedException("该订单不允许分账");
}
// 判断分账状态
if (Objects.equals(PayAllocStatusEnum.ALLOCATION.getCode(), payOrder.getAllocStatus())){
throw new TradeStatusErrorException("该订单已分账完成");
}
return payOrder;
}
/**
* 构建分账订单相关信息
*/
public TransactionAndDetail checkAndCreateAlloc(AllocationParam param, PayOrder payOrder){
// 创建分账单和明细并保存, 同时更新支付订单状态 使用事务
TransactionAndDetail orderAndDetail;
// 判断是否传输了分账接收方列表
if (CollUtil.isNotEmpty(param.getReceivers())) {
orderAndDetail = allocOrderService.createAndUpdate(param, payOrder);
} else {
AllocGroup allocationGroup;
if (Objects.nonNull(param.getGroupNo())){
// 指定分账组
allocationGroup = groupManager.findByGroupNo(param.getGroupNo(),param.getAppId()).orElseThrow(() -> new DataErrorException("未查询到分账组"));
} else {
// 默认分账组
allocationGroup = groupManager.findDefaultGroup(payOrder.getChannel(),param.getAppId()).orElseThrow(() -> new DataErrorException("未查询到默认分账组"));
}
// 判断通道类型是否一致
if (!Objects.equals(allocationGroup.getChannel(), payOrder.getChannel())){
throw new ValidationFailedException("分账接收方列表存在非本通道的数据");
}
var receiversByGroups = allocationGroupService.findReceiversByGroups(allocationGroup.getId());
orderAndDetail = allocOrderService.createAndUpdate(param ,payOrder, receiversByGroups);
}
return orderAndDetail;
}
/**
* 获取发起分账或完结的明细
*/
public List<AllocDetail> getDetails(Long allocOrderId){
List<AllocDetail> details = allocOrderDetailManager.findAllByOrderId(allocOrderId);
// 过滤掉忽略的条目
return details.stream()
.filter(detail -> !Objects.equals(detail.getResult(), AllocDetailResultEnum.IGNORE.getCode()))
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,185 @@
package org.dromara.daxpay.service.service.allocation.transaction;
import cn.bootx.platform.core.exception.ValidationFailedException;
import cn.bootx.platform.core.util.BigDecimalUtil;
import cn.hutool.core.util.IdUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.daxpay.core.enums.AllocDetailResultEnum;
import org.dromara.daxpay.core.enums.AllocTransactionResultEnum;
import org.dromara.daxpay.core.enums.AllocTransactionStatusEnum;
import org.dromara.daxpay.core.enums.PayAllocStatusEnum;
import org.dromara.daxpay.core.param.allocation.transaction.ReceiverParam;
import org.dromara.daxpay.core.param.allocation.transaction.AllocationParam;
import org.dromara.daxpay.core.util.TradeNoGenerateUtil;
import org.dromara.daxpay.service.bo.allocation.receiver.AllocGroupReceiverResultBo;
import org.dromara.daxpay.service.dao.allocation.receiver.AllocReceiverManager;
import org.dromara.daxpay.service.dao.allocation.transaction.AllocDetailManager;
import org.dromara.daxpay.service.dao.allocation.transaction.AllocTransactionManager;
import org.dromara.daxpay.service.dao.order.pay.PayOrderManager;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocTransaction;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.TransactionAndDetail;
import org.dromara.daxpay.service.entity.order.pay.PayOrder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 分账交易记录服务
* @author xxm
* @since 2024/11/14
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AllocTransactionService {
private final AllocReceiverManager receiverManager;
private final AllocTransactionManager transactionManager;
private final AllocDetailManager transactionDetailManager;
private final PayOrderManager payOrderManager;
/**
* 生成分账订单, 根据分账组创建
*/
@Transactional(rollbackFor = Exception.class)
public TransactionAndDetail createAndUpdate(AllocationParam param, PayOrder payOrder, List<AllocGroupReceiverResultBo> receiversByGroups) {
// 订单明细
List<AllocDetail> details = receiversByGroups.stream()
.map(o -> {
// 计算分账金额, 小数部分直接舍弃, 防止分账金额超过上限
// 等同于 payOrder.getAmount() * rate / 100;
var amount = payOrder.getAmount()
.multiply(o.getRate())
.divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN);
AllocDetail detail = new AllocDetail()
.setReceiverNo(o.getReceiverNo())
.setReceiverId(o.getId())
.setAmount(amount)
.setResult(AllocDetailResultEnum.PENDING.getCode())
.setRate(amount)
.setReceiverType(o.getReceiverType())
.setReceiverName(o.getReceiverName())
.setReceiverAccount(o.getReceiverAccount());
// 如果金额为0, 设置为分账失败, 不参与分账
if (BigDecimalUtil.isEqual(amount, BigDecimal.ZERO)) {
detail.setResult(AllocDetailResultEnum.IGNORE.getCode())
.setErrorMsg("分账比例有误或金额太小, 无法进行分账")
.setFinishTime(LocalDateTime.now());
}
return detail;
})
.collect(Collectors.toList());
return this.saveAllocOrder(param, payOrder, details);
}
/**
* 生成分账订单, 通过传入的分账方创建
*/
@Transactional(rollbackFor = Exception.class)
public TransactionAndDetail createAndUpdate(AllocationParam param, PayOrder payOrder) {
List<String> receiverNos = param.getReceivers()
.stream()
.map(ReceiverParam::getReceiverNo)
.distinct()
.collect(Collectors.toList());
if (receiverNos.size() != param.getReceivers()
.size()) {
throw new ValidationFailedException("分账接收方编号重复");
}
var receiverNoMap = param.getReceivers()
.stream()
.collect(Collectors.toMap(ReceiverParam::getReceiverNo, ReceiverParam::getAmount));
// 查询分账接收方信息
var receivers = receiverManager.findAllByReceiverNos(receiverNos, payOrder.getAppId());
if (receivers.size() != receiverNos.size()) {
throw new ValidationFailedException("分账接收方列表存在重复或无效的数据");
}
// 判断分账接收方类型是否都与分账订单类型匹配
boolean anyMatch = receivers.stream()
.anyMatch(o -> !Objects.equals(o.getChannel(), payOrder.getChannel()));
if (anyMatch) {
throw new ValidationFailedException("分账接收方列表存在非本通道的数据");
}
long allocId = IdUtil.getSnowflakeNextId();
// 订单明细
List<AllocDetail> details = receivers.stream()
.map(o -> {
// 计算分账比例, 不是很精确
var amount = receiverNoMap.get(o.getReceiverNo());
var rate = amount
.divide(payOrder.getAmount(), 2, RoundingMode.DOWN)
.multiply(BigDecimal.valueOf(100));
AllocDetail detail = new AllocDetail();
detail.setAllocationId(allocId)
.setReceiverId(o.getId())
.setReceiverNo(o.getReceiverNo())
.setAmount(amount)
.setResult(AllocDetailResultEnum.PENDING.getCode())
.setRate(rate)
.setReceiverType(o.getReceiverType())
.setReceiverName(o.getReceiverName())
.setReceiverAccount(o.getReceiverAccount());
return detail;
})
.collect(Collectors.toList());
return this.saveAllocOrder(param, payOrder, details);
}
/**
* 保存分账相关订单信息
*/
private TransactionAndDetail saveAllocOrder(AllocationParam param, PayOrder payOrder, List<AllocDetail> details) {
long allocId = IdUtil.getSnowflakeNextId();
// 分账明细设置ID
details.forEach(o -> o.setAllocationId(allocId));
// 求分账的总额
var sumAmount = details.stream()
.map(AllocDetail::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 分账订单
var allocOrder = new AllocTransaction()
.setOrderId(payOrder.getId())
.setOrderNo(payOrder.getOrderNo())
.setBizOrderNo(payOrder.getBizOrderNo())
.setOutOrderNo(payOrder.getOutOrderNo())
.setTitle(payOrder.getTitle())
.setAllocNo(TradeNoGenerateUtil.allocation())
.setBizAllocNo(param.getBizAllocNo())
.setChannel(payOrder.getChannel())
.setDescription(param.getDescription())
.setStatus(AllocTransactionStatusEnum.ALLOC_PROCESSING.getCode())
.setResult(AllocTransactionResultEnum.ALL_PENDING.getCode())
.setAmount(sumAmount)
.setNotifyUrl(param.getNotifyUrl())
.setAttach(param.getAttach())
.setClientIp(param.getClientIp());
// 如果分账订单金额为0, 设置为忽略状态
if (BigDecimalUtil.isEqual(sumAmount, BigDecimal.ZERO)) {
allocOrder.setStatus(AllocTransactionStatusEnum.IGNORE.getCode())
.setFinishTime(LocalDateTime.now())
.setResult(AllocTransactionStatusEnum.ALLOC_FAILED.getCode())
.setErrorMsg("分账比例有误或金额太小, 无法进行分账");
}
allocOrder.setId(allocId);
// 更新支付订单分账状态
payOrder.setAllocStatus(PayAllocStatusEnum.ALLOCATION.getCode());
payOrderManager.updateById(payOrder);
transactionDetailManager.saveAll(details);
transactionManager.save(allocOrder);
return new TransactionAndDetail(allocOrder,details);
}
}

View File

@@ -0,0 +1,48 @@
package org.dromara.daxpay.service.strategy;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.dromara.daxpay.service.bo.allocation.receiver.AllocStartResultBo;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocTransaction;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import java.util.List;
/**
* 分账接收方管理抽象策略
* @author xxm
* @since 2024/4/1
*/
@Slf4j
@Getter
@Setter
public abstract class AbsAllocationStrategy implements PaymentStrategy{
private AllocTransaction transaction;
private List<AllocDetail> details;
/**
* 初始化参数
*/
public void initParam(AllocTransaction transaction, List<AllocDetail> details) {
this.transaction = transaction;
this.details = details;
}
/**
* 操作前处理, 校验和初始化支付配置
*/
public abstract void doBeforeHandler();
/**
* 分账启动
*/
public abstract AllocStartResultBo start();
/**
* 分账完结
*/
public abstract void finish();
}