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(); } }