feat 分账相关接口

This commit is contained in:
DaxPay
2024-05-20 13:05:07 +08:00
parent 4c6262be05
commit d0da636394
14 changed files with 135 additions and 107 deletions

View File

@@ -3,22 +3,26 @@
- [x] 新增资金流水
- [x] 增加对超时订单进行处理(数据库同步)
- [ ] 支持分账组分账和自己传接收方进行分账
- [ ] 分账组管理提供接口调用
- [ ] 添加分账接收方
- [ ] 查询分账接收方
- [ ] 删除分账接收方
- [ ] 启动分账
- [ ] 分账查询
- [ ] 纷争完结
- [ ] 分账完结
- [ ] DEMO增加获取微信OpenID和支付宝OpenId功能
- [ ] 分账接收方管理提供接口调用
- [ ] 分账情况查询, 分账结果/剩余可分账金额
- [ ] 分账回调处理
- [ ] 分账组管理提供接口调用
- [ ] 分账通知
- [ ] 分账通知发送功能
- [ ] 分账支持手动和自动分账两种
- [ ] 金额过小不进行分账, 增加新状态, 金额小于0.01元直接忽略
- [ ] 增加收单收银台功能
- 2.0.7: 对账完善和系统优化
- [ ] 对账提供外部接口调用
- [ ] 下载系统账单
- [ ] 增加资金对账单功能
- [ ] 支付通道两个独立的配置进行合并为一个
2.1.x 版本内容
- [ ] 对账回退及退款
@@ -33,7 +37,6 @@
- [ ] 同步接口
- [ ] 对账接口
- [ ] 增加通道开通能力管理,在操作发起前的校验时提前进行拦截
- [ ] 支付通道两个独立的配置进行合并为一个
**任务池**
- [x] 对账改造:

View File

@@ -144,28 +144,28 @@ public class AggregateService {
public WxJsapiSignResult wxJsapiPrePay(String aggregateCode, String openId) {
AggregatePayInfo aggregatePayInfo = getInfo(aggregateCode);
// 拼装支付发起参数
PayParam simplePayParam = new PayParam();
simplePayParam.setBizOrderNo(aggregatePayInfo.getBizOrderNo());
simplePayParam.setTitle(aggregatePayInfo.getTitle());
simplePayParam.setAmount(aggregatePayInfo.getAmount());
simplePayParam.setChannel(PayChannelEnum.WECHAT.getCode());
simplePayParam.setMethod(PayMethodEnum.JSAPI.getCode());
PayParam payParam = new PayParam();
payParam.setBizOrderNo(aggregatePayInfo.getBizOrderNo());
payParam.setTitle(aggregatePayInfo.getTitle());
payParam.setAmount(aggregatePayInfo.getAmount());
payParam.setChannel(PayChannelEnum.WECHAT.getCode());
payParam.setMethod(PayMethodEnum.JSAPI.getCode());
// 设置微信专属请求参数
WeChatPayParam weChatPayParam = new WeChatPayParam();
weChatPayParam.setOpenId(openId);
simplePayParam.setExtraParam(weChatPayParam);
payParam.setExtraParam(weChatPayParam);
String ip = Optional.ofNullable(WebServletUtil.getRequest())
.map(ServletUtil::getClientIP)
.orElse("127.0.0.1");
simplePayParam.setClientIp(ip);
payParam.setClientIp(ip);
// 异步回调地址
simplePayParam.setNotifyUrl(StrUtil.format("{}/result/success", daxPayDemoProperties.getFrontH5Url()));
payParam.setNotifyUrl(StrUtil.format("{}/result/success", daxPayDemoProperties.getFrontH5Url()));
// 同步回调地址
simplePayParam.setReturnUrl(StrUtil.format("{}/result/success", daxPayDemoProperties.getFrontH5Url()));
payParam.setReturnUrl(StrUtil.format("{}/result/success", daxPayDemoProperties.getFrontH5Url()));
DaxPayResult<PayModel> execute = DaxPayKit.execute(simplePayParam);
DaxPayResult<PayModel> execute = DaxPayKit.execute(payParam);
// 判断是否支付成功
if (execute.getCode() != 0){
throw new BizException(execute.getMsg());
@@ -180,6 +180,9 @@ public class AggregateService {
AggregatePayInfo aggregatePayInfo = getInfo(code);
PayParam payParam = new PayParam();
payParam.setBizOrderNo(aggregatePayInfo.getBizOrderNo());
// 如果开启分账默认为自动分账
payParam.setAutoAllocation(aggregatePayInfo.getAllocation());
payParam.setAllocation(aggregatePayInfo.getAllocation());
payParam.setTitle(aggregatePayInfo.getTitle());
payParam.setAmount(aggregatePayInfo.getAmount());
payParam.setChannel(PayChannelEnum.ALI.getCode());
@@ -218,23 +221,25 @@ public class AggregateService {
.multiply(BigDecimal.valueOf(100))
.intValue();
PayParam simplePayParam = new PayParam();
simplePayParam.setBizOrderNo(param.getBizOrderNo());
simplePayParam.setAllocation(param.getAllocation());
simplePayParam.setTitle(param.getTitle());
simplePayParam.setAmount(amount);
simplePayParam.setChannel(payChannel.getCode());
simplePayParam.setMethod(PayMethodEnum.BARCODE.getCode());
PayParam payParam = new PayParam();
payParam.setBizOrderNo(param.getBizOrderNo());
payParam.setAllocation(param.getAllocation());
// 如果开启分账默认为自动分账
payParam.setAutoAllocation(param.getAllocation());
payParam.setTitle(param.getTitle());
payParam.setAmount(amount);
payParam.setChannel(payChannel.getCode());
payParam.setMethod(PayMethodEnum.BARCODE.getCode());
BarCodePayParam barCodePayParam = new BarCodePayParam();
barCodePayParam.setAuthCode(param.getAuthCode());
simplePayParam.setExtraParam(barCodePayParam);
payParam.setExtraParam(barCodePayParam);
String ip = Optional.ofNullable(WebServletUtil.getRequest())
.map(ServletUtil::getClientIP)
.orElse("127.0.0.1");
simplePayParam.setClientIp(ip);
payParam.setClientIp(ip);
DaxPayResult<PayModel> execute = DaxPayKit.execute(simplePayParam);
DaxPayResult<PayModel> execute = DaxPayKit.execute(payParam);
// 判断是否支付成功
if (execute.getCode() != 0){

View File

@@ -5,9 +5,9 @@ import cn.bootx.platform.common.core.rest.Res;
import cn.bootx.platform.common.core.rest.ResResult;
import cn.bootx.platform.common.core.rest.dto.LabelValue;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.daxpay.single.param.payment.allocation.AllocationStartParam;
import cn.daxpay.single.param.payment.allocation.AllocationSyncParam;
import cn.daxpay.single.param.payment.allocation.AllocationFinishParam;
import cn.daxpay.single.param.payment.allocation.AllocationResetParam;
import cn.daxpay.single.service.core.order.allocation.service.AllocationOrderService;
import cn.daxpay.single.service.core.payment.allocation.service.AllocationService;
import cn.daxpay.single.service.dto.order.allocation.AllocationOrderDetailDto;
@@ -80,19 +80,19 @@ public class AllocationOrderController {
@Operation(summary = "分账完结")
@PostMapping("/finish")
public ResResult<Void> finish(Long id){
public ResResult<Void> finish(String allocationNo){
AllocationFinishParam param = new AllocationFinishParam();
param.setOrderId(id);
param.setAllocationNo(allocationNo);
allocationService.finish(param);
return Res.ok();
}
@Operation(summary = "分账重试")
@Operation(summary = "重新发起分账")
@PostMapping("/retry")
public ResResult<Void> retryAllocation(Long id){
AllocationResetParam param = new AllocationResetParam();
param.setOrderId(id);
allocationService.retryAllocation(param);
public ResResult<Void> retryAllocation(String bizAllocationNo){
AllocationStartParam param = new AllocationStartParam();
param.setBizAllocationNo(bizAllocationNo);
allocationService.allocation(param);
return Res.ok();
}
}

View File

@@ -20,6 +20,8 @@ public interface PaymentApiCode {
String SYNC_PAY = "syncPay";
/** 退款同步 */
String SYNC_REFUND = "syncRefund";
/** 分账同步 */
String SYNC_ALLOCATION = "syncAllocation";
/** 查询支付订单 */
String QUERY_PAY_ORDER = "queryPayOrder";
/** 查询退款订单 */

View File

@@ -15,8 +15,8 @@ import lombok.EqualsAndHashCode;
@Schema(title = "分账请求参数")
public class AllocationFinishParam extends PaymentCommonParam {
@Schema(description = "分账订单ID")
private Long orderId;
@Schema(description = "商户分账单号")
private String bizAllocationNo;
@Schema(description = "分账单号")
private String allocationNo;

View File

@@ -1,22 +0,0 @@
package cn.daxpay.single.param.payment.allocation;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 重新分账参数
* @author xxm
* @since 2024/4/15
*/
@Data
@Accessors(chain = true)
@Schema(title = "重新分账参数")
public class AllocationResetParam {
@Schema(description = "分账订单ID")
private Long orderId;
@Schema(description = "分账单号")
private String allocationNo;
}

View File

@@ -17,6 +17,7 @@ import javax.validation.constraints.NotBlank;
@Schema(title = "开始分账请求参数")
public class AllocationStartParam extends PaymentCommonParam {
/** 商户分账单号 */
@Schema(description = "商户分账单号")
@NotBlank(message = "商户分账单号不可为空")
private String bizAllocationNo;

View File

@@ -17,10 +17,7 @@ import lombok.experimental.Accessors;
@Schema(title = "分账请求结果")
public class AllocationResult extends PaymentCommonResult {
@Schema(description = "分账订单ID")
private Long orderId;
@Schema(description = "分账订单号, 如果请求时未传, 则默认使用分账订单ID")
@Schema(description = "分账订单")
private String allocationNo;
@Schema(description = "分账状态")

View File

@@ -3,6 +3,7 @@ package cn.daxpay.single.gateway.controller;
import cn.daxpay.single.code.PaymentApiCode;
import cn.daxpay.single.param.payment.allocation.AllocationFinishParam;
import cn.daxpay.single.param.payment.allocation.AllocationStartParam;
import cn.daxpay.single.param.payment.allocation.AllocationSyncParam;
import cn.daxpay.single.result.DaxResult;
import cn.daxpay.single.result.allocation.AllocationResult;
import cn.daxpay.single.service.annotation.PaymentApi;
@@ -34,7 +35,7 @@ public class UniAllocationController {
private final AllocationReceiverService receiverService;
@PaymentApi(PaymentApiCode.ALLOCATION)
@Operation(summary = "触发分账接口")
@Operation(summary = "触发分账")
@PostMapping("/open")
public DaxResult<AllocationResult> open(@RequestBody AllocationStartParam param){
return DaxRes.ok(allocationService.allocation(param));
@@ -48,31 +49,42 @@ public class UniAllocationController {
return DaxRes.ok();
}
@PaymentApi(PaymentApiCode.ALLOCATION_RECEIVER_QUERY)
@Operation(summary = "分账接收方查询接口")
@PostMapping("/allocationReceiverQuery")
public DaxResult<Void> allocationReceiverQuery(@RequestBody AllocationReceiverParam param){
@PaymentApi(PaymentApiCode.SYNC_ALLOCATION)
@Operation(summary = "分账同步接口")
@PostMapping("/sync")
public DaxResult<Void> sync(@RequestBody AllocationSyncParam param){
allocationService.sync(param);
return DaxRes.ok();
}
@PaymentApi(PaymentApiCode.QUERY_ALLOCATION_ORDER)
@Operation(summary = "分账查询接口")
@PostMapping("/query")
public DaxResult<Void> query(@RequestBody AllocationFinishParam param){
// allocationService.query(param);
return DaxRes.ok();
}
@PaymentApi(PaymentApiCode.ALLOCATION_RECEIVER_ADD)
@Operation(summary = "添加分账接收方接口")
@PostMapping("/allocationReceiverAdd")
public DaxResult<Void> allocationReceiverAdd(@RequestBody AllocationReceiverParam param){
@PostMapping("/receiver/add")
public DaxResult<Void> receiverAdd(@RequestBody AllocationReceiverParam param){
return DaxRes.ok();
}
@PaymentApi(PaymentApiCode.ALLOCATION_RECEIVER_REMOVE)
@Operation(summary = "删除分账接收方接口")
@PostMapping("/allocationReceiverRemove")
public DaxResult<Void> allocationReceiverRemove(@RequestBody AllocationReceiverParam param){
@PostMapping("/receiver/remove")
public DaxResult<Void> receiverRemove(@RequestBody AllocationReceiverParam param){
return DaxRes.ok();
}
@PaymentApi(PaymentApiCode.ALLOCATION_RECEIVER_QUERY)
@Operation(summary = "查询分账接收方接口")
@PostMapping("/allocationReceiverRemoveByGateway")
public DaxResult<Void> allocationReceiverRemoveByGateway(@RequestBody AllocationReceiverParam param){
@PostMapping("/receiver/query")
public DaxResult<Void> receiveQuery(@RequestBody AllocationReceiverParam param){
return DaxRes.ok();
}

View File

@@ -70,15 +70,16 @@ public class PayOrderManager extends BaseManager<PayOrderMapper, PayOrder> {
}
/**
* 查询自动分账订单记录(指定时间和状态的订单)
* 查询自动分账订单记录(指定时间和状态的订单)
*/
public List<PayOrder> findAllocation() {
public List<PayOrder> findAutoAllocation() {
List<String> status = Arrays.asList(PayStatusEnum.SUCCESS.getCode(),
PayStatusEnum.PARTIAL_REFUND.getCode(),
PayStatusEnum.REFUNDING.getCode(),
PayStatusEnum.REFUNDED.getCode());
return this.lambdaQuery()
.eq(PayOrder::getAllocation, true)
.eq(PayOrder::getAutoAllocation, true)
.eq(PayOrder::getAllocationStatus, PayOrderAllocStatusEnum.WAITING.getCode())
.in(PayOrder::getStatus, status)
.list();

View File

@@ -66,6 +66,7 @@ public class AllocationReceiverService {
);
}
/**
* 根据通道获取分账接收方类型
*/
@@ -173,4 +174,26 @@ public class AllocationReceiverService {
manager.updateById(receiver);
}
/**
* 接口添加
*/
public void add(){
}
/**
* 接口删除
*/
public void remove(){
}
/**
* 接口查询
*/
public void find(){
}
}

View File

@@ -8,7 +8,6 @@ import cn.daxpay.single.code.AllocOrderStatusEnum;
import cn.daxpay.single.code.PayOrderAllocStatusEnum;
import cn.daxpay.single.exception.pay.PayFailureException;
import cn.daxpay.single.param.payment.allocation.AllocationFinishParam;
import cn.daxpay.single.param.payment.allocation.AllocationResetParam;
import cn.daxpay.single.param.payment.allocation.AllocationStartParam;
import cn.daxpay.single.param.payment.allocation.AllocationSyncParam;
import cn.daxpay.single.result.allocation.AllocationResult;
@@ -57,7 +56,9 @@ public class AllocationService {
private final AllocationOrderService allocationOrderService;
private final AllocationOrderDetailManager allocationOrderDetailManager;
private final PayOrderQueryService payOrderQueryService;
private final LockTemplate lockTemplate;
@@ -65,14 +66,24 @@ public class AllocationService {
* 开启分账, 使用分账组进行分账
*/
public AllocationResult allocation(AllocationStartParam param) {
PayOrder payOrder = this.getAndCheckPayOrder(param);
return this.allocation(payOrder, param);
// 判断是否已经有分账订单
AllocationOrder allocationOrder = allocationOrderManager.findByBizAllocationNo(param.getBizAllocationNo())
.orElse(null);
if (Objects.nonNull(allocationOrder)){
// 重复分账
return this.retryAllocation(allocationOrder);
} else {
// 开启分账
PayOrder payOrder = this.getAndCheckPayOrder(param);
return this.allocation(param, payOrder);
}
}
/**
* 开启分账, 未传输默认分账组, 则使用默认该通道默认分账组
* 开启分账, 未传输分账组, 则使用默认该通道默认分账组
*/
public AllocationResult allocation(PayOrder payOrder, AllocationStartParam param) {
public AllocationResult allocation(AllocationStartParam param,PayOrder payOrder) {
LockInfo lock = lockTemplate.lock("payment:allocation:" + payOrder.getId(),10000,200);
if (Objects.isNull(lock)){
throw new RepetitiveOperationException("分账发起处理中,请勿重复操作");
@@ -115,8 +126,7 @@ public class AllocationService {
.getOutAllocationNo();
order.setOutAllocationNo(gatewayNo);
allocationOrderManager.updateById(order);
return new AllocationResult().setOrderId(order.getId())
return new AllocationResult()
.setAllocationNo(order.getAllocationNo())
.setStatus(order.getStatus());
} finally {
@@ -127,17 +137,8 @@ public class AllocationService {
/**
* 重新分账
*/
public void retryAllocation(AllocationResetParam param){
AllocationOrder allocationOrder;
if (Objects.nonNull(param.getOrderId())){
allocationOrder = allocationOrderManager.findById(param.getOrderId())
.orElseThrow(() -> new DataNotExistException("未查询到分账单信息"));
} else {
allocationOrder = allocationOrderManager.findByAllocationNo(param.getAllocationNo())
.orElseThrow(() -> new DataNotExistException("未查询到分账单信息"));
}
LockInfo lock = lockTemplate.lock("payment:allocation:" + allocationOrder.getOrderId(),10000,200);
private AllocationResult retryAllocation(AllocationOrder order){
LockInfo lock = lockTemplate.lock("payment:allocation:" + order.getOrderId(),10000,200);
if (Objects.isNull(lock)){
throw new RepetitiveOperationException("分账发起处理中,请勿重复操作");
}
@@ -146,31 +147,34 @@ public class AllocationService {
List<String> list = Arrays.asList(AllocOrderStatusEnum.ALLOCATION_END.getCode(),
AllocOrderStatusEnum.ALLOCATION_FAILED.getCode(),
AllocOrderStatusEnum.ALLOCATION_PROCESSING.getCode());
if (!list.contains(allocationOrder.getStatus())){
if (!list.contains(order.getStatus())){
throw new PayFailureException("分账单状态错误");
}
List<AllocationOrderDetail> details = allocationOrderDetailManager.findAllByOrderId(allocationOrder.getId());
List<AllocationOrderDetail> details = allocationOrderDetailManager.findAllByOrderId(order.getId());
// 创建分账策略并初始化
AbsAllocationStrategy allocationStrategy = AllocationFactory.create(allocationOrder.getChannel());
allocationStrategy.initParam(allocationOrder, details);
AbsAllocationStrategy allocationStrategy = AllocationFactory.create(order.getChannel());
allocationStrategy.initParam(order, details);
// 分账预处理
allocationStrategy.doBeforeHandler();
try {
// 重复分账处理
allocationStrategy.allocation();
allocationOrder.setStatus(AllocOrderStatusEnum.ALLOCATION_PROCESSING.getCode())
order.setStatus(AllocOrderStatusEnum.ALLOCATION_PROCESSING.getCode())
.setErrorMsg(null);
} catch (Exception e) {
log.error("重新分账出现错误:", e);
// 失败
allocationOrder.setStatus(AllocOrderStatusEnum.ALLOCATION_FAILED.getCode())
order.setStatus(AllocOrderStatusEnum.ALLOCATION_FAILED.getCode())
.setErrorMsg(e.getMessage());
}
allocationOrderManager.updateById(allocationOrder);
allocationOrderManager.updateById(order);
return new AllocationResult()
.setAllocationNo(order.getAllocationNo())
.setStatus(order.getStatus());
} finally {
lockTemplate.releaseLock(lock);
}
@@ -181,11 +185,11 @@ public class AllocationService {
*/
public void finish(AllocationFinishParam param) {
AllocationOrder allocationOrder;
if (Objects.nonNull(param.getOrderId())){
allocationOrder = allocationOrderManager.findById(param.getOrderId())
if (Objects.nonNull(param.getAllocationNo())){
allocationOrder = allocationOrderManager.findByAllocationNo(param.getAllocationNo())
.orElseThrow(() -> new DataNotExistException("未查询到分账单信息"));
} else {
allocationOrder = allocationOrderManager.findByAllocationNo(param.getAllocationNo())
allocationOrder = allocationOrderManager.findByBizAllocationNo(param.getBizAllocationNo())
.orElseThrow(() -> new DataNotExistException("未查询到分账单信息"));
}
this.finish(allocationOrder);

View File

@@ -3,11 +3,12 @@ package cn.daxpay.single.service.core.record.flow.entity;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
import cn.bootx.table.modify.annotation.DbColumn;
import cn.bootx.table.modify.annotation.DbTable;
import cn.daxpay.single.code.PayChannelEnum;
import cn.daxpay.single.service.code.TradeFlowRecordTypeEnum;
import cn.daxpay.single.service.core.record.flow.convert.TradeFlowRecordConvert;
import cn.daxpay.single.service.dto.record.flow.TradeFlowRecordDto;
import io.swagger.v3.oas.annotations.media.Schema;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@@ -20,7 +21,8 @@ import lombok.experimental.Accessors;
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "资金流水记录")
@TableName("pay_trade_flow_record")
@DbTable(comment = "资金流水记录")
public class TradeFlowRecord extends MpCreateEntity implements EntityBaseFunction<TradeFlowRecordDto> {
/** 订单标题 */

View File

@@ -26,11 +26,11 @@ public class AllocationAutoStartTask implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
for (PayOrder payOrder : payOrderManager.findAllocation()) {
for (PayOrder payOrder : payOrderManager.findAutoAllocation()) {
AllocationStartParam param = new AllocationStartParam();
param.setBizAllocationNo(OrderNoGenerateUtil.allocation());
try {
allocationService.allocation(payOrder, param);
allocationService.allocation(param, payOrder);
} catch (Exception e) {
log.warn("自动分账失败, 支付订单号: {}", payOrder.getOrderNo());
log.warn("自动分账失败:{}", e.getMessage());