feat 分账同步功能开发

This commit is contained in:
DaxPay
2024-12-16 16:34:45 +08:00
parent 6229c5a69a
commit 13c28069ec
18 changed files with 514 additions and 217 deletions

View File

@@ -1,29 +1,37 @@
package org.dromara.daxpay.channel.alipay.service.allocation;
import cn.bootx.platform.common.mybatisplus.base.MpIdEntity;
import cn.bootx.platform.common.mybatisplus.function.CollectorsFunction;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayResponse;
import com.alipay.api.domain.AlipayTradeOrderSettleModel;
import com.alipay.api.domain.OpenApiRoyaltyDetailInfoPojo;
import com.alipay.api.domain.SettleExtendParams;
import com.alipay.api.domain.*;
import com.alipay.api.request.AlipayTradeOrderSettleQueryRequest;
import com.alipay.api.request.AlipayTradeOrderSettleRequest;
import com.alipay.api.response.AlipayTradeOrderSettleQueryResponse;
import com.alipay.api.response.AlipayTradeOrderSettleResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.daxpay.channel.alipay.code.AliPayCode;
import org.dromara.daxpay.channel.alipay.service.config.AliPayConfigService;
import org.dromara.daxpay.core.enums.AllocDetailResultEnum;
import org.dromara.daxpay.core.exception.OperationFailException;
import org.dromara.daxpay.core.exception.TradeFailException;
import org.dromara.daxpay.core.util.PayUtil;
import org.dromara.daxpay.service.bo.allocation.AllocStartResultBo;
import org.dromara.daxpay.service.bo.allocation.AllocSyncResultBo;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocOrder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -60,7 +68,7 @@ public class AliPayAllocationService {
model.setRoyaltyParameters(royaltyParameters);
AlipayTradeOrderSettleRequest request = new AlipayTradeOrderSettleRequest();
request.setBizModel(model);
AlipayTradeOrderSettleResponse response = null;
AlipayTradeOrderSettleResponse response;
try {
response = aliPayConfigService.execute(request);
this.verifyErrorMsg(response);
@@ -79,7 +87,7 @@ public class AliPayAllocationService {
public void finish(AllocOrder allocOrder, List<AllocDetail> orderDetails){
// 分账主体参数
AlipayTradeOrderSettleModel model = new AlipayTradeOrderSettleModel();
model.setOutRequestNo(String.valueOf(allocOrder.getAllocNo()));
model.setOutRequestNo(allocOrder.getAllocNo());
model.setTradeNo(allocOrder.getOutOrderNo());
model.setRoyaltyMode(AliPayCode.ALLOC_ASYNC);
// 分账完结参数
@@ -113,37 +121,40 @@ public class AliPayAllocationService {
/**
* 分账状态同步
*/
// @SneakyThrows
// public AllocRemoteSyncResult sync(AllocOrder allocOrder, List<AllocDetail> allocOrderDetails, AliPayConfig config){
// AlipayClient alipayClient = aliPayConfigService.getAlipayClient(config);
// AlipayTradeOrderSettleQueryModel model = new AlipayTradeOrderSettleQueryModel();
// model.setTradeNo(allocOrder.getOutOrderNo());
// model.setOutRequestNo(allocOrder.getAllocNo());
// AlipayTradeOrderSettleQueryRequest request = new AlipayTradeOrderSettleQueryRequest();
// request.setBizModel(model);
// AlipayTradeOrderSettleQueryResponse response = alipayClient.execute(request);
// // 验证
// this.verifyErrorMsg(response);
// Map<String, AllocOrderDetail> detailMap = allocOrderDetails.stream()
// .collect(Collectors.toMap(AllocOrderDetail::getReceiverAccount, Function.identity(), CollectorsFunction::retainLatest));
// List<RoyaltyDetail> royaltyDetailList = response.getRoyaltyDetailList();
// for (RoyaltyDetail receiver : royaltyDetailList) {
// AllocOrderDetail detail = detailMap.get(receiver.getTransIn());
// if (Objects.nonNull(detail)) {
// detail.setResult(this.getDetailResultEnum(receiver.getState()).getCode());
// detail.setErrorCode(receiver.getErrorCode());
// detail.setErrorMsg(receiver.getErrorDesc());
// // 如果是完成, 更新时间
// if (AllocDetailResultEnum.SUCCESS.getCode().equals(detail.getResult())){
// LocalDateTime finishTime = LocalDateTimeUtil.of(receiver.getExecuteDt());
// detail.setFinishTime(finishTime)
// .setErrorMsg(null)
// .setErrorCode(null);
// }
// }
// }
// return new AllocRemoteSyncResult().setSyncInfo(JSONUtil.toJsonStr(response));
// }
public AllocSyncResultBo sync(AllocOrder allocOrder, List<AllocDetail> allocOrderDetails){
AlipayTradeOrderSettleQueryModel model = new AlipayTradeOrderSettleQueryModel();
model.setOutRequestNo(allocOrder.getAllocNo());
model.setTradeNo(allocOrder.getOutOrderNo());
AlipayTradeOrderSettleQueryRequest request = new AlipayTradeOrderSettleQueryRequest();
request.setBizModel(model);
AlipayTradeOrderSettleQueryResponse response;
try {
response = aliPayConfigService.execute(request);
} catch (AlipayApiException e) {
throw new OperationFailException(e.getMessage());
}
// 验证
this.verifyErrorMsg(response);
Map<String, AllocDetail> detailMap = allocOrderDetails.stream()
.collect(Collectors.toMap(AllocDetail::getReceiverAccount, Function.identity(), CollectorsFunction::retainLatest));
List<RoyaltyDetail> royaltyDetailList = response.getRoyaltyDetailList();
for (RoyaltyDetail receiver : royaltyDetailList) {
var detail = detailMap.get(receiver.getTransIn());
if (Objects.nonNull(detail)) {
detail.setResult(this.getDetailResultEnum(receiver.getState()).getCode());
detail.setErrorCode(receiver.getErrorCode());
detail.setErrorMsg(receiver.getErrorDesc());
// 如果是完成, 更新时间
if (AllocDetailResultEnum.SUCCESS.getCode().equals(detail.getResult())){
LocalDateTime finishTime = LocalDateTimeUtil.of(receiver.getExecuteDt());
detail.setFinishTime(finishTime)
.setErrorMsg(null)
.setErrorCode(null);
}
}
}
return new AllocSyncResultBo().setSyncInfo(JSONUtil.toJsonStr(response));
}
/**
* 验证错误信息

View File

@@ -5,6 +5,7 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.daxpay.channel.alipay.service.allocation.AliPayAllocationService;
import org.dromara.daxpay.core.enums.ChannelEnum;
import org.dromara.daxpay.service.bo.allocation.AllocStartResultBo;
import org.dromara.daxpay.service.bo.allocation.AllocSyncResultBo;
import org.dromara.daxpay.service.strategy.AbsAllocationStrategy;
import org.springframework.stereotype.Component;
@@ -43,4 +44,12 @@ public class AliAllocationStrategy extends AbsAllocationStrategy {
public void finish() {
aliPayAllocationService.finish(getOrder(), getDetails());
}
/**
* 同步状态
*/
@Override
public AllocSyncResultBo doSync() {
return aliPayAllocationService.sync(getOrder(), getDetails());
}
}

View File

@@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.daxpay.core.enums.ChannelEnum;
import org.dromara.daxpay.service.bo.allocation.AllocStartResultBo;
import org.dromara.daxpay.service.bo.allocation.AllocSyncResultBo;
import org.dromara.daxpay.service.strategy.AbsAllocationStrategy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -46,4 +47,12 @@ public class WechatAllocationStrategy extends AbsAllocationStrategy {
}
/**
* 同步状态
*/
@Override
public AllocSyncResultBo doSync() {
return null;
}
}

View File

@@ -18,7 +18,9 @@ public enum TradeTypeEnum {
PAY("pay","支付"),
REFUND("refund","退款"),
TRANSFER("transfer","转账"),;
TRANSFER("transfer","转账"),
ALLOCATION("allocation","分账"),
;
private final String code;
private final String name;

View File

@@ -0,0 +1,26 @@
package org.dromara.daxpay.core.param.allocation.transaction;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.daxpay.core.param.PaymentCommonParam;
/**
* 分账同步请求参数
* @author xxm
* @since 2024/4/12
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Schema(title = "分账同步请求参数")
public class AllocSyncParam extends PaymentCommonParam {
@Schema(description = "分账号")
@Size(max = 32, message = "分账号不可超过32位")
private String allocNo;
@Schema(description = "商户分账号")
@Size(max = 100, message = "商户分账号不可超过100位")
private String bizAllocNo;
}

View File

@@ -2,13 +2,16 @@ package org.dromara.daxpay.core.param.allocation.transaction;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
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.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.dromara.daxpay.core.param.PaymentCommonParam;
import java.math.BigDecimal;
import java.util.List;
/**
@@ -64,4 +67,25 @@ public class AllocationParam extends PaymentCommonParam {
@Schema(description = "商户扩展参数")
private String attach;
/**
* 分账接收方列表
*/
@Data
@Accessors(chain = true)
@Schema(title = "分账接收方列表")
public static 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

@@ -1,34 +0,0 @@
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

@@ -0,0 +1,16 @@
package org.dromara.daxpay.core.result.allocation;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 分账同步接口返回类
* @author xxm
* @since 2024/5/20
*/
@Data
@Accessors(chain = true)
@Schema(title = "分账同步接口")
public class AllocSyncResult {
}

View File

@@ -1,4 +1,4 @@
package org.dromara.daxpay.core.result.allocation.order;
package org.dromara.daxpay.core.result.allocation;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@@ -0,0 +1,16 @@
package org.dromara.daxpay.service.bo.allocation;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 分账同步结果
* @author xxm
* @since 2024/5/23
*/
@Data
@Accessors(chain = true)
public class AllocSyncResultBo {
/** 同步时网关返回的对象, 序列化为json字符串 */
private String syncInfo;
}

View File

@@ -8,13 +8,15 @@ import cn.bootx.platform.core.rest.result.Result;
import com.fhs.core.trans.anno.TransMethodResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.daxpay.core.param.allocation.transaction.AllocFinishParam;
import org.dromara.daxpay.service.param.order.allocation.AllocOrderQuery;
import org.dromara.daxpay.service.result.allocation.order.AllocDetailVo;
import org.dromara.daxpay.service.result.allocation.order.AllocOrderVo;
import org.dromara.daxpay.service.service.allocation.AllocationService;
import org.dromara.daxpay.service.service.allocation.AllocationSyncService;
import org.dromara.daxpay.service.service.allocation.order.AllocOrderQueryService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -27,6 +29,7 @@ import java.util.List;
* @author xxm
* @since 2024/4/7
*/
@Validated
@Tag(name = "分账订单控制器")
@RestController
@RequestGroup(groupCode = "AllocOrder", groupName = "分账订单", moduleCode = "Alloc", moduleName = "分账管理" )
@@ -38,6 +41,8 @@ public class AllocOrderController {
private final AllocationService allocationService;
private final AllocationSyncService allocationSyncService;
@Operation(summary = "分页")
@GetMapping("/page")
public Result<PageResult<AllocOrderVo>> page(PageParam pageParam, AllocOrderQuery param){
@@ -64,13 +69,23 @@ public class AllocOrderController {
return Res.ok(queryService.findDetailById(id));
}
@Operation(summary = "分账重试")
@PostMapping("/retry")
public Result<Void> retry(@NotNull(message = "分账单ID不可为空") Long id){
return Res.ok();
}
@Operation(summary = "分账完结")
@PostMapping("/finish")
public Result<Void> finish(String allocNo){
AllocFinishParam param = new AllocFinishParam();
param.setAllocNo(allocNo);
allocationService.finish(param);
public Result<Void> finish(@NotNull(message = "分账单ID不可为空")Long id){
allocationService.finish(id);
return Res.ok();
}
@Operation(summary = "分账同步")
@PostMapping("/sync")
public Result<Void> sync(@NotNull(message = "分账单ID不可为空") Long id){
allocationSyncService.sync(id);
return Res.ok();
}
}

View File

@@ -8,15 +8,18 @@ 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.AllocReceiverRemoveParam;
import org.dromara.daxpay.core.param.allocation.transaction.AllocFinishParam;
import org.dromara.daxpay.core.param.allocation.transaction.AllocSyncParam;
import org.dromara.daxpay.core.param.allocation.transaction.AllocationParam;
import org.dromara.daxpay.core.param.allocation.transaction.QueryAllocOrderParam;
import org.dromara.daxpay.core.result.DaxResult;
import org.dromara.daxpay.core.result.allocation.AllocSyncResult;
import org.dromara.daxpay.core.result.allocation.order.AllocOrderResult;
import org.dromara.daxpay.core.result.allocation.order.AllocationResult;
import org.dromara.daxpay.core.result.allocation.AllocationResult;
import org.dromara.daxpay.core.result.allocation.receiver.AllocReceiverResult;
import org.dromara.daxpay.core.util.DaxRes;
import org.dromara.daxpay.service.common.anno.PaymentVerify;
import org.dromara.daxpay.service.service.allocation.AllocationService;
import org.dromara.daxpay.service.service.allocation.AllocationSyncService;
import org.dromara.daxpay.service.service.allocation.receiver.AllocReceiverService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -37,6 +40,7 @@ import org.springframework.web.bind.annotation.RestController;
public class UniAllocationController {
private final AllocReceiverService allocReceiverService;
private final AllocationService allocationService;
private final AllocationSyncService allocationSyncService;
@Operation(summary = "发起分账接口")
@PostMapping("/start")
@@ -46,11 +50,17 @@ public class UniAllocationController {
@Operation(summary = "分账完结接口")
@PostMapping("/finish")
public DaxResult<AllocationResult> finish(AllocFinishParam param){
public DaxResult<AllocationResult> finish(@RequestBody AllocFinishParam param){
return DaxRes.ok(allocationService.finish(param));
}
@Operation(summary = "分账写你查询接口")
@Operation(summary = "分账同步接口")
@PostMapping("/sync")
public DaxResult<AllocSyncResult> sync(@RequestBody AllocSyncParam param){
return DaxRes.ok(allocationSyncService.sync(param));
}
@Operation(summary = "分账订单查询接口")
@PostMapping("/query")
public DaxResult<AllocOrderResult> query(@RequestBody QueryAllocOrderParam param){
return DaxRes.ok(allocationService.queryAllocOrder(param));

View File

@@ -13,17 +13,19 @@ 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.QueryAllocOrderParam;
import org.dromara.daxpay.core.result.allocation.AllocationResult;
import org.dromara.daxpay.core.result.allocation.order.AllocOrderResult;
import org.dromara.daxpay.core.result.allocation.order.AllocationResult;
import org.dromara.daxpay.service.bo.allocation.AllocStartResultBo;
import org.dromara.daxpay.service.convert.allocation.AllocOrderConvert;
import org.dromara.daxpay.service.dao.allocation.transaction.AllocDetailManager;
import org.dromara.daxpay.service.dao.allocation.transaction.AllocOrderManager;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocAndDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocOrder;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocAndDetail;
import org.dromara.daxpay.service.entity.order.pay.PayOrder;
import org.dromara.daxpay.service.service.allocation.order.AllocationAssistService;
import org.dromara.daxpay.service.service.allocation.order.AllocOrderQueryService;
import org.dromara.daxpay.service.service.allocation.order.AllocOrderService;
import org.dromara.daxpay.service.service.assist.PaymentAssistService;
import org.dromara.daxpay.service.strategy.AbsAllocationStrategy;
import org.dromara.daxpay.service.util.PaymentStrategyFactory;
import org.springframework.stereotype.Service;
@@ -51,10 +53,15 @@ public class AllocationService {
private final AllocDetailManager allocOrderDetailManager;
private final AllocationAssistService allocationAssistService;
private final LockTemplate lockTemplate;
private final PaymentAssistService paymentAssistService;
private final AllocOrderQueryService allocOrderQueryService;
private final AllocOrderService allocOrderService;
/**
* 开启分账 多次请求只会分账一次
* 优先级 分账接收方列表 > 分账组编号 > 默认分账组
@@ -67,7 +74,7 @@ public class AllocationService {
return this.retryAlloc(param, allocOrder);
} else {
// 首次分账
PayOrder payOrder = allocationAssistService.getAndCheckPayOrder(param);
PayOrder payOrder = allocOrderQueryService.getAndCheckPayOrder(param);
return this.allocation(param, payOrder);
}
}
@@ -82,7 +89,7 @@ public class AllocationService {
}
try {
// 构建分账订单相关信息
AllocAndDetail orderAndDetail = allocationAssistService.checkAndCreateAlloc(param, payOrder);
AllocAndDetail orderAndDetail = allocOrderService.checkAndCreateAlloc(param, payOrder);
// 检查是否需要进行分账
var order = orderAndDetail.transaction();
List<AllocDetail> details = orderAndDetail.details();
@@ -123,6 +130,19 @@ public class AllocationService {
}
}
/**
* 分账重试
*/
public AllocationResult retryAlloc(Long id){
var allocOrder = allocationOrderManager.findById(id).orElseThrow(() -> new DataNotExistException("分账单不存在"));
AllocationParam param = new AllocationParam();
param.setNotifyUrl(allocOrder.getNotifyUrl())
.setAttach(allocOrder.getAttach())
.setReqTime(allocOrder.getReqTime());
param.setClientIp(allocOrder.getClientIp());
return retryAlloc(param, allocOrder);
}
/**
* 重新分账
*/
@@ -139,14 +159,14 @@ public class AllocationService {
if (!list.contains(order.getStatus())){
throw new TradeStatusErrorException("分账单状态错误,无法重试");
}
List<AllocDetail> details = allocationAssistService.getDetails(order.getId());
List<AllocDetail> details = allocOrderQueryService.getDetails(order.getId());
// 创建分账策略并初始化
var allocationStrategy = PaymentStrategyFactory.create(order.getChannel(),AbsAllocationStrategy.class);
allocationStrategy.initParam(order, details);
// 分账预处理
allocationStrategy.doBeforeHandler();
// 更新分账单信息
allocationAssistService.updateOrder(param, order);
allocOrderService.updateOrder(param, order);
try {
// 重复分账处理
allocationStrategy.start();
@@ -168,6 +188,17 @@ public class AllocationService {
}
}
/**
* 分账完结
*/
public AllocationResult finish(Long id) {
AllocOrder allocOrder = allocationOrderManager.findById(id)
.orElseThrow(() -> new DataNotExistException("未查询到分账单信息"));
paymentAssistService.initMchApp(allocOrder.getAppId());
return this.finish(allocOrder);
}
/**
* 分账完结
*/
@@ -191,7 +222,7 @@ public class AllocationService {
if (!Arrays.asList(ALLOC_END.getCode(),FINISH_FAILED.getCode()).contains(allocOrder.getStatus())) {
throw new TradeStatusErrorException("分账单状态错误");
}
List<AllocDetail> details = allocationAssistService.getDetails(allocOrder.getId());
List<AllocDetail> details = allocOrderQueryService.getDetails(allocOrder.getId());
// 创建分账策略并初始化
AbsAllocationStrategy allocationStrategy = PaymentStrategyFactory.create(allocOrder.getChannel(),AbsAllocationStrategy.class);

View File

@@ -0,0 +1,187 @@
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.AllocDetailResultEnum;
import org.dromara.daxpay.core.enums.AllocationResultEnum;
import org.dromara.daxpay.core.enums.AllocationStatusEnum;
import org.dromara.daxpay.core.enums.TradeTypeEnum;
import org.dromara.daxpay.core.param.allocation.transaction.AllocSyncParam;
import org.dromara.daxpay.core.result.allocation.AllocSyncResult;
import org.dromara.daxpay.service.bo.allocation.AllocSyncResultBo;
import org.dromara.daxpay.service.common.local.PaymentContextLocal;
import org.dromara.daxpay.service.dao.allocation.transaction.AllocDetailManager;
import org.dromara.daxpay.service.dao.allocation.transaction.AllocOrderManager;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocOrder;
import org.dromara.daxpay.service.entity.record.sync.TradeSyncRecord;
import org.dromara.daxpay.service.service.assist.PaymentAssistService;
import org.dromara.daxpay.service.service.record.sync.TradeSyncRecordService;
import org.dromara.daxpay.service.strategy.AbsAllocationStrategy;
import org.dromara.daxpay.service.util.PaymentStrategyFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* 对账同步
* @author xxm
* @since 2024/5/23
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AllocationSyncService {
private final AllocOrderManager allocationOrderManager;
private final AllocDetailManager allocOrderDetailManager;
private final TradeSyncRecordService paySyncRecordService;
private final LockTemplate lockTemplate;
private final PaymentAssistService paymentAssistService;
/**
* 分账同步
*/
@Transactional(rollbackFor = Exception.class)
public void sync(Long id) {
AllocOrder allocOrder = allocationOrderManager.findById(id)
.orElseThrow(() -> new DataNotExistException("分账单不存在"));
// 如果类型为忽略, 不进行同步处理
if (Objects.equals(allocOrder.getStatus(), AllocationStatusEnum.IGNORE.getCode())){
return;
}
paymentAssistService.initMchApp(allocOrder.getAppId());
// 调用同步逻辑
this.sync(allocOrder);
}
/**
* 分账同步
*/
@Transactional(rollbackFor = Exception.class)
public AllocSyncResult sync(AllocSyncParam param) {
// 获取分账订单
AllocOrder allocOrder = null;
if (Objects.nonNull(param.getAllocNo())){
allocOrder = allocationOrderManager.findByAllocNo(param.getAllocNo(),param.getAppId())
.orElseThrow(() -> new DataNotExistException("分账单不存在"));
}
if (Objects.isNull(allocOrder)){
allocOrder = allocationOrderManager.findByAllocNo(param.getBizAllocNo(),param.getAppId())
.orElseThrow(() -> new DataNotExistException("分账单不存在"));
}
// 如果类型为忽略, 不进行同步处理
if (Objects.equals(allocOrder.getStatus(), AllocationStatusEnum.IGNORE.getCode())){
return new AllocSyncResult();
}
// 调用同步逻辑
this.sync(allocOrder);
return new AllocSyncResult();
}
/**
* 分账同步
*/
@Transactional(rollbackFor = Exception.class)
public void sync(AllocOrder allocOrder){
LockInfo lock = lockTemplate.lock("payment:allocation:" + allocOrder.getOrderId(),10000,200);
if (Objects.isNull(lock)){
throw new RepetitiveOperationException("分账同步中,请勿重复操作");
}
try {
List<AllocDetail> detailList = allocOrderDetailManager.findAllByOrderId(allocOrder.getId());
// 获取分账策略
var allocationStrategy = PaymentStrategyFactory.create(allocOrder.getChannel(),AbsAllocationStrategy.class);
allocationStrategy.initParam(allocOrder, detailList);
// 分账完结预处理
allocationStrategy.doBeforeHandler();
// 执行同步操作, 分账明细的状态变更会在这个里面
AllocSyncResultBo allocSyncResultBo = allocationStrategy.doSync();
// 保存分账同步记录
this.saveRecord(allocOrder, allocSyncResultBo);
// 根据订单明细更新订单的状态和处理结果
allocOrder.setErrorMsg(null).setErrorCode(null);
this.updateOrderStatus(allocOrder, detailList);
} finally {
lockTemplate.releaseLock(lock);
}
}
/**
* 根据订单明细更新订单的状态和处理结果, 如果订单是分账结束或失败, 不更新状态
* TODO 是否多次同步会产生多次变动, 注意处理多次推送通知的问题, 目前是
*/
private void updateOrderStatus(AllocOrder allocOrder, List<AllocDetail> details){
// 如果是分账结束或失败, 不更新状态
String status = allocOrder.getStatus();
// 如果是分账结束或失败, 不进行对订单进行处理
List<String> list = Arrays.asList(AllocationStatusEnum.FINISH.getCode(), AllocationStatusEnum.FINISH_FAILED.getCode());
if (!list.contains(status)){
// 判断明细状态. 获取成功和失败的
long successCount = details.stream()
.map(AllocDetail::getResult)
.filter(AllocDetailResultEnum.SUCCESS.getCode()::equals)
.count();
long failCount = details.stream()
.map(AllocDetail::getResult)
.filter(AllocDetailResultEnum.FAIL.getCode()::equals)
.count();
// 成功和失败都为0 表示进行中
if (successCount == 0 && failCount == 0){
allocOrder.setStatus(AllocationStatusEnum.PROCESSING.getCode())
.setResult(AllocationResultEnum.ALL_PENDING.getCode());
} else {
if (failCount == details.size()){
// 全部失败
allocOrder.setStatus(AllocationStatusEnum.ALLOC_END.getCode())
.setResult(AllocationResultEnum.ALL_FAILED.getCode());
} else if (successCount == details.size()){
// 全部成功
allocOrder.setStatus(AllocationStatusEnum.ALLOC_END.getCode())
.setResult(AllocationResultEnum.ALL_SUCCESS.getCode());
} else {
// 部分成功
allocOrder.setStatus(AllocationStatusEnum.ALLOC_END.getCode())
.setResult(AllocationResultEnum.PART_SUCCESS.getCode());
}
}
}
allocOrderDetailManager.updateAllById(details);
allocationOrderManager.updateById(allocOrder);
// 如果状态为完成, 发送通知
if (Objects.equals(AllocationStatusEnum.ALLOC_END.getCode(), allocOrder.getStatus())){
// 发送通知
// clientNoticeService.registerAllocNotice(allocOrder, details);
}
}
/**
* 保存同步记录
*/
private void saveRecord(AllocOrder order, AllocSyncResultBo syncResult){
TradeSyncRecord syncRecord = new TradeSyncRecord()
.setBizTradeNo(order.getBizAllocNo())
.setTradeNo(order.getAllocNo())
.setOutTradeNo(order.getOutAllocNo())
.setTradeType(TradeTypeEnum.ALLOCATION.getCode())
.setChannel(order.getChannel())
.setSyncInfo(syncResult.getSyncInfo())
.setClientIp(PaymentContextLocal.get().getClientInfo().getClientIp());
paySyncRecordService.saveRecord(syncRecord);
}
}

View File

@@ -6,16 +6,26 @@ import cn.bootx.platform.core.rest.param.PageParam;
import cn.bootx.platform.core.rest.result.PageResult;
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.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.transaction.AllocDetailManager;
import org.dromara.daxpay.service.dao.allocation.transaction.AllocOrderManager;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocOrder;
import org.dromara.daxpay.service.entity.order.pay.PayOrder;
import org.dromara.daxpay.service.param.order.allocation.AllocOrderQuery;
import org.dromara.daxpay.service.result.allocation.order.AllocDetailVo;
import org.dromara.daxpay.service.result.allocation.order.AllocOrderVo;
import org.dromara.daxpay.service.service.order.pay.PayOrderQueryService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 分账订单查询服务类
@@ -30,6 +40,7 @@ public class AllocOrderQueryService {
private final AllocDetailManager allocOrderDetailManager;
private final AllocOrderManager allocationOrderManager;
private final PayOrderQueryService payOrderQueryService;
/**
* 分页查询
@@ -59,5 +70,33 @@ public class AllocOrderQueryService {
return allocOrderDetailManager.findById(id).map(AllocDetail::toResult).orElseThrow(() -> new DataNotExistException("分账订单明细不存在"));
}
/**
* 获取发起分账或完结的明细
*/
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());
}
/**
* 获取并检查支付订单
*/
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;
}
}

View File

@@ -2,6 +2,7 @@ package org.dromara.daxpay.service.service.allocation.order;
import cn.bootx.platform.core.exception.ValidationFailedException;
import cn.bootx.platform.core.util.BigDecimalUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -9,18 +10,22 @@ import org.dromara.daxpay.core.enums.AllocDetailResultEnum;
import org.dromara.daxpay.core.enums.AllocationResultEnum;
import org.dromara.daxpay.core.enums.AllocationStatusEnum;
import org.dromara.daxpay.core.enums.PayAllocStatusEnum;
import org.dromara.daxpay.core.param.allocation.transaction.ReceiverParam;
import org.dromara.daxpay.core.exception.DataErrorException;
import org.dromara.daxpay.core.param.allocation.transaction.AllocationParam;
import org.dromara.daxpay.core.param.allocation.transaction.AllocationParam.ReceiverParam;
import org.dromara.daxpay.core.util.TradeNoGenerateUtil;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocOrder;
import org.dromara.daxpay.service.result.allocation.receiver.AllocGroupReceiverVo;
import org.dromara.daxpay.service.dao.allocation.receiver.AllocGroupManager;
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.AllocOrderManager;
import org.dromara.daxpay.service.dao.order.pay.PayOrderManager;
import org.dromara.daxpay.service.entity.allocation.receiver.AllocGroup;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocAndDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocOrder;
import org.dromara.daxpay.service.entity.order.pay.PayOrder;
import org.dromara.daxpay.service.result.allocation.receiver.AllocGroupReceiverVo;
import org.dromara.daxpay.service.service.allocation.receiver.AllocGroupService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -45,6 +50,10 @@ public class AllocOrderService {
private final AllocOrderManager transactionManager;
private final AllocGroupManager groupManager;
private final AllocGroupService allocGroupService;
private final AllocDetailManager transactionDetailManager;
private final PayOrderManager payOrderManager;
@@ -181,5 +190,49 @@ public class AllocOrderService {
transactionManager.save(allocOrder);
return new AllocAndDetail(allocOrder,details);
}
/**
* 根据新传入的分账订单更新订单信息
*/
@Transactional(rollbackFor = Exception.class)
public void updateOrder(AllocationParam allocationParam, AllocOrder orderExtra) {
// 扩展信息
orderExtra.setClientIp(allocationParam.getClientIp())
.setNotifyUrl(allocationParam.getNotifyUrl())
.setAttach(allocationParam.getAttach())
.setReqTime(allocationParam.getReqTime());
transactionManager.updateById(orderExtra);
}
/**
* 检查并构建分账订单相关信息
*/
@Transactional(rollbackFor = Exception.class)
public AllocAndDetail checkAndCreateAlloc(AllocationParam param, PayOrder payOrder){
// 创建分账单和明细并保存, 同时更新支付订单状态 使用事务
AllocAndDetail orderAndDetail;
// 判断是否传输了分账接收方列表
if (CollUtil.isNotEmpty(param.getReceivers())) {
orderAndDetail = this.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 = allocGroupService.findReceiversByGroups(allocationGroup.getId());
orderAndDetail = this.createAndUpdate(param ,payOrder, receiversByGroups);
}
return orderAndDetail;
}
}

View File

@@ -1,124 +0,0 @@
package org.dromara.daxpay.service.service.allocation.order;
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.AllocOrderManager;
import org.dromara.daxpay.service.entity.allocation.receiver.AllocGroup;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocOrder;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocAndDetail;
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 AllocationAssistService {
private final AllocOrderManager transactionManager;
private final PayOrderQueryService payOrderQueryService;
private final AllocGroupManager groupManager;
private final AllocGroupService allocationGroupService;
private final AllocOrderService allocOrderService;
private final AllocDetailManager allocOrderDetailManager;
/**
* 根据新传入的分账订单更新订单信息
*/
@Transactional(rollbackFor = Exception.class)
public void updateOrder(AllocationParam allocationParam, AllocOrder 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 AllocAndDetail checkAndCreateAlloc(AllocationParam param, PayOrder payOrder){
// 创建分账单和明细并保存, 同时更新支付订单状态 使用事务
AllocAndDetail 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

@@ -3,6 +3,7 @@ package org.dromara.daxpay.service.strategy;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.dromara.daxpay.service.bo.allocation.AllocSyncResultBo;
import org.dromara.daxpay.service.bo.allocation.AllocStartResultBo;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocOrder;
@@ -47,4 +48,10 @@ public abstract class AbsAllocationStrategy implements PaymentStrategy{
*/
public abstract void finish();
/**
* 同步状态
*/
public abstract AllocSyncResultBo doSync();
}