feat 支付对账相关处理

This commit is contained in:
bootx
2024-01-18 23:01:01 +08:00
parent 7db30f5ccd
commit 36b0288386
23 changed files with 425 additions and 30 deletions

View File

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

@@ -3,7 +3,7 @@ package cn.bootx.platform.daxpay.gateway.controller;
import cn.bootx.platform.common.core.exception.BizException;
import cn.bootx.platform.common.core.rest.Res;
import cn.bootx.platform.common.core.rest.ResResult;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AlipayReconcileService;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayReconcileService;
import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WechatPayReconcileService;
import cn.bootx.platform.daxpay.service.core.timeout.task.PayExpiredTimeTask;
import cn.bootx.platform.daxpay.service.core.timeout.task.PayWaitOrderSyncTask;
@@ -31,7 +31,7 @@ import java.util.Objects;
public class TestController {
private final PayExpiredTimeTask expiredTimeTask;;
private final PayWaitOrderSyncTask waitOrderSyncTask;
private final AlipayReconcileService alipayReconcileService;
private final AliPayReconcileService alipayReconcileService;
private final WechatPayReconcileService wechatPayReconcileService;
private final LockTemplate lockTemplate;

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.service.core.channel.alipay.domain;
package cn.bootx.platform.daxpay.service.core.channel.alipay.entity;
import cn.hutool.core.annotation.Alias;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.service.core.channel.alipay.domain;
package cn.bootx.platform.daxpay.service.core.channel.alipay.entity;
import cn.hutool.core.annotation.Alias;
import lombok.Data;

View File

@@ -2,8 +2,8 @@ package cn.bootx.platform.daxpay.service.core.channel.alipay.service;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.code.AliPayCode;
import cn.bootx.platform.daxpay.service.core.channel.alipay.domain.AliReconcileBillDetail;
import cn.bootx.platform.daxpay.service.core.channel.alipay.domain.AliReconcileBillTotal;
import cn.bootx.platform.daxpay.service.core.channel.alipay.entity.AliReconcileBillDetail;
import cn.bootx.platform.daxpay.service.core.channel.alipay.entity.AliReconcileBillTotal;
import cn.bootx.platform.daxpay.service.core.channel.alipay.entity.AliPayConfig;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.csv.CsvReader;
@@ -37,7 +37,7 @@ import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class AlipayReconcileService {
public class AliPayReconcileService {
private final AliPayConfigService configService;
/**

View File

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

View File

@@ -0,0 +1,14 @@
package cn.bootx.platform.daxpay.service.core.channel.wechat.dao;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WxReconcileBillDetail;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 微信对账单明细表
* @author xxm
* @since 2024/1/18
*/
@Mapper
public interface WxReconcileBillDetailMapper extends BaseMapper<WxReconcileBillDetail> {
}

View File

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

View File

@@ -0,0 +1,14 @@
package cn.bootx.platform.daxpay.service.core.channel.wechat.dao;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WxReconcileBillTotal;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 微信对账单总表
* @author xxm
* @since 2024/1/18
*/
@Mapper
public interface WxReconcileBillTotalMapper extends BaseMapper<WxReconcileBillTotal> {
}

View File

@@ -1,15 +1,20 @@
package cn.bootx.platform.daxpay.service.core.channel.wechat.domain;
package cn.bootx.platform.daxpay.service.core.channel.wechat.entity;
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
import cn.hutool.core.annotation.Alias;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 微信对账单明细
* @author xxm
* @since 2024/1/17
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class WxReconcileBillDetail {
@TableName("pay_wechat_reconcile_bill_detail")
public class WxReconcileBillDetail extends MpCreateEntity {
@Alias("交易时间")
private String transactionTime;
@@ -34,9 +39,6 @@ public class WxReconcileBillDetail {
@Alias("设备号")
private String deviceInfo;
@Alias("交易状态")
private String transactionState;
@Alias("交易类型")
private String type;

View File

@@ -1,15 +1,20 @@
package cn.bootx.platform.daxpay.service.core.channel.wechat.domain;
package cn.bootx.platform.daxpay.service.core.channel.wechat.entity;
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
import cn.hutool.core.annotation.Alias;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 微信对账单汇总
* @author xxm
* @since 2024/1/17
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class WxReconcileBillTotal {
@TableName("pay_wechat_reconcile_bill_total")
public class WxReconcileBillTotal extends MpCreateEntity {
@Alias("总交易单数")
private String totalNum;

View File

@@ -1,15 +1,20 @@
package cn.bootx.platform.daxpay.service.core.channel.wechat.domain;
package cn.bootx.platform.daxpay.service.core.channel.wechat.entity;
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
import cn.hutool.core.annotation.Alias;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 微信资金账单明细
* @author xxm
* @since 2024/1/18
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class WxReconcileFundFlowDetail {
@TableName("pay_wechat_reconcile_fund_flow_detail")
public class WxReconcileFundFlowDetail extends MpCreateEntity {
@Alias("记账时间")
private String time;
@Alias("微信支付业务单号")

View File

@@ -1,19 +1,21 @@
package cn.bootx.platform.daxpay.service.core.channel.wechat.domain;
package cn.bootx.platform.daxpay.service.core.channel.wechat.entity;
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
import cn.hutool.core.annotation.Alias;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.EqualsAndHashCode;
/**
* 微信资金账单汇总
* @author xxm
* @since 2024/1/18
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class WxReconcileFundFlowTotal {
@TableName("pay_wechat_reconcile_fund_flow_total")
public class WxReconcileFundFlowTotal extends MpCreateEntity {
// 资金流水总笔数,收入笔数,收入金额,支出笔数,支出金额
@Alias("资金流水总笔数")
private String totalNum;
@Alias("收入笔数")

View File

@@ -2,10 +2,12 @@ package cn.bootx.platform.daxpay.service.core.channel.wechat.service;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.code.WeChatPayCode;
import cn.bootx.platform.daxpay.service.core.channel.wechat.domain.WxReconcileBillDetail;
import cn.bootx.platform.daxpay.service.core.channel.wechat.domain.WxReconcileBillTotal;
import cn.bootx.platform.daxpay.service.core.channel.wechat.domain.WxReconcileFundFlowDetail;
import cn.bootx.platform.daxpay.service.core.channel.wechat.dao.WxReconcileBillDetailManager;
import cn.bootx.platform.daxpay.service.core.channel.wechat.dao.WxReconcileBillTotalManger;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WxReconcileBillDetail;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WxReconcileBillTotal;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WxReconcileFundFlowDetail;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.text.csv.CsvReader;
import cn.hutool.core.text.csv.CsvUtil;
@@ -34,8 +36,10 @@ import static cn.bootx.platform.daxpay.service.code.WeChatPayCode.ACCOUNT_TYPE_B
@Slf4j
@Service
@RequiredArgsConstructor
public class WechatPayReconcileService {
public class WechatPayReconcileService{
private final WeChatPayConfigService weChatPayConfigService;
private final WxReconcileBillTotalManger reconcileBillTotalManger;
private final WxReconcileBillDetailManager reconcileBillDetailManager;
/**
* 下载对账单并保存

View File

@@ -0,0 +1,42 @@
package cn.bootx.platform.daxpay.service.core.payment.reconcile.factory;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;
import cn.bootx.platform.daxpay.service.func.AbsReconcileStrategy;
import cn.hutool.extra.spring.SpringUtil;
import lombok.experimental.UtilityClass;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 支付对账工厂类
* @author xxm
* @since 2024/1/18
*/
@UtilityClass
public class PayReconcileStrategyFactory {
/**
* 根据传入的支付类型批量创建策略
*/
public List<AbsReconcileStrategy> create() {
Map<String, AbsReconcileStrategy> beansOfType = SpringUtil.getBeansOfType(AbsReconcileStrategy.class);
return new ArrayList<>(beansOfType.values());
}
/**
* 根据传入的支付类型批量创建策略
*/
public AbsReconcileStrategy create(String channel) {
PayChannelEnum channelEnum = PayChannelEnum.findByCode(channel);
Map<String, AbsReconcileStrategy> beansOfType = SpringUtil.getBeansOfType(AbsReconcileStrategy.class);
return beansOfType.values().stream()
.filter(strategy -> Objects.equals(strategy.getType(), channelEnum))
.findFirst()
.orElseThrow(PayUnsupportedMethodException::new);
}
}

View File

@@ -1,9 +1,14 @@
package cn.bootx.platform.daxpay.service.core.payment.reconcile.service;
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
@@ -13,11 +18,37 @@ import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class PayReconcileService {
/**
* 下载对账单
*/
public void downReconcileBill(){
public void downAll(LocalDate date){
List<AbsReconcileStrategy> strategies = PayReconcileStrategyFactory.create();
strategies.forEach(AbsReconcileStrategy::doBeforeHandler);
}
/**
* 手动发起对账下载
*/
public void downByChannel(LocalDate date, String channel){
AbsReconcileStrategy absReconcileStrategy = PayReconcileStrategyFactory.create(channel);
absReconcileStrategy.doBeforeHandler();
absReconcileStrategy.downAndSave(date);
}
/**
* 全部对账
*/
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

@@ -1,10 +1,19 @@
package cn.bootx.platform.daxpay.service.core.payment.reconcile.strategy;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.service.core.channel.alipay.entity.AliPayConfig;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayConfigService;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayReconcileService;
import cn.bootx.platform.daxpay.service.func.AbsReconcileStrategy;
import cn.hutool.core.date.DatePattern;
import lombok.RequiredArgsConstructor;
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;
/**
@@ -13,8 +22,43 @@ import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROT
* @since 2024/1/17
*/
@Slf4j
@Scope(SCOPE_PROTOTYPE)
@Service
@Scope(SCOPE_PROTOTYPE)
@RequiredArgsConstructor
public class AlipayReconcileStrategy {
public class AlipayReconcileStrategy extends AbsReconcileStrategy {
private final AliPayReconcileService reconcileService;
private final AliPayConfigService configService;
private AliPayConfig config;
/**
* 策略标识
*
* @see PayChannelEnum
*/
@Override
public PayChannelEnum getType() {
return PayChannelEnum.ALI;
}
/**
* 对账前处理, 主要是初始化支付SDK配置
*/
@Override
public void doBeforeHandler() {
this.config = configService.getConfig();
configService.initConfig(this.config);
}
/**
* 下载对账单
*/
@Override
public void downAndSave(LocalDate date) {
String format = LocalDateTimeUtil.format(date, DatePattern.NORM_DATE_PATTERN);
reconcileService.downAndSave(format);
}
}

View File

@@ -1,10 +1,19 @@
package cn.bootx.platform.daxpay.service.core.payment.reconcile.strategy;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPayConfigService;
import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WechatPayReconcileService;
import cn.bootx.platform.daxpay.service.func.AbsReconcileStrategy;
import cn.hutool.core.date.DatePattern;
import lombok.RequiredArgsConstructor;
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;
/**
@@ -16,5 +25,38 @@ import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROT
@Scope(SCOPE_PROTOTYPE)
@Service
@RequiredArgsConstructor
public class WechatPayReconcileStrategy {
public class WechatPayReconcileStrategy extends AbsReconcileStrategy {
private final WechatPayReconcileService reconcileService;
private final WeChatPayConfigService weChatPayConfigService;
private WeChatPayConfig config;
/**
* 策略标识
*
* @see PayChannelEnum
*/
@Override
public PayChannelEnum getType() {
return PayChannelEnum.WECHAT;
}
/**
* 对账前处理, 主要是初始化支付SDK配置
*/
@Override
public void doBeforeHandler() {
this.config = weChatPayConfigService.getConfig();
}
/**
* 下载对账单
*/
@Override
public void downAndSave(LocalDate date) {
String format = LocalDateTimeUtil.format(date, DatePattern.PURE_DATE_PATTERN);
reconcileService.downAndSave(format,this.config);
}
}

View File

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

View File

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

@@ -0,0 +1,44 @@
package cn.bootx.platform.daxpay.service.core.record.reconcile.entity;
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 支付对账记录详情
* @author xxm
* @since 2024/1/18
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName("pay_reconcile_detail_record")
public class PayReconcileDetailRecord extends MpCreateEntity {
/** 交易状态 */
private String status;
/** 交易类型 支付/退款 */
private String type;
/** 订单号 - 支付ID/退款ID */
private String paymentId;
/** 网关订单号 - 支付宝/微信的订单号 */
private String gatewayOrderNo;
/** 交易金额 */
private Integer amount;
/** 手续费 */
private Integer poundage;
/** 费率 */
private Integer rate;
/** 商品名称 */
private String title;
}

View File

@@ -7,6 +7,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDate;
/**
* 支付对账单记录
* @author xxm
@@ -18,4 +20,17 @@ import lombok.experimental.Accessors;
@DbTable(comment = "支付对账单记录")
@TableName("pay_reconcile_record")
public class PayReconcileRecord extends MpCreateEntity {
/** 日期 */
private LocalDate date;
/** 通道 */
private String channel;
/** 对账单是否下载成功 */
private boolean success;
/** 错误信息 */
private String errorMsg;
}

View File

@@ -0,0 +1,43 @@
package cn.bootx.platform.daxpay.service.func;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import java.time.LocalDate;
/**
* 支付对账策略
* @author xxm
* @since 2024/1/18
*/
public abstract class AbsReconcileStrategy {
/**
* 策略标识
* @see PayChannelEnum
*/
public abstract PayChannelEnum getType();
/**
* 对账前处理, 主要是初始化支付SDK配置
*/
public void doBeforeHandler() {
}
/**
* 下载对账单到本地进行保存
*/
public abstract void downAndSave(LocalDate date);
/**
* 轧差
* 1. 远程有, 本地无 补单(追加回订单/记录差异表)
* 2. 远程无, 本地有 记录差错表
* 3. 远程有, 本地有, 但状态不一致
*/
public void offsetting(LocalDate date){
}
}