482 lines
20 KiB
C#
482 lines
20 KiB
C#
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 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())
|
|
{
|
|
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 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")
|
|
{
|
|
await ProcessWeChatPaymentClosedAsync(
|
|
registration,
|
|
order,
|
|
now,
|
|
registrationRepository,
|
|
orderRepository,
|
|
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,
|
|
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);
|
|
|
|
_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();
|
|
}
|
|
}
|