feat 增加支付同步日志记录. 超时自动取消和定时同步支付状态功能. 退款功能优化

This commit is contained in:
nws
2024-01-05 23:27:49 +08:00
parent 4546b1c8f5
commit 9858c3d305
47 changed files with 327 additions and 174 deletions

View File

@@ -27,11 +27,22 @@
- [x] 同步记录/关闭记录/修复记录 增加记录请求ID
- 2024-01-05:
- [x] 支付同步日志记录, 无论同步成功还是失败, 以及修复成功还是失败, 都需要记录日志
- [ ] 超时自动取消功能联调
- [x] 超时自动取消功能联调, 先用spring定时任务实现, 通过支付同步实现
- [x] 待支付支付单定时同步状态, 先用spring定时任务实现, 通过支付同步实现
- [x] 退款功能联调
- 2024-01-06:
- [ ] 增加支付修复记录
- [ ] 退款状态同步逻辑
- [ ] 退款回调的处理
- [ ] 支付状态同步处理考虑退款情况
- [ ] 增加消息通知机制(通知客户端)
- **任务池**
- 支付同步时,有些状态无法区分处理, 导致无法修复
- 下面内容同: 支付网关/本地
- 支付宝: 订单未找到/支付关闭 支付网关/支付超时 支付网关/支付成功
- 订单取消/修复/取消/同步添加分布式锁, 防止操作订单时出现重复操作
- 支付状态同步处理退款情况
- 支付配置支持数据库配置和配置文件配置
- 超时任务处理支持轮训表
- 增加回调机制(通知客户端)
- 增加消息通知机制(通知客户端)
- 新增支付单预警功能, 处理支付单与网关状态不一致且无法自动修复的情况

View File

@@ -15,13 +15,10 @@ import java.util.Objects;
@Getter
@RequiredArgsConstructor
public enum PayStatusEnum {
UNKNOWN("unknown","未知状态"),
FAIL("fail","失败"),
PROGRESS("progress","支付中"),
SUCCESS("success","成功"),
FAIL("fail","失败"),
CANCEL("cancel","支付取消"),
CLOSE("close","支付关闭"),
TIMEOUT("timeout","超时取消"),
PARTIAL_REFUND("partial_refund","部分退款"),
REFUNDED("refunded","全部退款");

View File

@@ -18,11 +18,17 @@ import java.util.Objects;
public enum PaySyncStatusEnum {
FAIL("fail", "查询失败"),
PAY_SUCCESS("pay_success", "支付成功"),
PAY_WAIT("pay_wait", "等待付款中"),
PAY_WAIT("pay_wait", "待支付"),
CLOSED("closed", "已关闭"),
REFUND("refund", "已退款"),
NOT_FOUND("not_found", "未查询到订单"),
/** 本地订单到了超时时间, 但是网关和本地都未关闭, 需要触发关闭相关处理 */
NOT_FOUND("not_found", "交易不存在"),
/**
* 未查询到订单(具体类型未知)
* 区别于上面的未查询到订单,有些支付方式如支付宝,发起支付后并不能查询到订单,需要用户进行操作后才能查询到订单,
* 所以查询为了区分,增加一个未知的状态, 用于处理这种特殊情况, 然后根据业务需要,关闭订单或者进行其他操作
*/
NOT_FOUND_UNKNOWN("not_found_unknown","交易不存在(特殊)"),
/** 不属于网关同步过来的状态, 需要手动设置, 处理本地订单到了超时时间, 但是网关和本地都未关闭, 需要触发关闭相关处理 */
TIMEOUT("timeout", "超时未关闭");
/** 编码 */

View File

@@ -1,7 +1,7 @@
package cn.bootx.platform.daxpay.param.pay;
import cn.bootx.platform.daxpay.serializer.TimestampToLocalDateTimeDeserializer;
import cn.bootx.platform.daxpay.service.util.PayUtil;
import cn.bootx.platform.daxpay.util.PayUtil;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@@ -1,6 +1,5 @@
package cn.bootx.platform.daxpay.param.pay;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.param.channel.AliPayParam;
import cn.bootx.platform.daxpay.param.channel.VoucherPayParam;
import cn.bootx.platform.daxpay.param.channel.WalletPayParam;
@@ -9,7 +8,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
@@ -36,12 +34,6 @@ public class SimpleRefundParam extends PayCommonParam {
*/
@Schema(description = "退款订单号")
private String refundNo;
/**
* @see PayChannelEnum#getCode()
*/
@Schema(description = "支付通道编码")
@NotBlank(message = "支付通道编码不可为空")
private String payChannel;
/**
* 部分退款需要传输refundModes参数

View File

@@ -23,14 +23,12 @@ public class PaySyncResult extends PayCommonResult{
* 支付网关同步状态
* @see PaySyncStatusEnum
*/
private String syncStatus = FAIL.getCode();
@Schema(description = "支付网关同步状态")
private String gatewayStatus = FAIL.getCode();
@Schema(description = "是否同步成功")
private boolean success;
@Schema(description = "失败原因")
private String errorMsg;
@Schema(description = "是否进行了修复")
private boolean repair;
@@ -40,4 +38,7 @@ public class PaySyncResult extends PayCommonResult{
@Schema(description = "支付单修复后状态")
private String repairStatus;
@Schema(description = "失败原因")
private String errorMsg;
}

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.service.util;
package cn.bootx.platform.daxpay.util;
import cn.bootx.platform.daxpay.result.DaxResult;
import lombok.experimental.UtilityClass;

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.service.util;
package cn.bootx.platform.daxpay.util;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.daxpay.code.PayChannelEnum;

View File

@@ -0,0 +1,40 @@
package cn.bootx.platform.daxpay.gateway.controller;
import cn.bootx.platform.common.core.rest.Res;
import cn.bootx.platform.common.core.rest.ResResult;
import cn.bootx.platform.daxpay.service.core.timeout.task.PayExpiredTimeTask;
import cn.bootx.platform.daxpay.service.core.timeout.task.PayWaitOrderSyncTask;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试
* @author xxm
* @since 2024/1/5
*/
@Tag(name = "测试")
@RestController
@RequestMapping("/test")
@RequiredArgsConstructor
public class TestController {
private final PayExpiredTimeTask expiredTimeTask;;
private final PayWaitOrderSyncTask waitOrderSyncTask;
@Operation(summary = "同步")
@GetMapping("/sync")
public ResResult<Void> sync(){
waitOrderSyncTask.task();
return Res.ok();
}
@Operation(summary = "超时")
@GetMapping("/expired")
public ResResult<Void> expired(){
expiredTimeTask.task();
return Res.ok();
}
}

View File

@@ -2,17 +2,17 @@ package cn.bootx.platform.daxpay.gateway.controller;
import cn.bootx.platform.common.core.annotation.CountTime;
import cn.bootx.platform.common.core.annotation.IgnoreAuth;
import cn.bootx.platform.daxpay.service.annotation.PaymentApi;
import cn.bootx.platform.daxpay.service.core.payment.close.service.PayCloseService;
import cn.bootx.platform.daxpay.service.core.payment.pay.service.PayService;
import cn.bootx.platform.daxpay.service.core.payment.refund.service.PayRefundService;
import cn.bootx.platform.daxpay.service.core.payment.sync.service.PaySyncService;
import cn.bootx.platform.daxpay.param.pay.*;
import cn.bootx.platform.daxpay.result.DaxResult;
import cn.bootx.platform.daxpay.result.pay.PayResult;
import cn.bootx.platform.daxpay.result.pay.PaySyncResult;
import cn.bootx.platform.daxpay.result.pay.RefundResult;
import cn.bootx.platform.daxpay.service.util.DaxRes;
import cn.bootx.platform.daxpay.service.annotation.PaymentApi;
import cn.bootx.platform.daxpay.service.core.payment.close.service.PayCloseService;
import cn.bootx.platform.daxpay.service.core.payment.pay.service.PayService;
import cn.bootx.platform.daxpay.service.core.payment.refund.service.PayRefundService;
import cn.bootx.platform.daxpay.service.core.payment.sync.service.PaySyncService;
import cn.bootx.platform.daxpay.util.DaxRes;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

View File

@@ -49,11 +49,16 @@
<groupId>cn.bootx.platform</groupId>
<artifactId>common-spring</artifactId>
</dependency>
<!-- 查询构造器 -->
<dependency>
<groupId>cn.bootx.platform</groupId>
<artifactId>common-super-query</artifactId>
<version>${bootx-platform.version}</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<!-- 支付核心包-->

View File

@@ -13,8 +13,10 @@ import lombok.Getter;
public enum PayRepairTypeEnum {
SUCCESS("success","成功"),
CLOSE("close","关闭"),
TIMEOUT("timeout","超时关闭"),
CLOSE_LOCAL("close_local","关闭本地支付"),
WAIT("wait","待支付"),
/** 同时也会关闭本地支付 */
CLOSE_GATEWAY("close_gateway","关闭网关支付"),
REFUND("refund","退款");
private final String code;

View File

@@ -1,9 +1,11 @@
package cn.bootx.platform.daxpay.service.common.entity;
import cn.bootx.platform.common.mybatisplus.base.MpIdEntity;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.table.modify.annotation.DbColumn;
import cn.bootx.table.modify.mysql.annotation.DbMySqlIndex;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@@ -13,14 +15,23 @@ import java.time.LocalDateTime;
* @author xxm
* @since 2023/12/18
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class BasePayOrder {
public class BasePayOrder extends MpIdEntity {
/** 交易记录ID */
@DbColumn(comment = "交易记录ID")
@DbMySqlIndex(comment = "交易记录ID")
private Long paymentId;
/** 关联的业务号 */
@DbMySqlIndex(comment = "业务号索引")
@DbColumn(comment = "关联的业务号")
private String businessNo;
/** 交易金额 */
@DbColumn(comment = "交易金额")
private Integer amount;
@@ -29,11 +40,6 @@ public class BasePayOrder {
@DbColumn(comment = "可退款金额")
private Integer refundableBalance;
/** 关联的业务号 */
@DbMySqlIndex(comment = "业务号索引")
@DbColumn(comment = "关联的业务号")
private String businessNo;
/**
* 支付状态
* @see PayStatusEnum

View File

@@ -4,6 +4,7 @@ import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.daxpay.service.common.entity.BasePayOrder;
import cn.bootx.platform.daxpay.service.core.channel.alipay.convert.AlipayConvert;
import cn.bootx.platform.daxpay.service.dto.channel.alipay.AliPaymentDto;
import cn.bootx.table.modify.annotation.DbColumn;
import cn.bootx.table.modify.annotation.DbTable;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@@ -24,9 +25,11 @@ import lombok.experimental.Accessors;
public class AliPayOrder extends BasePayOrder implements EntityBaseFunction<AliPaymentDto> {
/** 支付宝交易号 */
@DbColumn(comment = "交易号")
private String tradeNo;
/** 支付方式 */
/** 所使用的支付方式 */
@DbColumn(comment = "支付方式")
private String payWay;
@Override

View File

@@ -27,7 +27,7 @@ import java.util.Objects;
@Service
@RequiredArgsConstructor
public class AliPayCloseService {
private final AliPaySyncService paySyncService;
private final AliPaySyncService aliPaySyncService;
/**
* 关闭支付 此处使用交易关闭接口, 支付宝支持 交易关闭 和 交易撤销 两种关闭订单的方式, 区别如下
@@ -70,7 +70,7 @@ public class AliPayCloseService {
* 关闭失败后, 获取支付网关的状态, 如果是关闭返回true, 其他情况抛出异常
*/
private boolean syncStatus(PayOrder payOrder){
GatewaySyncResult gatewaySyncResult = paySyncService.syncPayStatus(payOrder);
GatewaySyncResult gatewaySyncResult = aliPaySyncService.syncPayStatus(payOrder);
// 已经关闭
if (Objects.equals(gatewaySyncResult.getSyncStatus(), PaySyncStatusEnum.CLOSED)){
return true;

View File

@@ -70,7 +70,6 @@ public class AliPayConfigService {
*/
@SneakyThrows
public void initConfig(AliPayConfig alipayConfig) {
AliPayApiConfig aliPayApiConfig;
// 公钥
if (Objects.equals(alipayConfig.getAuthType(), AliPayCode.AUTH_TYPE_KEY)) {

View File

@@ -76,6 +76,7 @@ public class AliPayOrderService {
AliPayOrder aliPayOrder = new AliPayOrder();
aliPayOrder.setTradeNo(tradeNo)
.setPayWay(PaymentContextLocal.get().getAsyncPayInfo().getPayWay().getCode())
.setPaymentId(payOrder.getId())
.setAmount(amount)
.setRefundableBalance(amount)

View File

@@ -12,7 +12,7 @@ import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.channel.AliPayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.service.util.PayUtil;
import cn.bootx.platform.daxpay.util.PayUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.Method;

View File

@@ -68,9 +68,9 @@ public class AliPaySyncService {
return syncResult.setSyncStatus(PaySyncStatusEnum.CLOSED);
}
}
// 支付宝支付后, 客户未进行操作将不会创建出订单, 所以交易不存在等于未查询订单
// 支付宝支付后, 客户未进行操作将不会创建出订单, 所以交易不存在等于未查询订单
if (Objects.equals(response.getSubCode(), AliPayCode.ACQ_TRADE_NOT_EXIST)) {
return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_WAIT);
return syncResult.setSyncStatus(PaySyncStatusEnum.NOT_FOUND_UNKNOWN);
}
}
catch (AlipayApiException e) {

View File

@@ -4,6 +4,7 @@ import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.daxpay.service.common.entity.BasePayOrder;
import cn.bootx.platform.daxpay.service.core.channel.wechat.convert.WeChatConvert;
import cn.bootx.platform.daxpay.service.dto.channel.wechat.WeChatPayOrderDto;
import cn.bootx.table.modify.annotation.DbColumn;
import cn.bootx.table.modify.annotation.DbTable;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@@ -21,11 +22,15 @@ import lombok.experimental.Accessors;
@TableName("pay_wechat_pay_order")
public class WeChatPayOrder extends BasePayOrder implements EntityBaseFunction<WeChatPayOrderDto> {
/**
* 微信交易号
*/
/** 微信交易号 */
@DbColumn(comment = "交易号")
private String tradeNo;
/** 所使用的支付方式 */
@DbColumn(comment = "支付方式")
private String payWay;
@Override
public WeChatPayOrderDto toDto() {
return WeChatConvert.CONVERT.convert(this);

View File

@@ -77,12 +77,13 @@ public class WeChatPayOrderService {
// 创建微信支付记录
WeChatPayOrder wechatPayOrder = new WeChatPayOrder();
wechatPayOrder.setTradeNo(tradeNo)
.setPaymentId(payOrder.getId())
.setAmount(amount)
.setRefundableBalance(amount)
.setBusinessNo(payOrder.getBusinessNo())
.setStatus(PayStatusEnum.SUCCESS.getCode())
.setPayTime(LocalDateTime.now());
.setPayWay(PaymentContextLocal.get().getAsyncPayInfo().getPayWay().getCode())
.setPaymentId(payOrder.getId())
.setAmount(amount)
.setRefundableBalance(amount)
.setBusinessNo(payOrder.getBusinessNo())
.setStatus(PayStatusEnum.SUCCESS.getCode())
.setPayTime(LocalDateTime.now());
weChatPayOrderManager.save(wechatPayOrder);
}

View File

@@ -17,7 +17,7 @@ import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.param.channel.wechat.WeChatPayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.result.pay.PaySyncResult;
import cn.bootx.platform.daxpay.service.util.PayUtil;
import cn.bootx.platform.daxpay.util.PayUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.StrUtil;
@@ -272,7 +272,7 @@ public class WeChatPayService {
public void rotationSync(PayOrder payOrder) {
PaySyncResult paySyncResult = paySyncService.syncPayOrder(payOrder);
// 不为支付中状态后, 调用系统同步更新状态, 支付状态则继续重试
if (Objects.equals(PAY_WAIT.getCode(), paySyncResult.getSyncStatus())) {
if (Objects.equals(PAY_WAIT.getCode(), paySyncResult.getGatewayStatus())) {
throw new RetryableException();
}
}

View File

@@ -105,7 +105,7 @@ public class PayCallbackService {
// 执行支付关闭修复逻辑
PayRepairParam payRepairParam = new PayRepairParam()
.setRepairSource(PayRepairSourceEnum.CALLBACK)
.setRepairType(PayRepairTypeEnum.CLOSE);
.setRepairType(PayRepairTypeEnum.CLOSE_LOCAL);
payRepairService.repair(payOrder, payRepairParam);
return result;
}

View File

@@ -112,6 +112,7 @@ public class PayCloseService {
.getReqId();
PayCloseRecord record = new PayCloseRecord()
.setPaymentId(payOrder.getId())
.setBusinessNo(payOrder.getBusinessNo())
.setAsyncChannel(payOrder.getAsyncChannel())
.setClosed(closed)
.setErrorMsg(errMsg)

View File

@@ -19,7 +19,7 @@ import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrderChannel;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrderExtra;
import cn.bootx.platform.daxpay.service.core.record.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.service.util.PayUtil;
import cn.bootx.platform.daxpay.util.PayUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -184,8 +184,7 @@ public class PayAssistService {
throw new PayFailureException("已经支付成功,请勿重新支付");
}
// 支付失败类型状态
List<String> tradesStatus = Arrays.asList(PayStatusEnum.FAIL.getCode(), PayStatusEnum.CANCEL.getCode(),
PayStatusEnum.CLOSE.getCode(), PayStatusEnum.TIMEOUT.getCode());
List<String> tradesStatus = Arrays.asList(PayStatusEnum.FAIL.getCode(), PayStatusEnum.CLOSE.getCode());
if (tradesStatus.contains(payOrder.getStatus())) {
throw new PayFailureException("支付失败或已经被关闭");
}

View File

@@ -12,7 +12,7 @@ import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.SimplePayParam;
import cn.bootx.platform.daxpay.result.pay.PayResult;
import cn.bootx.platform.daxpay.service.util.PayUtil;
import cn.bootx.platform.daxpay.util.PayUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollectionUtil;

View File

@@ -67,7 +67,6 @@ public class PayRefundAssistService {
* 根据退款参数获取支付订单, 并进行检查
*/
public PayOrder getPayOrderAndCheckByRefundParam(RefundParam param, boolean simple){
if (!param.isRefundAll()) {
if (CollUtil.isEmpty(param.getRefundChannels())) {
throw new ValidationFailedException("退款通道参数不能为空");
@@ -87,19 +86,28 @@ public class PayRefundAssistService {
.orElseThrow(() -> new PayFailureException("未查询到支付订单"));
}
// 简单退款校验
if (payOrder.isCombinationPay() != simple){
throw new PayFailureException("组合支付不可以使用简单退款方式");
// 简单退款处理
if (simple){
// 简单退款校验
if (payOrder.isCombinationPay()){
throw new PayFailureException("组合支付不可以使用简单退款方式");
}
// 设置退款参数的通道配置
String channel = payOrder.getRefundableInfos()
.get(0)
.getChannel();
param.getRefundChannels().get(0).setChannel(channel);
}
// 状态判断, 支付中/失败/取消等不能进行退款
List<String> tradesStatus = Arrays.asList(
PayStatusEnum.PROGRESS.getCode(),
PayStatusEnum.CLOSE.getCode(),
PayStatusEnum.CANCEL.getCode(),
PayStatusEnum.TIMEOUT.getCode(),
PayStatusEnum.FAIL.getCode());
if (tradesStatus.contains(payOrder.getStatus())) {
throw new PayFailureException("状态非法, 无法退款");
PayStatusEnum statusEnum = PayStatusEnum.findByCode(payOrder.getStatus());
throw new PayFailureException("当前状态["+statusEnum.getName()+"]不允许状态非法, 无法退款");
}
return payOrder;
}

View File

@@ -54,7 +54,6 @@ public class PayRefundService {
BeanUtil.copyProperties(param,refundParam);
RefundChannelParam channelParam = new RefundChannelParam()
.setAmount(param.getAmount())
.setChannel(param.getPayChannel())
.setChannelExtra(param.getChannelExtra());
refundParam.setRefundChannels(Collections.singletonList(channelParam));
return this.refund(refundParam,true);
@@ -66,9 +65,10 @@ public class PayRefundService {
* @param simple 是否简单退款
*/
private RefundResult refund(RefundParam param, boolean simple){
// 检查获取支付订单, 同时设置退款参数中对应的支付通道参数
PayOrder payOrder = payRefundAssistService.getPayOrderAndCheckByRefundParam(param, simple);
// 参数校验
ValidationUtil.validateParam(param);
PayOrder payOrder = payRefundAssistService.getPayOrderAndCheckByRefundParam(param, simple);
// 退款上下文初始化
payRefundAssistService.initRefundContext(param);
// 是否全部退款

View File

@@ -33,30 +33,37 @@ public class PayRepairService {
/**
* 修复支付单
*/
@Transactional(rollbackFor = Exception.class )
@Transactional(rollbackFor = Exception.class)
public RepairResult repair(PayOrder order, PayRepairParam repairParam){
// 从退款记录中获取支付通道 退款记录中的支付通道跟支付时关联的支付通道一致
List<String> channels = order.getRefundableInfos()
.stream()
.map(OrderRefundableInfo::getChannel)
.collect(Collectors.toList());
// 初始化修复参数
List<AbsPayRepairStrategy> repairStrategies = PayRepairStrategyFactory.createAsyncLast(channels);
repairStrategies.forEach(repairStrategy -> repairStrategy.initRepairParam(order, repairParam.getRepairSource()));
repairStrategies.forEach(AbsPayRepairStrategy::doBeforeHandler);
RepairResult repairResult = new RepairResult().setOldStatus(PayStatusEnum.findByCode(order.getStatus()));
// 根据不同的类型执行对应的修复逻辑
switch (repairParam.getRepairType()) {
case SUCCESS:
this.success(order, repairStrategies);
repairResult.setRepairStatus(PayStatusEnum.SUCCESS);
break;
case CLOSE:
this.close(order, repairStrategies);
case CLOSE_LOCAL:
this.closeLocal(order, repairStrategies);
repairResult.setRepairStatus(PayStatusEnum.CLOSE);
break;
case TIMEOUT:
this.timeout(order, repairStrategies);
repairResult.setRepairStatus(PayStatusEnum.TIMEOUT);
case WAIT:
this.wait(order, repairStrategies);
repairResult.setRepairStatus(PayStatusEnum.PROGRESS);
break;
case CLOSE_GATEWAY:
this.closeGateway(order, repairStrategies);
repairResult.setRepairStatus(PayStatusEnum.CLOSE);
break;
case REFUND:
this.refund(order, repairStrategies);
@@ -67,44 +74,53 @@ public class PayRepairService {
return repairResult;
}
/**
* 变更未待支付
*
*/
private void wait(PayOrder order, List<AbsPayRepairStrategy> repairStrategies) {
// 修改订单支付状态为成功
order.setStatus(PayStatusEnum.PROGRESS.getCode());
payOrderService.updateById(order);
}
/**
* 变更为已支付
* 同步: 将异步支付状态修改为成功
* 回调: 将异步支付状态修改为成功
*/
private void success(PayOrder payment, List<AbsPayRepairStrategy> strategies) {
private void success(PayOrder order, List<AbsPayRepairStrategy> strategies) {
LocalDateTime payTime = PaymentContextLocal.get()
.getAsyncPayInfo()
.getPayTime();
// 执行个通道的成功处理方法
strategies.forEach(AbsPayRepairStrategy::doSuccessHandler);
// 修改订单支付状态为成功
payment.setStatus(PayStatusEnum.SUCCESS.getCode());
// TODO 读取支付网关中的时间
payment.setPayTime(payTime);
payOrderService.updateById(payment);
order.setStatus(PayStatusEnum.SUCCESS.getCode());
// 读取支付网关中的时间
order.setPayTime(payTime);
payOrderService.updateById(order);
}
/**
* 关闭支付
* 同步: 执行支付单所有的支付通道关闭支付逻辑, 不再重复调用网关进行支付的关闭
* 同步: 执行支付单所有的支付通道关闭支付逻辑, 如果来源是网关同步, 则不需要调用网关关闭
* 回调: 执行所有的支付通道关闭支付逻辑
*/
private void close(PayOrder payOrder, List<AbsPayRepairStrategy> absPayStrategies) {
private void closeLocal(PayOrder order, List<AbsPayRepairStrategy> absPayStrategies) {
// 执行策略的关闭方法
absPayStrategies.forEach(AbsPayRepairStrategy::doCloseHandler);
payOrder.setStatus(PayStatusEnum.CLOSE.getCode());
payOrderService.updateById(payOrder);
absPayStrategies.forEach(AbsPayRepairStrategy::doCloseLocalHandler);
order.setStatus(PayStatusEnum.CLOSE.getCode());
payOrderService.updateById(order);
}
/**
* 支付超时关闭订单
* 关闭网关交易, 同时也会关闭本地支付
*/
private void timeout(PayOrder payOrder, List<AbsPayRepairStrategy> absPayStrategies) {
private void closeGateway(PayOrder payOrder, List<AbsPayRepairStrategy> absPayStrategies) {
// 执行策略的关闭方法
absPayStrategies.forEach(AbsPayRepairStrategy::doTimeoutHandler);
payOrder.setStatus(PayStatusEnum.TIMEOUT.getCode());
absPayStrategies.forEach(AbsPayRepairStrategy::doCloseGatewayHandler);
payOrder.setStatus(PayStatusEnum.CLOSE.getCode());
payOrderService.updateById(payOrder);
}

View File

@@ -1,14 +1,13 @@
package cn.bootx.platform.daxpay.service.core.payment.repair.strategy;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.service.code.PayRepairSourceEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.core.channel.alipay.entity.AliPayConfig;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayCloseService;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayOrderService;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayConfigService;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayOrderService;
import cn.bootx.platform.daxpay.service.core.record.pay.dao.PayOrderChannelManager;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrderChannel;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.func.AbsPayRepairStrategy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -55,22 +54,18 @@ public class AliPayRepairStrategy extends AbsPayRepairStrategy {
* 取消支付
*/
@Override
public void doCloseHandler() {
// 如果非同步出的订单取消状态, 则调用支付宝网关关闭订单
if (this.getRepairSource() != PayRepairSourceEnum.SYNC){
closeService.close(this.getOrder());
}
public void doCloseLocalHandler() {
orderService.updateClose(this.getOrder().getId());
}
/**
* 订单支付超时取消支付
* 关闭本地支付和网关支付
*/
@Override
public void doTimeoutHandler() {
public void doCloseGatewayHandler() {
closeService.close(this.getOrder());
orderService.updateClose(this.getOrder().getId());
this.doCloseLocalHandler();
}
/**

View File

@@ -27,7 +27,7 @@ public class CashPayRepairStrategy extends AbsPayRepairStrategy {
* 取消支付
*/
@Override
public void doCloseHandler() {
public void doCloseLocalHandler() {
cashService.close(this.getOrder().getId());

View File

@@ -23,7 +23,7 @@ public class UnionPayRepairStrategy extends AbsPayRepairStrategy {
* 取消支付
*/
@Override
public void doCloseHandler() {
public void doCloseLocalHandler() {
}
}

View File

@@ -27,7 +27,7 @@ public class VoucherPayRepairStrategy extends AbsPayRepairStrategy {
* 取消支付
*/
@Override
public void doCloseHandler() {
public void doCloseLocalHandler() {
voucherPayService.close(this.getOrder().getId());
voucherPayOrderService.updateClose(this.getOrder().getId());
}

View File

@@ -29,7 +29,7 @@ public class WalletPayRepairStrategy extends AbsPayRepairStrategy {
* 取消支付
*/
@Override
public void doCloseHandler() {
public void doCloseLocalHandler() {
walletPayService.close(this.getOrder().getId());
walletPayOrderService.updateClose(this.getOrder().getId());
}

View File

@@ -1,14 +1,13 @@
package cn.bootx.platform.daxpay.service.core.payment.repair.strategy;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.service.code.PayRepairSourceEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPayCloseService;
import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPayConfigService;
import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPayOrderService;
import cn.bootx.platform.daxpay.service.core.record.pay.dao.PayOrderChannelManager;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrderChannel;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.func.AbsPayRepairStrategy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -61,23 +60,19 @@ public class WeChatPayRepairStrategy extends AbsPayRepairStrategy {
}
/**
* 取消支付
* 关闭本地支付
*/
@Override
public void doCloseHandler() {
// 如果非同步出的订单取消状态, 则调用支付网关关闭订单
if (this.getRepairSource() != PayRepairSourceEnum.SYNC){
closeService.close(this.getOrder(),this.weChatPayConfig);
}
public void doCloseLocalHandler() {
orderService.updateClose(this.getOrder().getId());
}
/**
* 订单支付超时取消支付
* 关闭本地支付和网关支付
*/
@Override
public void doTimeoutHandler() {
public void doCloseGatewayHandler() {
closeService.close(this.getOrder(),this.weChatPayConfig);
orderService.updateClose(this.getOrder().getId());
this.doCloseLocalHandler();
}
}

View File

@@ -26,10 +26,13 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import static cn.bootx.platform.daxpay.code.PaySyncStatusEnum.*;
/**
* 支付同步服务
* @author xxm
@@ -90,15 +93,15 @@ public class PaySyncService {
}
// 判断网关状态是否和支付单一致, 同时更新网关同步状态
boolean statusSync = this.checkStatusSync(syncResult,order);
boolean statusSync = this.checkAndAdjustSyncStatus(syncResult,order);
try {
// 状态不一致,执行支付单修复逻辑
if (!statusSync){
this.resultHandler(syncResult, order);
repairStatus = order.getStatus();
}
} catch (Exception e) {
// 同步失败, 返回失败响应, 同时记录失败的日志 TODO 后面异常范围能这么宽泛
} catch (PayFailureException e) {
// 同步失败, 返回失败响应, 同时记录失败的日志
syncResult.setSyncStatus(PaySyncStatusEnum.FAIL);
this.saveRecord(order, syncResult, false, oldStatus, null, e.getMessage());
return new PaySyncResult().setErrorMsg(e.getMessage());
@@ -107,39 +110,50 @@ public class PaySyncService {
// 同步成功记录日志
this.saveRecord( order, syncResult, !statusSync, oldStatus, repairStatus, null);
return new PaySyncResult()
.setGatewayStatus(syncResult.getSyncStatus().getCode())
.setSuccess(true)
.setRepair(!statusSync)
.setRepairStatus(repairStatus)
.setSyncStatus(syncResult.getSyncStatus().getCode());
.setOldStatus(oldStatus)
.setRepairStatus(repairStatus);
}
/**
* 支付单和网关状态是否一致, 同时待支付状态下, 处理支付超时情况
* 判断支付单和网关状态是否一致, 同时待支付状态下, 支付单支付超时进行状态的更改
*/
public boolean checkStatusSync(GatewaySyncResult syncResult, PayOrder order){
private boolean checkAndAdjustSyncStatus(GatewaySyncResult syncResult, PayOrder order){
PaySyncStatusEnum syncStatus = syncResult.getSyncStatus();
String orderStatus = order.getStatus();
// 支付成功比对
if (orderStatus.equals(PayStatusEnum.SUCCESS.getCode()) && syncStatus.equals(PaySyncStatusEnum.PAY_SUCCESS)){
// 本地支付成功/网关支付成功
if (orderStatus.equals(PayStatusEnum.SUCCESS.getCode()) && syncStatus.equals(PAY_SUCCESS)){
return true;
}
// 待支付比对 支付中都代表待支付, 需要处理订单超时的情况
List<PaySyncStatusEnum> enums = Collections.singletonList(PaySyncStatusEnum.PAY_WAIT);
if (orderStatus.equals(PayStatusEnum.PROGRESS.getCode()) && enums.contains(syncStatus)){
/*
本地支付中/网关支付中或者订单未找到(未知) 支付宝特殊情况,未找到订单可能是发起支付用户未操作、支付已关闭、交易未找到三种情况
所以需要根据本地订单不同的状态进行特殊处理
*/
List<PaySyncStatusEnum> syncWaitEnums = Arrays.asList(PAY_WAIT, NOT_FOUND_UNKNOWN);
if (orderStatus.equals(PayStatusEnum.PROGRESS.getCode()) && syncWaitEnums.contains(syncStatus)){
// 判断支付单是否支付超时, 如果待支付状态下触发超时
if (LocalDateTimeUtil.le(order.getExpiredTime(), LocalDateTime.now())){
// 将支付单同步状态状态调整为支付超时, 进行订单的关闭
syncResult.setSyncStatus(PaySyncStatusEnum.TIMEOUT);
return false;
}
return true;
}
// 支付关闭比对
if (orderStatus.equals(PayStatusEnum.CLOSE.getCode()) && syncStatus.equals(PaySyncStatusEnum.CLOSED)){
/*
关闭 /网关支付关闭、订单未找到、订单未找到(特殊) 订单未找到(特殊)主要是支付宝特殊情况,
未找到订单可能是发起支付用户未操作、支付已关闭、交易未找到三种情况
所以需要根据本地订单不同的状态进行特殊处理, 此处视为支付已关闭、交易未找到这两种, 处理方式相同, 都作为支付关闭处理
*/
List<String> payCloseEnums = Collections.singletonList(PayStatusEnum.CLOSE.getCode());
List<PaySyncStatusEnum> syncClose = Arrays.asList(CLOSED, NOT_FOUND, NOT_FOUND_UNKNOWN);
if (payCloseEnums.contains(orderStatus) && syncClose.contains(syncStatus)){
return true;
}
// 退款比对
// TODO 退款比对
if (orderStatus.equals(PayStatusEnum.REFUNDED.getCode()) && syncStatus.equals(PaySyncStatusEnum.REFUND)){
return true;
}
@@ -160,15 +174,23 @@ public class PaySyncService {
repairService.repair(payOrder,repairParam);
break;
}
// 待付款/ 支付
// 待支付, 将订单状态重新设置为待支付
case PAY_WAIT: {
log.info("依然是付款状态");
repairParam.setRepairType(PayRepairTypeEnum.WAIT);
repairService.repair(payOrder,repairParam);
break;
}
// 订单已经超时关闭 和 网关没找到记录, 对订单进行关闭
// 交易关闭和未找到, 都对本地支付订单进行关闭, 不需要再调用网关进行关闭
case CLOSED:
case NOT_FOUND: {
repairParam.setRepairType(PayRepairTypeEnum.CLOSE);
repairParam.setRepairType(PayRepairTypeEnum.CLOSE_LOCAL);
repairService.repair(payOrder, repairParam);
break;
}
// 超时关闭和交易不存在(特殊) 关闭本地支付订单, 同时调用网关进行关闭, 确保后续这个订单不能被支付
case TIMEOUT:
case NOT_FOUND_UNKNOWN:{
repairParam.setRepairType(PayRepairTypeEnum.CLOSE_GATEWAY);
repairService.repair(payOrder, repairParam);
break;
}
@@ -179,10 +201,6 @@ public class PaySyncService {
break;
}
// 调用出错
case TIMEOUT:
repairParam.setRepairType(PayRepairTypeEnum.TIMEOUT);
repairService.repair(payOrder, repairParam);
break;
case FAIL: {
// 不进行处理 TODO 添加重试
log.warn("支付状态同步接口调用出错");
@@ -207,9 +225,10 @@ public class PaySyncService {
private void saveRecord(PayOrder payOrder,GatewaySyncResult syncResult, boolean repair, String oldStatus, String repairStatus, String errorMsg){
PaySyncRecord paySyncRecord = new PaySyncRecord()
.setPaymentId(payOrder.getId())
.setBusinessNo(payOrder.getBusinessNo())
.setChannel(payOrder.getAsyncChannel())
.setSyncInfo(syncResult.getSyncInfo())
.setSyncStatus(syncResult.getSyncStatus().getCode())
.setGatewayStatus(syncResult.getSyncStatus().getCode())
.setRepairOrder(repair)
.setOldStatus(oldStatus)
.setRepairStatus(repairStatus)

View File

@@ -25,7 +25,7 @@ public class PayOrderSyncTaskService {
public void syncTask() {
log.info("开始同步支付订单");
// 1. 从超时订单列表中获取到未超时的订单号
for (String s : PayExpiredTimeRepository.getNormalKeysBy7Day()) {
for (String s : PayExpiredTimeRepository.getNormalKeysBy30Day()) {
try {
Long paymentId = Long.parseLong(s);
PaySyncParam paySyncParam = new PaySyncParam();

View File

@@ -29,6 +29,10 @@ public class PayCloseRecord extends MpCreateEntity implements EntityBaseFunction
@DbComment("支付记录id")
private Long paymentId;
/** 业务号 */
@DbComment("业务号")
private String businessNo;
/**
* 关闭的异步支付通道, 可以为空
* @see PayChannelEnum#getCode()

View File

@@ -29,7 +29,8 @@ public class PayOrderService {
private final PayExpiredTimeService expiredTimeService;
private final List<String> ORDER_FINISH = Arrays.asList(PayStatusEnum.CLOSE.getCode(),PayStatusEnum.TIMEOUT.getCode(), PayStatusEnum.SUCCESS.getCode());
//
private final List<String> ORDER_FINISH = Arrays.asList(PayStatusEnum.CLOSE.getCode(), PayStatusEnum.SUCCESS.getCode());
/**
* 根据id查询
@@ -49,12 +50,11 @@ public class PayOrderService {
* 新增
*/
public void save(PayOrder payOrder){
payOrderManager.save(payOrder);
// 异步支付需要添加订单超时任务记录
if (payOrder.isAsyncPay()){
expiredTimeService.registerExpiredTime(payOrder);
}
payOrderManager.save(payOrder);
}
/**

View File

@@ -33,12 +33,12 @@ import java.util.List;
@TableName(value = "pay_refund_order", autoResultMap = true)
public class PayRefundOrder extends MpBaseEntity implements EntityBaseFunction<PayRefundOrderDto> {
/** 支付单号 */
@DbColumn(comment = "关联的业务号")
/** 支付id */
@DbColumn(comment = "支付id")
private Long paymentId;
/** 关联的业务号 */
@DbColumn(comment = "关联的业务号")
/** 业务号 */
@DbColumn(comment = "业务号")
private String businessNo;
/** 异步方式关联退款请求号(部分退款情况) */

View File

@@ -28,7 +28,7 @@ public class PaySyncRecordManager extends BaseManager<PaySyncRecordMapper, PaySy
return lambdaQuery().orderByDesc(MpIdEntity::getId)
.like(Objects.nonNull(param.getPaymentId()), PaySyncRecord::getPaymentId, param.getPaymentId())
.eq(Objects.nonNull(param.getChannel()), PaySyncRecord::getChannel, param.getChannel())
.eq(Objects.nonNull(param.getStatus()), PaySyncRecord::getSyncStatus, param.getStatus())
.eq(Objects.nonNull(param.getStatus()), PaySyncRecord::getGatewayStatus, param.getStatus())
.page(mpPage);
}

View File

@@ -32,6 +32,10 @@ public class PaySyncRecord extends MpCreateEntity implements EntityBaseFunction<
@DbComment("支付记录id")
private Long paymentId;
/** 业务号 */
@DbComment("业务号")
private String businessNo;
/**
* 支付通道
* @see PayChannelEnum#getCode()
@@ -45,14 +49,14 @@ public class PaySyncRecord extends MpCreateEntity implements EntityBaseFunction<
private String syncInfo;
/**
* 同步状态
* 网关返回状态
* @see PaySyncStatusEnum
*/
@DbComment("同步状态")
private String syncStatus;
private String gatewayStatus;
/**
* 支付单如果状态不一致, 是否修复成功
* 支付单如果状态不一致, 是否进行修复
*/
@DbComment("是否进行修复")
private boolean repairOrder;

View File

@@ -43,10 +43,10 @@ public class PayExpiredTimeRepository {
/**
* 获取所有未过期的订单ID. (7天内的订单)
*/
public Set<String> getNormalKeysBy7Day(){
public Set<String> getNormalKeysBy30Day(){
LocalDateTime now = LocalDateTime.now();
long start = LocalDateTimeUtil.timestamp(now);
long end = LocalDateTimeUtil.timestamp(now.plusDays(7));
long end = LocalDateTimeUtil.timestamp(now.plusDays(30));
return redisClient.zrangeByScore(KEY, start, end);
}

View File

@@ -1,8 +1,7 @@
package cn.bootx.platform.daxpay.service.core.timeout.task;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.daxpay.service.core.record.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.param.pay.PaySyncParam;
import cn.bootx.platform.daxpay.service.core.payment.sync.service.PaySyncService;
import cn.bootx.platform.daxpay.service.core.timeout.dao.PayExpiredTimeRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -21,23 +20,23 @@ import java.util.Set;
@RequiredArgsConstructor
public class PayExpiredTimeTask {
private final PayExpiredTimeRepository repository;
private final PayOrderManager payOrderManager;
private final PaySyncService paySyncService;
// @Scheduled(cron = "*/5 * * * * ?")
public void task(){
log.info("执行....");
log.info("执行超时取消任务....");
Set<String> expiredKeys = repository.getExpiredKeys(LocalDateTime.now());
for (String expiredKey : expiredKeys) {
log.info("key:{}", expiredKey);
try {
// 查询对应的订单
PayOrder payOrder = payOrderManager.findById(Long.parseLong(expiredKey))
.orElseThrow(() -> new DataNotExistException("支付单未找到"));
// 调用订单同步逻辑. 结果如果不是支付中, 进行补偿处理
// 如果状态是支付中, 则进行支付超时处理
// 执行同步操作, 网关同步时会对支付的进行状态的处理
Long paymentId = Long.parseLong(expiredKey);
PaySyncParam paySyncParam = new PaySyncParam();
paySyncParam.setPaymentId(paymentId);
paySyncService.sync(paySyncParam);
} catch (Exception e) {
log.error("超时任务异常", e);
log.error("超时取消任务 异常", e);
}
}

View File

@@ -0,0 +1,43 @@
package cn.bootx.platform.daxpay.service.core.timeout.task;
import cn.bootx.platform.daxpay.param.pay.PaySyncParam;
import cn.bootx.platform.daxpay.service.core.payment.sync.service.PaySyncService;
import cn.bootx.platform.daxpay.service.core.timeout.dao.PayExpiredTimeRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Set;
/**
*
* @author xxm
* @since 2024/1/5
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PayWaitOrderSyncTask {
private final PayExpiredTimeRepository repository;
private final PaySyncService paySyncService;
public void task(){
log.info("开始同步支付订单");
// 从超时订单列表中获取到未超时的订单号
Set<String> keys = repository.getNormalKeysBy30Day();
for (String key : keys) {
log.info("key:{}", key);
try {
Long paymentId = Long.parseLong(key);
PaySyncParam paySyncParam = new PaySyncParam();
paySyncParam.setPaymentId(paymentId);
// 执行网关同步, 网关同步时会对支付的进行状态的处理
paySyncService.sync(paySyncParam);
} catch (Exception e) {
log.error("同步支付订单异常", e);
}
}
}
}

View File

@@ -45,15 +45,15 @@ public abstract class AbsPayRepairStrategy {
}
/**
* 取消支付
* 关闭本地支付
*/
public abstract void doCloseHandler();
public abstract void doCloseLocalHandler();
/**
* 订单支付超时取消支付
* 关闭本地支付和网关支付, 默认为关闭本地支付
*/
public void doTimeoutHandler() {
public void doCloseGatewayHandler() {
this.doCloseLocalHandler();
}
/**

View File

@@ -32,6 +32,7 @@
<dax.version>2.0.0</dax.version>
<!-- 三方库 -->
<slf4j.version>1.7.30</slf4j.version>
<redisson.version>3.16.8</redisson.version>
<xml-apis.version>1.4.01</xml-apis.version>
<spring.checkstyle.version>0.0.38</spring.checkstyle.version>
<plumelog.version>3.5.2</plumelog.version>