Merge remote-tracking branch 'origin/main'

This commit is contained in:
attiya 2026-01-12 09:28:41 +08:00
commit 37e54c5e31
9 changed files with 120 additions and 47 deletions

View File

@ -59,14 +59,20 @@ public class WxPayConfig {
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次

View File

@ -47,4 +47,11 @@ public interface EbikeOrderService extends IService<EbikeOrder> {
* @param orderId 订单ID
*/
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分钟单位分钟
* @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分钟单位分钟
* @return 未支付订单列表
*/
List<EbikePayment> getNoPayOvertimeOrder(int duration);
List<EbikePayment> getNoPayOvertimeOrder(int duration, int queryLimit);
/**
* 更新支付状态
@ -64,4 +64,11 @@ public interface EbikePaymentService extends IService<EbikePayment> {
* @return 支付记录详情
*/
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分钟单位分钟
* @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);
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.utils.ConvertUtil;
import com.cdzy.payment.enums.EbikeOrderStatus;
import com.cdzy.payment.enums.PaymentMethod;
import com.cdzy.payment.mapper.EbikePaymentMapper;
import com.cdzy.payment.model.dto.EbikeOrderPaymentDto;
@ -23,7 +24,9 @@ import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
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.EbikeOrderTableDef.EBIKE_ORDER;
@ -44,40 +47,51 @@ public class EbikePaymentServiceImpl extends ServiceImpl<EbikePaymentMapper, Ebi
private EbikeOrderService ebikeOrderService;
@Override
public List<EbikePayment> getNoPayNotTimeOutOrder(int duration) {
public List<EbikePayment> getNoPayNotTimeOutOrder(int duration, int queryLimit) {
// 未支付状态(已超时),创建时间 >= 当前时间 - duration 分钟
LocalDateTime cutoffTime = LocalDateTime.now().minusMinutes(duration);
QueryWrapper query = QueryWrapper.create()
.where(EBIKE_PAYMENT.TRADE_STATUS.eq(Transaction.TradeStateEnum.NOTPAY.ordinal()))
.and(createTimeFilter(duration, ">="));
.where(EBIKE_PAYMENT.TRADE_STATUS.eq(EbikeOrderStatus.PENDING_PAYMENT))
.ge(EbikePayment::getCreateTime, cutoffTime)
.limit(queryLimit);
return list(query);
}
@Override
public List<EbikePayment> getNoPayOvertimeOrder(int duration) {
public List<EbikePayment> getNoPayOvertimeOrder(int duration, int queryLimit) {
// 未支付状态(未超时),创建时间 < 当前时间 - duration 分钟
LocalDateTime cutoffTime = LocalDateTime.now().minusMinutes(duration);
QueryWrapper query = QueryWrapper.create()
.where(EBIKE_PAYMENT.TRADE_STATUS.eq(Transaction.TradeStateEnum.NOTPAY.ordinal()))
.and(createTimeFilter(duration, "<"));
.where(EBIKE_PAYMENT.TRADE_STATUS.eq(EbikeOrderStatus.PENDING_PAYMENT))
.lt(EbikePayment::getCreateTime, cutoffTime)
.limit(queryLimit);
return list(query);
}
@Override
public Boolean updatePaymentStatus(Transaction transaction) {
EbikePayment ebikePayment = getByPaymentId(transaction.getOutTradeNo());
if (Objects.isNull(ebikePayment)) {
log.warn("未找到本地订单outTradeNo: {}", transaction.getOutTradeNo());
return false;
}
ebikePayment.setTradeStatus(transaction.getTradeState().ordinal());
if (Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) {
// 支付成功 更新订单状态
// 支付成功 更新支付状态
BigDecimal totalAmount = calculateTotalAmount(transaction);
ebikePayment.setTotal(totalAmount);
String paymentTime = transaction.getSuccessTime();
ebikePayment.setPaymentTime(StringUtils.toLocalDatetime(paymentTime));
ebikePayment.setTransactionId(transaction.getTransactionId());
// 同步支付状态
// 同步订单状态
EbikeOrderPaymentDto paymentParam = new EbikeOrderPaymentDto();
paymentParam.setOrderId(ebikePayment.getOrderId());
paymentParam.setPaymentTime(ebikePayment.getPaymentTime());
paymentParam.setPaymentMethod(PaymentMethod.WECHAT);
ebikeOrderService.payment(paymentParam);
} else if (Transaction.TradeStateEnum.CLOSED.equals(transaction.getTradeState())) {
// 微信订单已取消,同步更新订单为取消
ebikeOrderService.cancelOrder(ebikePayment.getOrderId());
}
return updateById(ebikePayment);
}
@ -143,6 +157,18 @@ public class EbikePaymentServiceImpl extends ServiceImpl<EbikePaymentMapper, Ebi
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查询支付记录
*

View File

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

View File

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

View File

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