feat 接口订单和定时任务调整

This commit is contained in:
DaxPay
2024-05-17 18:03:52 +08:00
parent 9699661a1f
commit 4c6262be05
27 changed files with 486 additions and 188 deletions

View File

@@ -99,4 +99,13 @@ public class PayOrderManager extends BaseManager<PayOrderMapper, PayOrder> {
return baseMapper.getTalAmount(generator);
}
/**
* 查询当前超时的未支付订单
*/
public List<PayOrder> queryExpiredOrder() {
return lambdaQuery()
.eq(PayOrder::getStatus, PayStatusEnum.REFUNDING.getCode())
.lt(PayOrder::getExpiredTime, LocalDateTime.now())
.list();
}
}

View File

@@ -92,6 +92,7 @@ public class PayOrderQueryService {
return PayOrderConvert.CONVERT.convertResult(payOrder);
}
/**
* 查询支付总金额
*/

View File

@@ -11,6 +11,7 @@ import cn.daxpay.single.service.core.order.pay.entity.PayOrderExtra;
import cn.daxpay.single.service.core.order.pay.service.PayOrderService;
import cn.daxpay.single.service.core.payment.notice.service.ClientNoticeService;
import cn.daxpay.single.service.core.payment.pay.factory.PayStrategyFactory;
import cn.daxpay.single.service.core.record.flow.service.TradeFlowRecordService;
import cn.daxpay.single.service.func.AbsPayStrategy;
import cn.daxpay.single.util.PayUtil;
import cn.hutool.extra.spring.SpringUtil;
@@ -45,6 +46,8 @@ public class PayService {
private final PayOrderExtraManager payOrderExtraManager;
private final TradeFlowRecordService tradeFlowRecordService;
private final LockTemplate lockTemplate;
/**
@@ -129,8 +132,9 @@ public class PayService {
payOrder.setErrorCode(null);
payOrder.setErrorMsg(null);
payOrderService.updateById(payOrder);
// 如果支付完成 发送通知
// 如果支付完成 发送通知, 记录流水
if (Objects.equals(payOrder.getStatus(), SUCCESS.getCode())){
tradeFlowRecordService.savePay(payOrder);
clientNoticeService.registerPayNotice(payOrder, payInfo.getPayOrderExtra());
}
return payAssistService.buildResult(payOrder);
@@ -181,8 +185,9 @@ public class PayService {
payOrder.setErrorMsg(null);
payOrder.setErrorCode(null);
payOrderService.updateById(payOrder);
// 如果支付完成 发送通知
// 如果支付完成 发送通知, 记录流水
if (Objects.equals(payOrder.getStatus(), SUCCESS.getCode())){
tradeFlowRecordService.savePay(payOrder);
clientNoticeService.registerPayNotice(payOrder, payOrderExtra);
}
return payAssistService.buildResult(payOrder);

View File

@@ -19,6 +19,7 @@ import cn.daxpay.single.service.core.order.refund.entity.RefundOrder;
import cn.daxpay.single.service.core.order.refund.entity.RefundOrderExtra;
import cn.daxpay.single.service.core.payment.notice.service.ClientNoticeService;
import cn.daxpay.single.service.core.payment.refund.factory.RefundStrategyFactory;
import cn.daxpay.single.service.core.record.flow.service.TradeFlowRecordService;
import cn.daxpay.single.service.func.AbsRefundStrategy;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
@@ -54,6 +55,8 @@ public class RefundService {
private final PayOrderQueryService payOrderQueryService;
private final TradeFlowRecordService tradeFlowRecordService;
private final RefundOrderExtraManager refundOrderExtraManager;
private final LockTemplate lockTemplate;
@@ -199,16 +202,23 @@ public class RefundService {
*/
@Transactional(rollbackFor = Exception.class)
public void successHandler(RefundOrder refundOrder, PayOrder payOrder) {
RefundLocal refundInfo = PaymentContextLocal.get().getRefundInfo();
RefundLocal refundInfo = PaymentContextLocal.get()
.getRefundInfo();
// 剩余可退款余额
int refundableBalance = payOrder.getRefundableBalance();
// 设置支付订单状态
// 退款状态为退款中
if (refundInfo.getStatus() == RefundStatusEnum.PROGRESS) {
payOrder.setStatus(PayStatusEnum.REFUNDING.getCode());
} else if (refundableBalance == 0) {
payOrder.setStatus(PayStatusEnum.REFUNDED.getCode());
} else {
payOrder.setStatus(PayStatusEnum.PARTIAL_REFUND.getCode());
}
// 退款状态为成功
else {
if (refundableBalance == 0) {
payOrder.setStatus(PayStatusEnum.REFUNDED.getCode());
} else {
payOrder.setStatus(PayStatusEnum.PARTIAL_REFUND.getCode());
}
// 记录流水
tradeFlowRecordService.saveRefund(refundOrder);
}
payOrderService.updateById(payOrder);

View File

@@ -10,6 +10,7 @@ import cn.daxpay.single.service.core.order.pay.service.PayOrderService;
import cn.daxpay.single.service.core.payment.notice.service.ClientNoticeService;
import cn.daxpay.single.service.core.payment.repair.factory.PayRepairStrategyFactory;
import cn.daxpay.single.service.core.payment.repair.result.PayRepairResult;
import cn.daxpay.single.service.core.record.flow.service.TradeFlowRecordService;
import cn.daxpay.single.service.core.record.repair.entity.PayRepairRecord;
import cn.daxpay.single.service.core.record.repair.service.PayRepairRecordService;
import cn.daxpay.single.service.func.AbsPayRepairStrategy;
@@ -41,6 +42,7 @@ public class PayRepairService {
private final PayRepairRecordService recordService;
private final LockTemplate lockTemplate;
private final TradeFlowRecordService tradeFlowRecordService;
/**
* 修复支付单
@@ -117,6 +119,7 @@ public class PayRepairService {
order.setStatus(PayStatusEnum.SUCCESS.getCode())
.setPayTime(payTime)
.setCloseTime(null);
tradeFlowRecordService.savePay(order);
payOrderService.updateById(order);
}

View File

@@ -13,6 +13,7 @@ import cn.daxpay.single.service.core.order.refund.dao.RefundOrderManager;
import cn.daxpay.single.service.core.order.refund.entity.RefundOrder;
import cn.daxpay.single.service.core.payment.notice.service.ClientNoticeService;
import cn.daxpay.single.service.core.payment.repair.result.RefundRepairResult;
import cn.daxpay.single.service.core.record.flow.service.TradeFlowRecordService;
import cn.daxpay.single.service.core.record.repair.entity.PayRepairRecord;
import cn.daxpay.single.service.core.record.repair.service.PayRepairRecordService;
import cn.daxpay.single.util.OrderNoGenerateUtil;
@@ -49,6 +50,7 @@ public class RefundRepairService {
private final PayRepairRecordService recordService;
private final LockTemplate lockTemplate;
private final TradeFlowRecordService tradeFlowRecordService;
/**
* 修复退款单
@@ -113,11 +115,13 @@ public class RefundRepairService {
.setFinishTime(repairInfo.getFinishTime());
payOrder.setStatus(afterPayRefundStatus.getCode());
// 更新订单和退款相关订单
payOrderService.updateById(payOrder);
refundOrderManager.updateById(refundOrder);
// 记录流水
tradeFlowRecordService.saveRefund(refundOrder);
// 发送通知
List<String> list = Arrays.asList(RefundStatusEnum.SUCCESS.getCode(), RefundStatusEnum.CLOSE.getCode(), RefundStatusEnum.FAIL.getCode());
if (list.contains(refundOrder.getStatus())){

View File

@@ -1,6 +1,9 @@
package cn.daxpay.single.service.core.record.flow.convert;
import cn.daxpay.single.service.core.record.flow.entity.TradeFlowRecord;
import cn.daxpay.single.service.dto.record.flow.TradeFlowRecordDto;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
*
@@ -9,4 +12,7 @@ import org.mapstruct.Mapper;
*/
@Mapper
public interface TradeFlowRecordConvert {
TradeFlowRecordConvert CONVERT = Mappers.getMapper(TradeFlowRecordConvert.class);
TradeFlowRecordDto convert(TradeFlowRecord entity);
}

View File

@@ -1,7 +1,13 @@
package cn.daxpay.single.service.core.record.flow.dao;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.common.query.generator.QueryGenerator;
import cn.daxpay.single.service.core.record.flow.entity.TradeFlowRecord;
import cn.daxpay.single.service.param.record.TradeFlowRecordQuery;
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;
@@ -15,4 +21,13 @@ import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class TradeFlowRecordManager extends BaseManager<TradeFlowRecordMapper, TradeFlowRecord> {
/**
* 分页
*/
public Page<TradeFlowRecord> page(PageParam pageParam, TradeFlowRecordQuery param){
Page<TradeFlowRecord> mpPage = MpUtil.getMpPage(pageParam, TradeFlowRecord.class);
QueryWrapper<TradeFlowRecord> generator = QueryGenerator.generator(param);
return page(mpPage, generator);
}
}

View File

@@ -1,16 +1,17 @@
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.daxpay.single.code.PayChannelEnum;
import cn.daxpay.single.service.code.TradeFlowRecordTypeEnum;
import cn.bootx.table.modify.annotation.DbColumn;
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 lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 资金流水记录
* @author xxm
@@ -20,8 +21,9 @@ import java.time.LocalDateTime;
@Data
@Accessors(chain = true)
@Schema(title = "资金流水记录")
public class TradeFlowRecord extends MpCreateEntity {
/** 支付订单标题 */
public class TradeFlowRecord extends MpCreateEntity implements EntityBaseFunction<TradeFlowRecordDto> {
/** 订单标题 */
@DbColumn(comment = "标题")
private String title;
@@ -55,7 +57,8 @@ public class TradeFlowRecord extends MpCreateEntity {
@DbColumn(comment = "三方系统交易号")
private String outTradeNo;
/** 网关完成时间 */
@DbColumn(comment = "网关完成时间")
private LocalDateTime finishTime;
@Override
public TradeFlowRecordDto toDto() {
return TradeFlowRecordConvert.CONVERT.convert(this);
}
}

View File

@@ -1,5 +1,16 @@
package cn.daxpay.single.service.core.record.flow.service;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.common.core.rest.PageResult;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.daxpay.single.service.code.TradeFlowRecordTypeEnum;
import cn.daxpay.single.service.core.order.pay.entity.PayOrder;
import cn.daxpay.single.service.core.order.refund.entity.RefundOrder;
import cn.daxpay.single.service.core.record.flow.dao.TradeFlowRecordManager;
import cn.daxpay.single.service.core.record.flow.entity.TradeFlowRecord;
import cn.daxpay.single.service.dto.record.flow.TradeFlowRecordDto;
import cn.daxpay.single.service.param.record.TradeFlowRecordQuery;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -13,4 +24,53 @@ import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class TradeFlowRecordService {
private final TradeFlowRecordManager tradeFlowRecordManager;
/**
* 分页
*/
public PageResult<TradeFlowRecordDto> page(PageParam pageParam, TradeFlowRecordQuery query){
return MpUtil.convert2DtoPageResult(tradeFlowRecordManager.page(pageParam, query));
}
/**
* 查询详情
*/
public TradeFlowRecordDto findById(Long id){
return tradeFlowRecordManager.findById(id).map(TradeFlowRecord::toDto)
.orElseThrow(()->new DataNotExistException("交易流水记录不存在"));
}
/**
* 支付记账
*/
public void savePay(PayOrder payOrder){
TradeFlowRecord tradeFlowRecord = new TradeFlowRecord()
.setTradeNo(payOrder.getOrderNo())
.setBizTradeNo(payOrder.getBizOrderNo())
.setOutTradeNo(payOrder.getOutOrderNo())
.setChannel(payOrder.getChannel())
.setTitle(payOrder.getTitle())
.setType(TradeFlowRecordTypeEnum.PAY.getCode())
.setAmount(payOrder.getAmount());
tradeFlowRecordManager.save(tradeFlowRecord);
}
/**
* 退款记账
*/
public void saveRefund(RefundOrder refundOrder){
TradeFlowRecord tradeFlowRecord = new TradeFlowRecord()
.setTradeNo(refundOrder.getRefundNo())
.setBizTradeNo(refundOrder.getBizRefundNo())
.setOutTradeNo(refundOrder.getOutRefundNo())
.setChannel(refundOrder.getChannel())
.setTitle(refundOrder.getTitle())
.setType(TradeFlowRecordTypeEnum.PAY.getCode())
.setAmount(refundOrder.getAmount());
tradeFlowRecordManager.save(tradeFlowRecord);
}
}

View File

@@ -0,0 +1,53 @@
package cn.daxpay.single.service.dto.record.flow;
import cn.bootx.table.modify.annotation.DbColumn;
import cn.daxpay.single.code.PayChannelEnum;
import cn.daxpay.single.service.code.TradeFlowRecordTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 资金流水记录
* @author xxm
* @since 2024/5/17
*/
@Data
@Accessors(chain = true)
@Schema(title = "资金流水记录")
public class TradeFlowRecordDto {
/** 订单标题 */
@DbColumn(comment = "标题")
private String title;
/** 金额 */
@DbColumn(comment = "金额")
private Integer amount;
/**
* 业务类型
* @see TradeFlowRecordTypeEnum
*/
@DbColumn(comment = "业务类型")
private String type;
/**
* 支付通道
* @see PayChannelEnum
*/
@DbColumn(comment = "支付通道")
private String channel;
/** 本地交易号 */
@DbColumn(comment = "本地订单号")
private String tradeNo;
/** 商户交易号 */
@DbColumn(comment = "商户交易号")
private String bizTradeNo;
/** 三方系统交易号 */
@DbColumn(comment = "三方系统交易号")
private String outTradeNo;
}

View File

@@ -0,0 +1,18 @@
package cn.daxpay.single.service.param.record;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 流水记录查询类
* @author xxm
* @since 2024/5/17
*/
@Data
@Accessors(chain = true)
@Schema(title = "流水记录查询类")
public class TradeFlowRecordQuery {
}

View File

@@ -0,0 +1,45 @@
package cn.daxpay.single.service.task;
import cn.daxpay.single.service.core.order.pay.dao.PayOrderManager;
import cn.daxpay.single.service.core.order.pay.entity.PayOrder;
import cn.daxpay.single.service.core.payment.sync.service.PaySyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.stereotype.Component;
import java.util.List;
/**
*
* @author xxm
* @since 2024/5/17
*/
@Slf4j
@Component
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
@RequiredArgsConstructor
public class PayExpiredByDbTimeTask implements Job {
private final PaySyncService paySyncService;
private final PayOrderManager payOrderManager;
@Override
public void execute(JobExecutionContext context) {
// 从数据库查询获取超时的任务对象
List<PayOrder> payOrders = payOrderManager.queryExpiredOrder();
for (PayOrder order : payOrders) {
try {
// 设置补偿来源为定时任务
paySyncService.syncPayOrder(order);
} catch (Exception e) {
log.error("超时取消任务 异常", e);
}
}
}
}

View File

@@ -9,21 +9,25 @@ import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Service;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.Set;
/**
* 支付超时处理
* 支付超时处理(手动注册)
* @author xxm
* @since 2024/1/2
*/
@Slf4j
@Service
@Component
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
@RequiredArgsConstructor
public class PayExpiredTimeTask implements Job {
private final PayExpiredTimeRepository repository;

View File

@@ -1,6 +1,7 @@
package cn.daxpay.single.service.task;
import cn.daxpay.single.service.task.service.ReconcileTaskService;
import cn.daxpay.single.service.core.order.reconcile.entity.ReconcileOrder;
import cn.daxpay.single.service.core.payment.reconcile.service.ReconcileService;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.Getter;
@@ -24,7 +25,7 @@ import java.util.Objects;
@PersistJobDataAfterExecution
@RequiredArgsConstructor
public class ReconcileTask implements Job {
private final ReconcileTaskService reconcileTaskService;
private final ReconcileService reconcileService;
/**
* 任务参数, 格式
@@ -37,7 +38,7 @@ public class ReconcileTask implements Job {
* 任务实现
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
public void execute(JobExecutionContext context) {
if (StrUtil.isBlank(parameter)){
log.warn("传输参数为空");
return;
@@ -50,9 +51,26 @@ public class ReconcileTask implements Job {
} else {
date = date.minusDays(1);
}
reconcileTaskService.reconcileTask(date,bean.channel);
this.reconcileTask(date,bean.channel);
}
/**
* 执行任务
*/
public void reconcileTask(LocalDate date, String channel){
// 1. 查询需要定时对账的通道, 创建出来对账订单
ReconcileOrder reconcileOrder = reconcileService.create(date, channel);
// 2. 执行对账任务, 下载对账单并解析, 分别存储为原始数据和通用对账数据
reconcileService.downAndSave(reconcileOrder);
// 3. 执行账单明细比对, 生成差异单
reconcileService.compare(reconcileOrder);
}
/**
* 接收参数
*/

View File

@@ -1,11 +1,15 @@
package cn.daxpay.single.service.task;
import cn.daxpay.single.service.task.service.RefundSyncTaskService;
import cn.daxpay.single.service.core.order.refund.dao.RefundOrderManager;
import cn.daxpay.single.service.core.order.refund.entity.RefundOrder;
import cn.daxpay.single.service.core.payment.sync.service.RefundSyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 退款定时同步任务 一分钟一次, 查询退款中的订单进行同步
* @author xxm
@@ -18,10 +22,22 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor
public class RefundSyncTask implements Job {
private final RefundSyncTaskService refundSyncTaskService;
private final RefundSyncService refundSyncService;
private final RefundOrderManager refundOrderManager;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
refundSyncTaskService.syncTask();
// 查询退款中的退款订单
List<RefundOrder> list = refundOrderManager.findAllByProgress();
for (RefundOrder refundOrder : list) {
try {
// 调用同步方法
refundSyncService.syncRefundOrder(refundOrder);
} catch (Exception e) {
log.warn("退款执行同步失败, ID: {}",refundOrder.getId());
log.warn("退款执行同步失败",e);
}
}
}
}

View File

@@ -1,37 +0,0 @@
package cn.daxpay.single.service.task.service;
import cn.daxpay.single.service.core.order.reconcile.entity.ReconcileOrder;
import cn.daxpay.single.service.core.payment.reconcile.service.ReconcileService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
/**
* 支付对账定时任务服务类
* @author xxm
* @since 2024/1/20
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ReconcileTaskService {
private final ReconcileService reconcileService;
/**
* 执行任务
*/
public void reconcileTask(LocalDate date, String channel){
// 1. 查询需要定时对账的通道, 创建出来对账订单
ReconcileOrder reconcileOrder = reconcileService.create(date, channel);
// 2. 执行对账任务, 下载对账单并解析, 分别存储为原始数据和通用对账数据
reconcileService.downAndSave(reconcileOrder);
// 3. 执行账单明细比对, 生成差异单
reconcileService.compare(reconcileOrder);
}
}

View File

@@ -1,42 +0,0 @@
package cn.daxpay.single.service.task.service;
import cn.daxpay.single.service.core.order.refund.dao.RefundOrderManager;
import cn.daxpay.single.service.core.order.refund.entity.RefundOrder;
import cn.daxpay.single.service.core.payment.sync.service.RefundSyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 定时
* @author xxm
* @since 2024/3/12
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class RefundSyncTaskService {
private final RefundSyncService refundSyncService;
private final RefundOrderManager refundOrderManager;
/**
* 同步任务
*/
public void syncTask(){
// 查询退款中的退款订单
List<RefundOrder> list = refundOrderManager.findAllByProgress();
for (RefundOrder refundOrder : list) {
try {
// 调用同步方法
refundSyncService.syncRefundOrder(refundOrder);
} catch (Exception e) {
log.warn("退款执行同步失败, ID: {}",refundOrder.getId());
log.warn("退款执行同步失败",e);
}
}
}
}