using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
using YD_Event.Core.Infrastructure.WeChat;
using YD_Event.Core.Infrastructure.WeChat.Dto;
namespace YD_Event.Application.Event;
///
/// 支付超时处理后台服务
///
public class PaymentExpirationBackgroundService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger _logger;
private readonly PeriodicTimer _timer;
public PaymentExpirationBackgroundService(
IServiceProvider serviceProvider,
ILogger logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
_timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("支付超时处理后台服务已启动");
try
{
while (await _timer.WaitForNextTickAsync(stoppingToken))
{
await ProcessExpiredPaymentsAsync(stoppingToken);
await ProcessUnpaidWeChatOrdersAsync(stoppingToken);
await ProcessRefundingWeChatOrdersAsync(stoppingToken);
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("支付超时处理后台服务已停止");
}
}
private async Task ProcessExpiredPaymentsAsync(CancellationToken stoppingToken)
{
try
{
_logger.LogInformation("开始处理超时未支付订单");
using var scope = _serviceProvider.CreateScope();
var registrationRepository = scope.ServiceProvider.GetRequiredService>();
var orderRepository = scope.ServiceProvider.GetRequiredService>();
var userCouponRepository = scope.ServiceProvider.GetRequiredService>();
var couponRepository = scope.ServiceProvider.GetRequiredService>();
var now = DateTime.Now;
var expirationTime = now.AddMinutes(-30);
var expiredRegistrations = await registrationRepository.AsQueryable()
.Where(x => x.PaymentMethod == 2
&& x.PaymentStatus == 0
&& x.CreatedTime <= expirationTime
&& !x.DeleteMark)
.ToListAsync(stoppingToken);
if (expiredRegistrations.Any())
{
_logger.LogInformation("发现 {Count} 个超时未支付的线上订单", expiredRegistrations.Count);
var idsToCancel = expiredRegistrations.Select(x => x.Id).ToList();
var orderNos = expiredRegistrations.Where(x => !string.IsNullOrEmpty(x.OrderNo)).Select(x => x.OrderNo).ToList();
await registrationRepository.UpdateAsync(x => new EventRegistration
{
PaymentStatus = 4,
UpdatedTime = now
}, x => idsToCancel.Contains(x.Id));
if (orderNos.Any())
{
// 获取这些订单并释放优惠券
var orders = await orderRepository.AsQueryable()
.Where(x => orderNos.Contains(x.OrderNo) && x.CouponId.HasValue)
.ToListAsync(stoppingToken);
foreach (var order in orders)
{
await ReleaseCouponAsync(order.CouponId.Value, order.CreatedUserId, order.Id,
userCouponRepository, couponRepository, stoppingToken);
}
// 更新订单状态
await orderRepository.UpdateAsync(x => new YD_Event.Core.Entities.Order
{
OrderStatus = OrderStatus.Cancelled,
UpdatedTime = now
}, x => orderNos.Contains(x.OrderNo));
}
_logger.LogInformation("已成功取消 {Count} 个超时未支付订单", expiredRegistrations.Count);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "处理超时未支付订单时发生错误");
}
}
///
/// 释放优惠券,恢复为未使用状态
///
private async Task ReleaseCouponAsync(long couponId, long userId, long orderId,
ISqlSugarRepository userCouponRepository,
ISqlSugarRepository couponRepository,
CancellationToken stoppingToken)
{
var userCoupon = await userCouponRepository.GetFirstAsync(uc =>
uc.CouponId == couponId &&
uc.UserId == userId &&
uc.OrderId == orderId &&
!uc.DeleteMark, stoppingToken);
if (userCoupon != null && userCoupon.Status == 1)
{
userCoupon.Status = 0;
userCoupon.UseTime = null;
userCoupon.OrderId = null;
await userCouponRepository.UpdateAsync(userCoupon, stoppingToken);
// 减少优惠券的已使用数量
var coupon = await couponRepository.GetByIdAsync(couponId, stoppingToken);
if (coupon != null && coupon.UsedQuantity > 0)
{
coupon.UsedQuantity--;
await couponRepository.UpdateAsync(coupon, stoppingToken);
}
_logger.LogInformation($"优惠券已释放,用户优惠券ID={userCoupon.Id},订单ID={orderId}");
}
else if (userCoupon != null && userCoupon.Status == 0)
{
// 如果优惠券还没有被标记为已使用,只需要清空OrderId
userCoupon.OrderId = null;
await userCouponRepository.UpdateAsync(userCoupon, stoppingToken);
_logger.LogInformation($"优惠券OrderId已清空,用户优惠券ID={userCoupon.Id},订单ID={orderId}");
}
}
///
/// 处理微信未支付订单:定期检查并更新订单状态
///
private async Task ProcessUnpaidWeChatOrdersAsync(CancellationToken stoppingToken)
{
try
{
_logger.LogInformation("开始检查微信未支付订单");
using var scope = _serviceProvider.CreateScope();
var registrationRepository = scope.ServiceProvider.GetRequiredService>();
var orderRepository = scope.ServiceProvider.GetRequiredService>();
var weChatPayConfigRepository = scope.ServiceProvider.GetRequiredService>();
var weChatPayService = scope.ServiceProvider.GetRequiredService();
var paymentRepository = scope.ServiceProvider.GetRequiredService>();
var now = DateTime.Now;
var unpaidRegistrations = await registrationRepository.AsQueryable()
.Where(x => x.PaymentMethod == 2
&& x.PaymentMethodType == PaymentMethodType.WeChatPay
&& x.PaymentStatus == 0
&& !x.DeleteMark)
.OrderBy(x => x.CreatedTime)
.Take(50)
.ToListAsync(stoppingToken);
if (!unpaidRegistrations.Any())
{
_logger.LogInformation("没有发现需要检查的微信未支付订单");
return;
}
_logger.LogInformation("发现 {Count} 个微信未支付订单需要检查", unpaidRegistrations.Count);
foreach (var registration in unpaidRegistrations)
{
try
{
if (string.IsNullOrEmpty(registration.OrderNo))
{
_logger.LogWarning("报名记录 {RegistrationId} 没有订单编号", registration.Id);
continue;
}
var order = await orderRepository.GetFirstAsync(o => o.OrderNo == registration.OrderNo, stoppingToken);
if (order == null)
{
_logger.LogWarning("找不到订单编号 {OrderNo} 对应的订单记录", registration.OrderNo);
continue;
}
var weChatPayConfig = await weChatPayConfigRepository.GetFirstAsync(
c => c.MerchantId == order.MerchantId && c.Status == AvailabilityStatus.Enable && !c.DeleteMark,
stoppingToken);
if (weChatPayConfig == null)
{
_logger.LogWarning("商户 {MerchantId} 没有找到可用的微信支付配置", order.MerchantId);
continue;
}
var queryResult = await weChatPayService.QueryOrderAsync(weChatPayConfig, registration.OrderNo);
if (!queryResult.Success)
{
_logger.LogWarning("查询订单 {OrderNo} 状态失败: {Message}", registration.OrderNo, queryResult.Message);
continue;
}
_logger.LogInformation("订单 {OrderNo} 微信支付状态: {TradeState}", registration.OrderNo, queryResult.TradeState);
if (queryResult.TradeState == "SUCCESS")
{
await ProcessWeChatPaymentSuccessAsync(
registration,
order,
queryResult,
now,
registrationRepository,
orderRepository,
paymentRepository,
stoppingToken);
}
else if (queryResult.TradeState == "CLOSED" || queryResult.TradeState == "REVOKED")
{
var userCouponRepository = scope.ServiceProvider.GetRequiredService>();
var couponRepository = scope.ServiceProvider.GetRequiredService>();
await ProcessWeChatPaymentClosedAsync(
registration,
order,
now,
registrationRepository,
orderRepository,
userCouponRepository,
couponRepository,
stoppingToken);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "处理报名记录 {RegistrationId} 时发生错误", registration.Id);
}
}
_logger.LogInformation("微信未支付订单检查完成");
}
catch (Exception ex)
{
_logger.LogError(ex, "检查微信未支付订单时发生错误");
}
}
///
/// 处理微信支付成功的订单
///
private async Task ProcessWeChatPaymentSuccessAsync(
EventRegistration registration,
YD_Event.Core.Entities.Order order,
WeChatPayQueryResult queryResult,
DateTime now,
ISqlSugarRepository registrationRepository,
ISqlSugarRepository orderRepository,
ISqlSugarRepository paymentRepository,
CancellationToken stoppingToken)
{
_logger.LogInformation("订单 {OrderNo} 支付成功,更新订单状态", registration.OrderNo);
var paymentTime = queryResult.SuccessTime.HasValue ? queryResult.SuccessTime.Value.LocalDateTime : now;
registration.PaymentStatus = 1;
registration.PaymentTime = paymentTime;
registration.UpdatedTime = now;
await registrationRepository.UpdateAsync(registration);
order.OrderStatus = OrderStatus.Paid;
order.PaymentTime = paymentTime;
order.UpdatedTime = now;
await orderRepository.UpdateAsync(order);
var existingPayment = await paymentRepository.GetFirstAsync(p => p.RegistrationId == registration.Id, stoppingToken);
if (existingPayment != null)
{
existingPayment.PaymentStatus = 1;
existingPayment.PaymentTime = paymentTime;
existingPayment.UpdatedTime = now;
if (!string.IsNullOrEmpty(queryResult.TransactionId))
{
existingPayment.TransactionNo = queryResult.TransactionId;
}
await paymentRepository.UpdateAsync(existingPayment);
}
else
{
var idGenerator = _serviceProvider.GetRequiredService();
var newPayment = new EventRegistrationPayment
{
Id = idGenerator.NextId(),
RegistrationId = registration.Id,
TransactionNo = queryResult.TransactionId ?? string.Empty,
PaymentChannel = 1,
PaymentAmount = order.ActualAmount,
PaymentTime = paymentTime,
PaymentStatus = 1,
RefundStatus = 0,
RefundAmount = 0,
PayeeAccount = string.Empty,
CreatedTime = now,
CreatedUserId = registration.CreatedUserId
};
await paymentRepository.InsertAsync(newPayment);
}
_logger.LogInformation("订单 {OrderNo} 支付状态更新成功", registration.OrderNo);
}
///
/// 处理微信支付关闭的订单
///
private async Task ProcessWeChatPaymentClosedAsync(
EventRegistration registration,
YD_Event.Core.Entities.Order order,
DateTime now,
ISqlSugarRepository registrationRepository,
ISqlSugarRepository orderRepository,
ISqlSugarRepository userCouponRepository,
ISqlSugarRepository couponRepository,
CancellationToken stoppingToken)
{
_logger.LogInformation("订单 {OrderNo} 支付已关闭,更新订单状态为已取消", registration.OrderNo);
registration.PaymentStatus = 4;
registration.UpdatedTime = now;
await registrationRepository.UpdateAsync(registration);
order.OrderStatus = OrderStatus.Cancelled;
order.UpdatedTime = now;
await orderRepository.UpdateAsync(order);
// 释放优惠券
if (order.CouponId.HasValue)
{
await ReleaseCouponAsync(order.CouponId.Value, order.CreatedUserId, order.Id,
userCouponRepository, couponRepository, stoppingToken);
}
_logger.LogInformation("订单 {OrderNo} 状态更新为已取消成功", registration.OrderNo);
}
///
/// 处理微信退款中的订单
///
private async Task ProcessRefundingWeChatOrdersAsync(CancellationToken stoppingToken)
{
try
{
_logger.LogInformation("开始检查微信退款中的订单");
using var scope = _serviceProvider.CreateScope();
var refundRecordRepository = scope.ServiceProvider.GetRequiredService>();
var registrationRepository = scope.ServiceProvider.GetRequiredService>();
var orderRepository = scope.ServiceProvider.GetRequiredService>();
var weChatPayConfigRepository = scope.ServiceProvider.GetRequiredService>();
var weChatPayService = scope.ServiceProvider.GetRequiredService();
var paymentRepository = scope.ServiceProvider.GetRequiredService>();
var now = DateTime.Now;
var refundingRecords = await refundRecordRepository.AsQueryable()
.Where(x => x.RefundStatus == 0 && !x.DeleteMark)
.OrderBy(x => x.ApplyTime)
.Take(50)
.ToListAsync(stoppingToken);
if (!refundingRecords.Any())
{
_logger.LogInformation("没有发现需要检查的微信退款订单");
return;
}
_logger.LogInformation("发现 {Count} 个微信退款订单需要检查", refundingRecords.Count);
foreach (var refundRecord in refundingRecords)
{
try
{
if (string.IsNullOrEmpty(refundRecord.OutRefundNo))
{
_logger.LogWarning("退款记录 {RefundRecordId} 没有退款单号", refundRecord.Id);
continue;
}
WeChatPayConfig? weChatPayConfig = null;
if (refundRecord.WeChatPayConfigId.HasValue)
{
weChatPayConfig = await weChatPayConfigRepository.GetByIdAsync(refundRecord.WeChatPayConfigId.Value, stoppingToken);
}
else if (refundRecord.MerchantId.HasValue)
{
weChatPayConfig = await weChatPayConfigRepository.GetFirstAsync(
c => c.MerchantId == refundRecord.MerchantId && c.Status == AvailabilityStatus.Enable && !c.DeleteMark,
stoppingToken);
}
if (weChatPayConfig == null)
{
_logger.LogWarning("退款记录 {RefundRecordId} 没有找到可用的微信支付配置", refundRecord.Id);
continue;
}
var queryResult = await weChatPayService.QueryRefundAsync(weChatPayConfig, refundRecord.OutRefundNo);
if (!queryResult.Success)
{
_logger.LogWarning("查询退款 {OutRefundNo} 状态失败: {Message}", refundRecord.OutRefundNo, queryResult.Message);
continue;
}
_logger.LogInformation("退款 {OutRefundNo} 微信退款状态: {RefundStatus}", refundRecord.OutRefundNo, queryResult.RefundStatus);
if (queryResult.RefundStatus == "SUCCESS")
{
await ProcessWeChatRefundSuccessAsync(
refundRecord,
queryResult,
now,
refundRecordRepository,
registrationRepository,
orderRepository,
paymentRepository,
stoppingToken);
}
else if (queryResult.RefundStatus == "CLOSED" || queryResult.RefundStatus == "ABNORMAL")
{
await ProcessWeChatRefundFailedAsync(
refundRecord,
queryResult,
now,
refundRecordRepository,
stoppingToken);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "处理退款记录 {RefundRecordId} 时发生错误", refundRecord.Id);
}
}
_logger.LogInformation("微信退款订单检查完成");
}
catch (Exception ex)
{
_logger.LogError(ex, "检查微信退款订单时发生错误");
}
}
///
/// 处理微信退款成功的订单
///
private async Task ProcessWeChatRefundSuccessAsync(
RefundRecord refundRecord,
WeChatRefundQueryResult queryResult,
DateTime now,
ISqlSugarRepository refundRecordRepository,
ISqlSugarRepository registrationRepository,
ISqlSugarRepository orderRepository,
ISqlSugarRepository paymentRepository,
CancellationToken stoppingToken)
{
_logger.LogInformation("退款 {OutRefundNo} 成功,更新退款状态", refundRecord.OutRefundNo);
var successTime = queryResult.SuccessTime.HasValue ? queryResult.SuccessTime.Value.LocalDateTime : now;
refundRecord.RefundStatus = 1;
refundRecord.SuccessTime = successTime;
refundRecord.RefundId = queryResult.RefundId ?? refundRecord.RefundId;
refundRecord.WeChatResponse = queryResult.WeChatResponse ?? refundRecord.WeChatResponse;
refundRecord.UpdatedTime = now;
await refundRecordRepository.UpdateAsync(refundRecord);
var registration = await registrationRepository.GetByIdAsync(refundRecord.RegistrationId, stoppingToken);
if (registration != null)
{
registration.PaymentStatus = 3;
registration.UpdatedTime = now;
await registrationRepository.UpdateAsync(registration);
}
var order = await orderRepository.GetFirstAsync(o => o.OrderNo == refundRecord.OutTradeNo, stoppingToken);
if (order != null)
{
order.OrderStatus = OrderStatus.Refunded;
order.UpdatedTime = now;
await orderRepository.UpdateAsync(order);
}
var payment = await paymentRepository.GetFirstAsync(p => p.RegistrationId == refundRecord.RegistrationId, stoppingToken);
if (payment != null)
{
payment.RefundStatus = 2;
payment.RefundAmount = refundRecord.RefundAmount;
payment.RefundTime = successTime;
payment.UpdatedTime = now;
await paymentRepository.UpdateAsync(payment);
}
_logger.LogInformation("退款 {OutRefundNo} 状态更新成功", refundRecord.OutRefundNo);
}
///
/// 处理微信退款失败的订单
///
private async Task ProcessWeChatRefundFailedAsync(
RefundRecord refundRecord,
WeChatRefundQueryResult queryResult,
DateTime now,
ISqlSugarRepository refundRecordRepository,
CancellationToken stoppingToken)
{
_logger.LogInformation("退款 {OutRefundNo} 失败,更新退款状态", refundRecord.OutRefundNo);
refundRecord.RefundStatus = queryResult.RefundStatus == "CLOSED" ? 3 : 2;
refundRecord.ErrorMessage = queryResult.Message ?? "退款失败";
refundRecord.WeChatResponse = queryResult.WeChatResponse ?? refundRecord.WeChatResponse;
refundRecord.UpdatedTime = now;
await refundRecordRepository.UpdateAsync(refundRecord);
_logger.LogInformation("退款 {OutRefundNo} 状态更新为失败成功", refundRecord.OutRefundNo);
}
public override void Dispose()
{
_timer.Dispose();
base.Dispose();
}
}