YD_Event.Admin/YD_Event.Application/Event/PaymentExpirationBackgroundService.cs
2026-04-14 17:30:20 +08:00

549 lines
24 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// 支付超时处理后台服务
/// </summary>
public class PaymentExpirationBackgroundService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<PaymentExpirationBackgroundService> _logger;
private readonly PeriodicTimer _timer;
public PaymentExpirationBackgroundService(
IServiceProvider serviceProvider,
ILogger<PaymentExpirationBackgroundService> 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<ISqlSugarRepository<EventRegistration>>();
var orderRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<YD_Event.Core.Entities.Order>>();
var userCouponRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon>>();
var couponRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<YD_Event.Core.Entities.Coupon>>();
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, "处理超时未支付订单时发生错误");
}
}
/// <summary>
/// 释放优惠券,恢复为未使用状态
/// </summary>
private async Task ReleaseCouponAsync(long couponId, long userId, long orderId,
ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon> userCouponRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Coupon> 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}");
}
}
/// <summary>
/// 处理微信未支付订单:定期检查并更新订单状态
/// </summary>
private async Task ProcessUnpaidWeChatOrdersAsync(CancellationToken stoppingToken)
{
try
{
_logger.LogInformation("开始检查微信未支付订单");
using var scope = _serviceProvider.CreateScope();
var registrationRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<EventRegistration>>();
var orderRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<YD_Event.Core.Entities.Order>>();
var weChatPayConfigRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<WeChatPayConfig>>();
var weChatPayService = scope.ServiceProvider.GetRequiredService<WeChatPayService>();
var paymentRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<EventRegistrationPayment>>();
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<ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon>>();
var couponRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<YD_Event.Core.Entities.Coupon>>();
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, "检查微信未支付订单时发生错误");
}
}
/// <summary>
/// 处理微信支付成功的订单
/// </summary>
private async Task ProcessWeChatPaymentSuccessAsync(
EventRegistration registration,
YD_Event.Core.Entities.Order order,
WeChatPayQueryResult queryResult,
DateTime now,
ISqlSugarRepository<EventRegistration> registrationRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
ISqlSugarRepository<EventRegistrationPayment> 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<IIdGenerator>();
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);
}
/// <summary>
/// 处理微信支付关闭的订单
/// </summary>
private async Task ProcessWeChatPaymentClosedAsync(
EventRegistration registration,
YD_Event.Core.Entities.Order order,
DateTime now,
ISqlSugarRepository<EventRegistration> registrationRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon> userCouponRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Coupon> 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);
}
/// <summary>
/// 处理微信退款中的订单
/// </summary>
private async Task ProcessRefundingWeChatOrdersAsync(CancellationToken stoppingToken)
{
try
{
_logger.LogInformation("开始检查微信退款中的订单");
using var scope = _serviceProvider.CreateScope();
var refundRecordRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<RefundRecord>>();
var registrationRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<EventRegistration>>();
var orderRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<YD_Event.Core.Entities.Order>>();
var weChatPayConfigRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<WeChatPayConfig>>();
var weChatPayService = scope.ServiceProvider.GetRequiredService<WeChatPayService>();
var paymentRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<EventRegistrationPayment>>();
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, "检查微信退款订单时发生错误");
}
}
/// <summary>
/// 处理微信退款成功的订单
/// </summary>
private async Task ProcessWeChatRefundSuccessAsync(
RefundRecord refundRecord,
WeChatRefundQueryResult queryResult,
DateTime now,
ISqlSugarRepository<RefundRecord> refundRecordRepository,
ISqlSugarRepository<EventRegistration> registrationRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
ISqlSugarRepository<EventRegistrationPayment> 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);
}
/// <summary>
/// 处理微信退款失败的订单
/// </summary>
private async Task ProcessWeChatRefundFailedAsync(
RefundRecord refundRecord,
WeChatRefundQueryResult queryResult,
DateTime now,
ISqlSugarRepository<RefundRecord> 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();
}
}