feat 支付对账单创建和下载, 以及定时任务类

This commit is contained in:
bootx
2024-01-21 01:04:24 +08:00
parent 36b0288386
commit 97ed6b7e25
19 changed files with 368 additions and 90 deletions

View File

@@ -0,0 +1,41 @@
package cn.bootx.platform.daxpay.admin.controller.record;
import cn.bootx.platform.common.core.rest.Res;
import cn.bootx.platform.common.core.rest.ResResult;
import cn.bootx.platform.common.core.util.ValidationUtil;
import cn.bootx.platform.daxpay.service.core.order.reconcile.service.PayReconcileOrderService;
import cn.bootx.platform.daxpay.service.core.payment.reconcile.service.PayReconcileService;
import cn.bootx.platform.daxpay.service.param.reconcile.CreateReconcileOrderParam;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* 支付对账
* @author xxm
* @since 2024/1/18
*/
@Tag(name = "支付对账控制器")
@RestController
@RequestMapping("/record/reconcile")
@RequiredArgsConstructor
public class PayReconcileOrderController {
private final PayReconcileService reconcileService;
private final PayReconcileOrderService reconcileOrderService;
@Operation(summary = "手动创建支付对账订单")
@PostMapping("/createOrder")
public ResResult<Void> createOrder(@RequestBody CreateReconcileOrderParam param){
ValidationUtil.validateParam(param);
reconcileOrderService.create(param.getDate(), param.getChannel());
return Res.ok();
}
@Operation(summary = "手动触发对账文件下载")
@PostMapping("/downById")
public ResResult<Void> downByChannel(Long id){
reconcileService.downAndSave(id);
return Res.ok();
}
}

View File

@@ -1,20 +0,0 @@
package cn.bootx.platform.daxpay.admin.controller.record;
import cn.bootx.platform.daxpay.service.core.payment.reconcile.service.PayReconcileService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 支付对账
* @author xxm
* @since 2024/1/18
*/
@Tag(name = "支付对账控制器")
@RestController
@RequestMapping("/record/reconcile")
@RequiredArgsConstructor
public class PayReconcileRecordController {
private final PayReconcileService reconcileService;
}

View File

@@ -19,12 +19,12 @@ import java.util.Objects;
@RequiredArgsConstructor
public enum PayChannelEnum {
ALI("ali_pay", "支付宝"),
WECHAT("wechat_pay", "微信支付"),
UNION_PAY("union_pay", "云闪付"),
CASH("cash_pay", "现金支付"),
WALLET("wallet_pay", "钱包支付"),
VOUCHER("voucher_pay", "储值卡支付");
ALI("ali_pay", "支付宝", "ali"),
WECHAT("wechat_pay", "微信支付", "wx"),
UNION_PAY("union_pay", "云闪付", "uni"),
CASH("cash_pay", "现金支付",null),
WALLET("wallet_pay", "钱包支付",null),
VOUCHER("voucher_pay", "储值卡支付",null);
/** 支付通道编码 */
private final String code;
@@ -32,6 +32,9 @@ public enum PayChannelEnum {
/** 支付通道名称 */
private final String name;
/** 对账前缀 */
private final String reconcilePrefix;
/**
* 根据字符编码获取
*/

View File

@@ -49,12 +49,22 @@
<groupId>cn.bootx.platform</groupId>
<artifactId>common-spring</artifactId>
</dependency>
<!-- 查询构造器 -->
<dependency>
<groupId>cn.bootx.platform</groupId>
<artifactId>common-super-query</artifactId>
</dependency>
<!-- 定时任务(quartz) -->
<dependency>
<groupId>cn.bootx.platform</groupId>
<artifactId>common-starter-quartz</artifactId>
</dependency>
<!-- 序列生成器 -->
<dependency>
<groupId>cn.bootx.platform</groupId>
<artifactId>common-sequence</artifactId>
<version>${bootx-platform.version}</version>
</dependency>
<!-- 支付核心包-->
<dependency>

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
package cn.bootx.platform.daxpay.service.core.record.reconcile.dao;
package cn.bootx.platform.daxpay.service.core.order.reconcile.dao;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.daxpay.service.core.record.reconcile.entity.PayReconcileRecord;
import cn.bootx.platform.daxpay.service.core.order.reconcile.entity.PayReconcileOrder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
@@ -14,5 +14,5 @@ import org.springframework.stereotype.Repository;
@Slf4j
@Repository
@RequiredArgsConstructor
public class PayReconcileRecordManager extends BaseManager<PayReconcileRecordMapper, PayReconcileRecord> {
public class PayReconcileOrderManager extends BaseManager<PayReconcileOrderMapper, PayReconcileOrder> {
}

View File

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

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.service.core.record.reconcile.entity;
package cn.bootx.platform.daxpay.service.core.order.reconcile.entity;
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -15,7 +15,7 @@ import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
@TableName("pay_reconcile_detail_record")
public class PayReconcileDetailRecord extends MpCreateEntity {
public class PayReconcileDetail extends MpCreateEntity {
/** 交易状态 */
private String status;

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.service.core.record.reconcile.entity;
package cn.bootx.platform.daxpay.service.core.order.reconcile.entity;
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
import cn.bootx.table.modify.annotation.DbTable;
@@ -19,7 +19,14 @@ import java.time.LocalDate;
@Accessors(chain = true)
@DbTable(comment = "支付对账单记录")
@TableName("pay_reconcile_record")
public class PayReconcileRecord extends MpCreateEntity {
public class PayReconcileOrder extends MpCreateEntity {
/**
* 批次号
* 规则通道简称 + yyyyMMdd + 两位流水号
* 例子wx2024012001ali2024012002
*/
private String batchNo;
/** 日期 */
private LocalDate date;
@@ -28,7 +35,10 @@ public class PayReconcileRecord extends MpCreateEntity {
private String channel;
/** 对账单是否下载成功 */
private boolean success;
private boolean down;
/** 是否比对完成 */
private boolean compare;
/** 错误信息 */
private String errorMsg;

View File

@@ -0,0 +1,81 @@
package cn.bootx.platform.daxpay.service.core.order.reconcile.service;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.common.sequence.func.Sequence;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.core.order.reconcile.dao.PayReconcileOrderManager;
import cn.bootx.platform.daxpay.service.core.order.reconcile.entity.PayReconcileOrder;
import cn.hutool.core.date.DatePattern;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.util.Optional;
/**
* 支付对账订单服务
* @author xxm
* @since 2024/1/20
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PayReconcileOrderService {
private final PayReconcileOrderManager reconcileOrderManager;
private final Sequence sequence;
/**
* 更新, 开启一个新事务进行更新
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void update(PayReconcileOrder order){
reconcileOrderManager.updateById(order);
}
/**
* 根据Id查询
*/
public Optional<PayReconcileOrder> findById(Long id){
return reconcileOrderManager.findById(id);
}
/**
* 创建对账订单
*/
public PayReconcileOrder create(LocalDate date,String channel) {
// 获取通道枚举
PayChannelEnum channelEnum = PayChannelEnum.findByCode(channel);
// 判断是否为为异步通道
if (!PayChannelEnum.ASYNC_TYPE.contains(channelEnum)){
throw new PayFailureException("不支持对账的通道, 请检查");
}
// 生成批次号
String format = LocalDateTimeUtil.format(date, DatePattern.PURE_DATE_PATTERN);
String key = channelEnum.getReconcilePrefix()+format;
String seqNo = this.genSeqNo(key);
PayReconcileOrder order = new PayReconcileOrder()
.setBatchNo(seqNo)
.setChannel(channel)
.setDate(date);
reconcileOrderManager.save(order);
return order;
}
/**
* 生成批次号
* 规则:通道简称 + yyyyMMdd + 两位流水号
* 例子wx2024012001、ali2024012002
*/
private String genSeqNo(String key){
long next = sequence.next(key);
return String.format("%2d",next);
}
}

View File

@@ -1,14 +1,14 @@
package cn.bootx.platform.daxpay.service.core.payment.reconcile.service;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.daxpay.service.core.order.reconcile.entity.PayReconcileOrder;
import cn.bootx.platform.daxpay.service.core.order.reconcile.service.PayReconcileOrderService;
import cn.bootx.platform.daxpay.service.core.payment.reconcile.factory.PayReconcileStrategyFactory;
import cn.bootx.platform.daxpay.service.func.AbsReconcileStrategy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
/**
* 支付对账单下载服务
* @author xxm
@@ -18,37 +18,34 @@ import java.util.List;
@Service
@RequiredArgsConstructor
public class PayReconcileService {
private final PayReconcileOrderService reconcileOrderService;
/**
* 下载对账单
* 下载对账单并进行保存
*/
public void downAll(LocalDate date){
List<AbsReconcileStrategy> strategies = PayReconcileStrategyFactory.create();
strategies.forEach(AbsReconcileStrategy::doBeforeHandler);
public void downAndSave(Long reconcileOrderId) {
PayReconcileOrder payReconcileOrder = reconcileOrderService.findById(reconcileOrderId)
.orElseThrow(() -> new DataNotExistException("未找到对账订单"));
this.downAndSave(payReconcileOrder);
}
/**
* 手动发起对账下载
* 下载对账单并进行保存
*/
public void downByChannel(LocalDate date, String channel){
AbsReconcileStrategy absReconcileStrategy = PayReconcileStrategyFactory.create(channel);
public void downAndSave(PayReconcileOrder recordOrder) {
AbsReconcileStrategy absReconcileStrategy = PayReconcileStrategyFactory.create(recordOrder.getChannel());
absReconcileStrategy.initParam(recordOrder);
absReconcileStrategy.doBeforeHandler();
absReconcileStrategy.downAndSave(date);
try {
absReconcileStrategy.downAndSave();
recordOrder.setDown(true);
reconcileOrderService.update(recordOrder);
} catch (Exception e) {
log.error("下载对账单异常", e);
recordOrder.setErrorMsg(e.getMessage());
reconcileOrderService.update(recordOrder);
throw new RuntimeException(e);
}
}
/**
* 全部对账
*/
public void offsetting(LocalDate date){
List<AbsReconcileStrategy> strategies = PayReconcileStrategyFactory.create();
strategies.forEach(AbsReconcileStrategy::doBeforeHandler);
}
/**
* 全部对账
*/
public void offsettingByChannel(LocalDate date, String channel){
AbsReconcileStrategy absReconcileStrategy = PayReconcileStrategyFactory.create(channel);
absReconcileStrategy.doBeforeHandler();
absReconcileStrategy.offsetting(date);
}
}

View File

@@ -12,8 +12,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
/**
@@ -57,8 +55,8 @@ public class AlipayReconcileStrategy extends AbsReconcileStrategy {
* 下载对账单
*/
@Override
public void downAndSave(LocalDate date) {
String format = LocalDateTimeUtil.format(date, DatePattern.NORM_DATE_PATTERN);
public void downAndSave() {
String format = LocalDateTimeUtil.format(this.getRecordOrder().getDate(), DatePattern.NORM_DATE_PATTERN);
reconcileService.downAndSave(format);
}
}

View File

@@ -12,8 +12,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
/**
@@ -55,8 +53,8 @@ public class WechatPayReconcileStrategy extends AbsReconcileStrategy {
* 下载对账单
*/
@Override
public void downAndSave(LocalDate date) {
String format = LocalDateTimeUtil.format(date, DatePattern.PURE_DATE_PATTERN);
public void downAndSave() {
String format = LocalDateTimeUtil.format(this.getRecordOrder().getDate(), DatePattern.PURE_DATE_PATTERN);
reconcileService.downAndSave(format,this.config);
}
}

View File

@@ -1,14 +0,0 @@
package cn.bootx.platform.daxpay.service.core.record.reconcile.dao;
import cn.bootx.platform.daxpay.service.core.record.reconcile.entity.PayReconcileRecord;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
*
* @author xxm
* @since 2024/1/18
*/
@Mapper
public interface PayReconcileRecordMapper extends BaseMapper<PayReconcileRecord> {
}

View File

@@ -1,17 +1,19 @@
package cn.bootx.platform.daxpay.service.func;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import java.time.LocalDate;
import cn.bootx.platform.daxpay.service.core.order.reconcile.entity.PayReconcileOrder;
import lombok.Getter;
/**
* 支付对账策略
* @author xxm
* @since 2024/1/18
*/
@Getter
public abstract class AbsReconcileStrategy {
/** 对账订单 */
private PayReconcileOrder recordOrder;
/**
* 策略标识
@@ -25,19 +27,26 @@ public abstract class AbsReconcileStrategy {
public void doBeforeHandler() {
}
/**
* 初始化参数
*/
public void initParam(PayReconcileOrder recordOrder){
this.recordOrder = recordOrder;
}
/**
* 下载对账单到本地进行保存
*/
public abstract void downAndSave(LocalDate date);
public abstract void downAndSave();
/**
* 轧差
* 比对生成对账差异单
* 1. 远程有, 本地无 补单(追加回订单/记录差异表)
* 2. 远程无, 本地有 记录差错表
* 3. 远程有, 本地有, 但状态不一致
*/
public void offsetting(LocalDate date){
public void offsetting(){
}
}

View File

@@ -0,0 +1,28 @@
package cn.bootx.platform.daxpay.service.param.reconcile;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.LocalDate;
/**
* 创建对账订单参数
* @author xxm
* @since 2024/1/20
*/
@Data
@Accessors(chain = true)
@Schema(title = "创建对账订单参数")
public class CreateReconcileOrderParam {
@NotNull(message = "日期不能为空")
@Schema(description = "日期")
private LocalDate date;
@NotBlank(message = "同步的支付通道不能为空")
@Schema(description = "支付通道")
private String channel;
}

View File

@@ -0,0 +1,52 @@
package cn.bootx.platform.daxpay.service.task;
import cn.bootx.platform.daxpay.service.task.service.PayReconcileTaskService;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.util.Objects;
/**
* 对账任务下载和保存定时任务
* @author xxm
* @since 2024/1/20
*/
@Slf4j
@Component
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
@RequiredArgsConstructor
public class PayReconcileTask implements Job {
private final PayReconcileTaskService reconcileTaskService;
/**
* 若参数变量名修改 QuartzJobScheduler 中也需对应修改 需要给一个set方法, 让系统设置值
*/
@Setter
private String channel;
/**
* 同步账单规则是 T+N 天, 例如T+1就是同步昨天的账单, T+2就是同步前天的账单
*/
@Setter
private Integer n;
/**
* 任务实现
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 日期, 如果未配置T+N规则, 默认为T+1
LocalDate date = LocalDate.now();
if (Objects.nonNull(n)){
date = date.minusDays(n);
} else {
date = date.minusDays(1);
}
reconcileTaskService.x1(date,channel);
}
}

View File

@@ -0,0 +1,39 @@
package cn.bootx.platform.daxpay.service.task.service;
import cn.bootx.platform.daxpay.service.core.order.reconcile.entity.PayReconcileOrder;
import cn.bootx.platform.daxpay.service.core.order.reconcile.service.PayReconcileOrderService;
import cn.bootx.platform.daxpay.service.core.payment.reconcile.service.PayReconcileService;
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 PayReconcileTaskService {
private final PayReconcileService reconcileService;
private final PayReconcileOrderService reconcileOrderService;
/**
* 执行任务
*/
public void x1(LocalDate date, String channel){
// 1. 查询需要定时对账的通道, 创建出来对账订单
PayReconcileOrder reconcileOrder = reconcileOrderService.create(date, channel);
// 2. 执行对账任务, 下载对账单并解析, 分别存储为原始数据和通用对账数据
reconcileService.downAndSave(reconcileOrder);
// 3. 执行账单比对, 生成差异单
}
}