支付退款定时任务

This commit is contained in:
yanglei 2026-01-09 15:55:06 +08:00
parent 307d56054e
commit fd94be6571
9 changed files with 120 additions and 47 deletions

View File

@ -59,14 +59,20 @@ public class WxPayConfig {
private String refundNotifyUrl; private String refundNotifyUrl;
/** /**
* 支付退款过期时间分钟 * 支付退款过期时间分钟
* 默认24小时超过24小时订单会自动关闭 * 默认2小时超过2小时订单会自动关闭
*/ */
private Integer expireMinute = 1440; private Integer expireMinute = 120;
/**
* 定时任务单次查寻最大条数
*/
private Integer queryLimit = 100;
/** /**
* 支付状态检查定时任务执行表达式 * 支付状态检查定时任务执行表达式
* 默认每12小时执行1次 * 默认每2小时执行1次
*/ */
private String paySchedule = "0 0 0/12 * * ?"; private String paySchedule = "0 0 0/2 * * ?";
/** /**
* 退款状态检查定时任务执行表达式 * 退款状态检查定时任务执行表达式
* 默认每30分钟执行1次 * 默认每30分钟执行1次

View File

@ -47,4 +47,11 @@ public interface EbikeOrderService extends IService<EbikeOrder> {
* @param orderId 订单ID * @param orderId 订单ID
*/ */
void rejectRefund(Long orderId); void rejectRefund(Long orderId);
/**
* 取消订单
*
* @param orderId 订单id
*/
void cancelOrder(Long orderId);
} }

View File

@ -23,7 +23,7 @@ public interface EbikePaymentService extends IService<EbikePayment> {
* @param duration 订单创建时间超过duration分钟单位分钟 * @param duration 订单创建时间超过duration分钟单位分钟
* @return 未支付订单列表 * @return 未支付订单列表
*/ */
List<EbikePayment> getNoPayNotTimeOutOrder(int duration); List<EbikePayment> getNoPayNotTimeOutOrder(int duration, int queryLimit);
/** /**
* 查询未支付订单, 超时的 * 查询未支付订单, 超时的
@ -31,7 +31,7 @@ public interface EbikePaymentService extends IService<EbikePayment> {
* @param duration 订单创建时间超过duration分钟单位分钟 * @param duration 订单创建时间超过duration分钟单位分钟
* @return 未支付订单列表 * @return 未支付订单列表
*/ */
List<EbikePayment> getNoPayOvertimeOrder(int duration); List<EbikePayment> getNoPayOvertimeOrder(int duration, int queryLimit);
/** /**
* 更新支付状态 * 更新支付状态
@ -64,4 +64,11 @@ public interface EbikePaymentService extends IService<EbikePayment> {
* @return 支付记录详情 * @return 支付记录详情
*/ */
EbikePayment getByOrderId(Long orderId); EbikePayment getByOrderId(Long orderId);
/**
* 取消支付订单
*
* @param paymentId 支付id
*/
void cancelPayment(Long paymentId);
} }

View File

@ -23,7 +23,7 @@ public interface EbikeRefundService extends IService<EbikeRefund> {
* @param duration 订单创建时间超过duration分钟单位分钟 * @param duration 订单创建时间超过duration分钟单位分钟
* @return 未成功退款订单列表 * @return 未成功退款订单列表
*/ */
List<EbikeRefund> getNoSuccessRefundOrderByDuration(int duration); List<EbikeRefund> getNoSuccessRefundOrderByDuration(int duration, int queryLimit);
/** /**
* 更新退款状态 * 更新退款状态

View File

@ -74,4 +74,14 @@ public class EbikeOrderServiceImpl extends ServiceImpl<EbikeOrderMapper, EbikeOr
userOrders.setOrderStatus(EbikeOrderStatus.REFUND_REJECTED); userOrders.setOrderStatus(EbikeOrderStatus.REFUND_REJECTED);
this.mapper.update(userOrders); this.mapper.update(userOrders);
} }
@Override
public void cancelOrder(Long orderId) {
QueryWrapper queryWrapper = QueryWrapper.create()
.where(EBIKE_ORDER.ORDER_ID.eq(orderId))
.where(EBIKE_ORDER.ORDER_STATUS.eq(EbikeOrderStatus.PENDING_PAYMENT));
EbikeOrder userOrders = this.mapper.selectOneByQuery(queryWrapper);
userOrders.setOrderStatus(EbikeOrderStatus.CANCELLED);
this.mapper.update(userOrders);
}
} }

View File

@ -2,6 +2,7 @@ package com.cdzy.payment.service.impl;
import com.cdzy.common.enums.GlobalConstants; import com.cdzy.common.enums.GlobalConstants;
import com.cdzy.common.utils.ConvertUtil; import com.cdzy.common.utils.ConvertUtil;
import com.cdzy.payment.enums.EbikeOrderStatus;
import com.cdzy.payment.enums.PaymentMethod; import com.cdzy.payment.enums.PaymentMethod;
import com.cdzy.payment.mapper.EbikePaymentMapper; import com.cdzy.payment.mapper.EbikePaymentMapper;
import com.cdzy.payment.model.dto.EbikeOrderPaymentDto; import com.cdzy.payment.model.dto.EbikeOrderPaymentDto;
@ -23,7 +24,9 @@ import org.springframework.stereotype.Service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Objects;
import static com.cdzy.payment.model.entity.table.EbikeOrderDetailTableDef.EBIKE_ORDER_DETAIL; import static com.cdzy.payment.model.entity.table.EbikeOrderDetailTableDef.EBIKE_ORDER_DETAIL;
import static com.cdzy.payment.model.entity.table.EbikeOrderTableDef.EBIKE_ORDER; import static com.cdzy.payment.model.entity.table.EbikeOrderTableDef.EBIKE_ORDER;
@ -44,40 +47,51 @@ public class EbikePaymentServiceImpl extends ServiceImpl<EbikePaymentMapper, Ebi
private EbikeOrderService ebikeOrderService; private EbikeOrderService ebikeOrderService;
@Override @Override
public List<EbikePayment> getNoPayNotTimeOutOrder(int duration) { public List<EbikePayment> getNoPayNotTimeOutOrder(int duration, int queryLimit) {
// 未支付状态(已超时),创建时间 >= 当前时间 - duration 分钟 // 未支付状态(已超时),创建时间 >= 当前时间 - duration 分钟
LocalDateTime cutoffTime = LocalDateTime.now().minusMinutes(duration);
QueryWrapper query = QueryWrapper.create() QueryWrapper query = QueryWrapper.create()
.where(EBIKE_PAYMENT.TRADE_STATUS.eq(Transaction.TradeStateEnum.NOTPAY.ordinal())) .where(EBIKE_PAYMENT.TRADE_STATUS.eq(EbikeOrderStatus.PENDING_PAYMENT))
.and(createTimeFilter(duration, ">=")); .ge(EbikePayment::getCreateTime, cutoffTime)
.limit(queryLimit);
return list(query); return list(query);
} }
@Override @Override
public List<EbikePayment> getNoPayOvertimeOrder(int duration) { public List<EbikePayment> getNoPayOvertimeOrder(int duration, int queryLimit) {
// 未支付状态(未超时),创建时间 < 当前时间 - duration 分钟 // 未支付状态(未超时),创建时间 < 当前时间 - duration 分钟
LocalDateTime cutoffTime = LocalDateTime.now().minusMinutes(duration);
QueryWrapper query = QueryWrapper.create() QueryWrapper query = QueryWrapper.create()
.where(EBIKE_PAYMENT.TRADE_STATUS.eq(Transaction.TradeStateEnum.NOTPAY.ordinal())) .where(EBIKE_PAYMENT.TRADE_STATUS.eq(EbikeOrderStatus.PENDING_PAYMENT))
.and(createTimeFilter(duration, "<")); .lt(EbikePayment::getCreateTime, cutoffTime)
.limit(queryLimit);
return list(query); return list(query);
} }
@Override @Override
public Boolean updatePaymentStatus(Transaction transaction) { public Boolean updatePaymentStatus(Transaction transaction) {
EbikePayment ebikePayment = getByPaymentId(transaction.getOutTradeNo()); EbikePayment ebikePayment = getByPaymentId(transaction.getOutTradeNo());
if (Objects.isNull(ebikePayment)) {
log.warn("未找到本地订单outTradeNo: {}", transaction.getOutTradeNo());
return false;
}
ebikePayment.setTradeStatus(transaction.getTradeState().ordinal()); ebikePayment.setTradeStatus(transaction.getTradeState().ordinal());
if (Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) { if (Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) {
// 支付成功 更新订单状态 // 支付成功 更新支付状态
BigDecimal totalAmount = calculateTotalAmount(transaction); BigDecimal totalAmount = calculateTotalAmount(transaction);
ebikePayment.setTotal(totalAmount); ebikePayment.setTotal(totalAmount);
String paymentTime = transaction.getSuccessTime(); String paymentTime = transaction.getSuccessTime();
ebikePayment.setPaymentTime(StringUtils.toLocalDatetime(paymentTime)); ebikePayment.setPaymentTime(StringUtils.toLocalDatetime(paymentTime));
ebikePayment.setTransactionId(transaction.getTransactionId()); ebikePayment.setTransactionId(transaction.getTransactionId());
// 同步支付状态 // 同步订单状态
EbikeOrderPaymentDto paymentParam = new EbikeOrderPaymentDto(); EbikeOrderPaymentDto paymentParam = new EbikeOrderPaymentDto();
paymentParam.setOrderId(ebikePayment.getOrderId()); paymentParam.setOrderId(ebikePayment.getOrderId());
paymentParam.setPaymentTime(ebikePayment.getPaymentTime()); paymentParam.setPaymentTime(ebikePayment.getPaymentTime());
paymentParam.setPaymentMethod(PaymentMethod.WECHAT); paymentParam.setPaymentMethod(PaymentMethod.WECHAT);
ebikeOrderService.payment(paymentParam); ebikeOrderService.payment(paymentParam);
} else if (Transaction.TradeStateEnum.CLOSED.equals(transaction.getTradeState())) {
// 微信订单已取消,同步更新订单为取消
ebikeOrderService.cancelOrder(ebikePayment.getOrderId());
} }
return updateById(ebikePayment); return updateById(ebikePayment);
} }
@ -143,6 +157,18 @@ public class EbikePaymentServiceImpl extends ServiceImpl<EbikePaymentMapper, Ebi
return this.getOne(query); return this.getOne(query);
} }
@Override
public void cancelPayment(Long paymentId) {
QueryWrapper query = QueryWrapper.create()
.where(EBIKE_PAYMENT.PAYMENT_ID.eq(paymentId));
EbikePayment payment = this.getOne(query);
payment.setTradeStatus(GlobalConstants.NUMBER_FOUR);
payment.setUpdateTime(LocalDateTime.now());
this.updateById(payment);
// 同步订单状态变为取消
ebikeOrderService.cancelOrder(payment.getOrderId());
}
/** /**
* 根据支付id查询支付记录 * 根据支付id查询支付记录
* *

View File

@ -1,5 +1,6 @@
package com.cdzy.payment.service.impl; package com.cdzy.payment.service.impl;
import com.cdzy.common.ex.EbikeException;
import com.cdzy.payment.enums.RefundStatus; import com.cdzy.payment.enums.RefundStatus;
import com.cdzy.payment.mapper.EbikeRefundMapper; import com.cdzy.payment.mapper.EbikeRefundMapper;
import com.cdzy.payment.model.entity.EbikeRefund; import com.cdzy.payment.model.entity.EbikeRefund;
@ -17,7 +18,9 @@ import org.springframework.stereotype.Service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Objects;
import static com.cdzy.payment.model.entity.table.EbikeRefundTableDef.EBIKE_REFUND; import static com.cdzy.payment.model.entity.table.EbikeRefundTableDef.EBIKE_REFUND;
@ -35,18 +38,13 @@ public class EbikeRefundServiceImpl extends ServiceImpl<EbikeRefundMapper, Ebike
private EbikeOrderService ebikeOrderService; private EbikeOrderService ebikeOrderService;
@Override @Override
public List<EbikeRefund> getNoSuccessRefundOrderByDuration(int duration) { public List<EbikeRefund> getNoSuccessRefundOrderByDuration(int duration, int queryLimit) {
// trade_state 等于2(退款状态中) 并且创建时间超过duration分钟的订单 // 查询创建时间已超过指定分钟数且退款状态仍为处理中的退款单
int totalSeconds = duration * 60; LocalDateTime cutoffTime = LocalDateTime.now().minusMinutes(duration);
String timeFilter = String.format(
"%s + INTERVAL '%d seconds' <= NOW()",
EBIKE_REFUND.CREATE_TIME.getName(),
totalSeconds
);
QueryWrapper query = QueryWrapper.create() QueryWrapper query = QueryWrapper.create()
.where(EBIKE_REFUND.REFUND_STATUS.eq(RefundStatus.PROCESSING)) .where(EBIKE_REFUND.REFUND_STATUS.eq(RefundStatus.PROCESSING))
.and(timeFilter); .lt(EbikeRefund::getCreateTime, cutoffTime)
.limit(queryLimit);
return list(query); return list(query);
} }
@ -69,7 +67,11 @@ public class EbikeRefundServiceImpl extends ServiceImpl<EbikeRefundMapper, Ebike
return false; return false;
} }
EbikeRefund ebikeRefund = getByRefundOrderId(refund.getOutRefundNo()); EbikeRefund ebikeRefund = getByRefundOrderId(refund.getOutRefundNo());
if (Objects.isNull(ebikeRefund)) {
throw new EbikeException("本地退款单不存在");
}
ebikeRefund.setRefundStatus(refund.getStatus().ordinal()); ebikeRefund.setRefundStatus(refund.getStatus().ordinal());
// 退款成功
if (Status.SUCCESS.equals(refund.getStatus())) { if (Status.SUCCESS.equals(refund.getStatus())) {
String refundTime = refund.getSuccessTime(); String refundTime = refund.getSuccessTime();
ebikeRefund.setRefundTime(StringUtils.toLocalDatetime(refundTime)); ebikeRefund.setRefundTime(StringUtils.toLocalDatetime(refundTime));
@ -81,9 +83,12 @@ public class EbikeRefundServiceImpl extends ServiceImpl<EbikeRefundMapper, Ebike
log.info("订单Id:{}, 订单状态:{}", orderId, refund.getStatus().ordinal()); log.info("订单Id:{}, 订单状态:{}", orderId, refund.getStatus().ordinal());
// 更新订单退款状态 // 更新订单退款状态
switch (refund.getStatus()) { switch (refund.getStatus()) {
case PROCESSING, CLOSED -> ebikeOrderService.refund(orderId); // 退款中
case PROCESSING -> ebikeOrderService.refund(orderId);
// 退款成功
case SUCCESS -> ebikeOrderService.doneRefund(orderId); case SUCCESS -> ebikeOrderService.doneRefund(orderId);
case ABNORMAL -> ebikeOrderService.failRefund(orderId); // 退款失败/关闭 订单退款失败
case ABNORMAL, CLOSED -> ebikeOrderService.failRefund(orderId);
} }
return updateById(ebikeRefund); return updateById(ebikeRefund);
} }

View File

@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* 用户订单支付任务检测支付状态更新订单状态 * 用户订单支付任务检测支付状态更新订单状态
@ -38,54 +39,64 @@ public class WxPayTask {
/** /**
* 查询创建未超过24小时并且未支付的订单 * 查询创建未超过2小时并且未支付的订单
*/ */
public void checkOrderStatus() { public void checkOrderStatus() {
log.info("checkOrderStatus 执行......"); log.info("checkOrderStatus 执行......");
// 1. 查询未支付的未超时的订单 // 1. 查询未支付的未超时的订单
List<EbikePayment> ebikePaymentList = ebikePaymentService.getNoPayNotTimeOutOrder(wxPayConfig.getExpireMinute()); List<EbikePayment> ebikePaymentList = ebikePaymentService.getNoPayNotTimeOutOrder(wxPayConfig.getExpireMinute(), wxPayConfig.getQueryLimit());
if (ebikePaymentList.isEmpty()) {
log.debug("当前无未支付订单");
return;
}
log.info("共发现 {} 笔待检查订单", ebikePaymentList.size());
// 2. 遍历订单查询支付状态 // 2. 遍历订单查询支付状态
for (EbikePayment ebikePayment : ebikePaymentList) { for (EbikePayment ebikePayment : ebikePaymentList) {
log.warn("未支付的订单号 ===> {}", ebikePayment.getOrderId()); log.info("未支付的订单号 ===> {}", ebikePayment.getOrderId());
// 调用微信支付查询接口查询支付状态 // 调用微信支付查询接口查询支付状态
Transaction transaction = wxPayService.queryOrderByOutTradeNo(String.valueOf(ebikePayment.getTradeId())); Transaction transaction = wxPayService.queryOrderByOutTradeNo(String.valueOf(ebikePayment.getTradeId()));
// 3. 更新订单状态 if (Objects.nonNull(transaction)) {
if (transaction != null) { // 更新订单状态
ebikePaymentService.updatePaymentStatus(transaction); ebikePaymentService.updatePaymentStatus(transaction);
} }
} }
} }
/** /**
* 查询创建超过24小时并且未支付的订单 * 查询创建超过2小时并且未支付的订单 直接关闭订单 未使用
*/ */
public void closeOrder() { public void closeOrder() {
log.info("closeOrder 执行......"); log.info("closeOrder 执行......");
// 1. 查询未支付的超时订单 // 1. 查询未支付的超时订单
List<EbikePayment> ebikePaymentList = ebikePaymentService.getNoPayOvertimeOrder(wxPayConfig.getExpireMinute()); List<EbikePayment> ebikePaymentList = ebikePaymentService.getNoPayOvertimeOrder(wxPayConfig.getExpireMinute(), wxPayConfig.getQueryLimit());
if (ebikePaymentList.isEmpty()) {
log.debug("当前无超时未支付订单");
return;
}
log.info("共发现 {} 笔超时订单,准备处理...", ebikePaymentList.size());
// 2. 遍历订单关闭订单 // 2. 遍历订单关闭订单
for (EbikePayment ebikePayment : ebikePaymentList) { for (EbikePayment ebikePayment : ebikePaymentList) {
log.warn("超时未支付的订单号 ===> {}", ebikePayment.getOrderId()); log.warn("超时未支付的订单号 ===> {}", ebikePayment.getOrderId());
// 调用微信支付关闭接口关闭订单 // 调用微信支付关闭接口关闭订单(*超时微信会自动关闭)
boolean close = wxPayService.closeOrder(String.valueOf(ebikePayment.getTradeId())); //boolean close = wxPayService.closeOrder(String.valueOf(ebikePayment.getTradeId()));
if (close) { // 更新订单状态 订单状态变为取消支付订单状态变为取消
// 3. 更新订单状态 ebikePaymentService.cancelPayment(ebikePayment.getPaymentId());
Transaction transaction = new Transaction();
transaction.setTradeState(Transaction.TradeStateEnum.CLOSED);
transaction.setTradeStateDesc("订单关闭");
ebikePaymentService.updatePaymentStatus(transaction);
}
} }
} }
/** /**
* 查询创建未超过24小时并且未成功的退款单 * 查询创建未超过2小时并且未成功的退款单
*/ */
public void checkRefundStatus() { public void checkRefundStatus() {
log.info("checkRefundStatus 执行......"); log.info("checkRefundStatus 执行......");
// 1. 查询未成功的退款单 // 1. 查询未成功的退款单
List<EbikeRefund> ebikeRefundList = ebikeRefundService.getNoSuccessRefundOrderByDuration(wxPayConfig.getExpireMinute()); List<EbikeRefund> ebikeRefundList = ebikeRefundService.getNoSuccessRefundOrderByDuration(wxPayConfig.getExpireMinute(), wxPayConfig.getQueryLimit());
if (ebikeRefundList.isEmpty()) {
log.debug("当前无退款中的退款单");
return;
}
log.info("共发现 {} 笔待处理的退款单", ebikeRefundList.size());
// 2. 遍历退款单查询退款状态 // 2. 遍历退款单查询退款状态
for (EbikeRefund ebikeRefund : ebikeRefundList) { for (EbikeRefund ebikeRefund : ebikeRefundList) {
log.warn("超时未退款的退款单号 ===> {}", ebikeRefund.getRefundId()); log.warn("超时未退款的退款单号 ===> {}", ebikeRefund.getRefundId());

View File

@ -56,8 +56,9 @@ payment:
public-key-id: PUB_KEY_ID_0117151470052025042500331704000601 public-key-id: PUB_KEY_ID_0117151470052025042500331704000601
pay-notify_url: https://www.cdzhuojing.cn/ebike/payment/wxPayment/notify/pay pay-notify_url: https://www.cdzhuojing.cn/ebike/payment/wxPayment/notify/pay
refund-notify_url: https://www.cdzhuojing.cn/ebike/payment/wxPayment/notify/refund refund-notify_url: https://www.cdzhuojing.cn/ebike/payment/wxPayment/notify/refund
expire-minutes: 1440 expire-minute: 120
pay-schedule: 0 0 0/12 * * ? query-limit: 100
pay-schedule: 0 0 0/2 * * ?
refund-schedule: 0 0/30 * * * ? refund-schedule: 0 0/30 * * * ?
task-scheduler-pool: task-scheduler-pool:
poolSize: 100 poolSize: 100