报名失败

This commit is contained in:
马梓彭 2026-05-14 09:31:29 +08:00
parent 77996f25f2
commit 6e576690d2
3 changed files with 294 additions and 2 deletions

View File

@ -23,7 +23,7 @@ public class PaymentExpirationBackgroundService : BackgroundService
{
_serviceProvider = serviceProvider;
_logger = logger;
_timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
_timer = new PeriodicTimer(TimeSpan.FromMinutes(0.2));
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
@ -37,6 +37,7 @@ public class PaymentExpirationBackgroundService : BackgroundService
await ProcessExpiredPaymentsAsync(stoppingToken);
await ProcessUnpaidWeChatOrdersAsync(stoppingToken);
await ProcessRefundingWeChatOrdersAsync(stoppingToken);
await ProcessFailedRegistrationsAsync(stoppingToken);
}
}
catch (OperationCanceledException)
@ -540,6 +541,90 @@ public class PaymentExpirationBackgroundService : BackgroundService
_logger.LogInformation("退款 {OutRefundNo} 状态更新为失败成功", refundRecord.OutRefundNo);
}
/// <summary>
/// 处理已取消但实际支付成功的报名(报名失败退款)
/// </summary>
private async Task ProcessFailedRegistrationsAsync(CancellationToken stoppingToken)
{
try
{
_logger.LogInformation("开始检查已取消的报名,将查询微信订单状态");
using var scope = _serviceProvider.CreateScope();
var registrationRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<EventRegistration>>();
var unifiedRegistrationService = scope.ServiceProvider.GetRequiredService<UnifiedRegistrationService>();
var oneDayAgo = DateTime.Now.AddDays(-1);
// 只查询一天内已取消状态的报名
var cancelledRegistrations = await registrationRepository.AsQueryable()
.Where(x => x.PaymentStatus == 4 && !x.DeleteMark && x.CreatedTime >= oneDayAgo)
.OrderBy(x => x.CreatedTime)
.Take(50)
.ToListAsync(stoppingToken);
if (!cancelledRegistrations.Any())
{
_logger.LogInformation("没有需要检查的已取消报名");
return;
}
_logger.LogInformation("发现 {Count} 个已取消的报名需要检查", cancelledRegistrations.Count);
foreach (var registration in cancelledRegistrations)
{
try
{
stoppingToken.ThrowIfCancellationRequested();
// 检查是否已经有退款记录,避免重复处理
var refundRecordRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<RefundRecord>>();
var existingRefund = await refundRecordRepository.GetFirstAsync(
r => r.RegistrationId == registration.Id && !r.DeleteMark,
stoppingToken);
if (existingRefund != null)
{
_logger.LogInformation("报名 {RegistrationId} 已有退款记录,跳过", registration.Id);
continue;
}
_logger.LogInformation("检查已取消的报名,报名ID={RegistrationId}", registration.Id);
// 调用统一报名服务处理退款
var success = await unifiedRegistrationService.ProcessFailedRegistrationRefundAsync(registration.Id, 0);
if (success)
{
_logger.LogInformation("报名失败退款处理成功,报名ID={RegistrationId}", registration.Id);
}
else
{
_logger.LogWarning("报名失败退款处理失败,报名ID={RegistrationId}", registration.Id);
}
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "处理报名 {RegistrationId} 时发生异常", registration.Id);
}
}
_logger.LogInformation("已取消报名检查完成");
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "检查已取消但支付成功的报名时发生异常");
}
}
public override void Dispose()
{
_timer.Dispose();

View File

@ -4010,4 +4010,205 @@ public class UnifiedRegistrationService : ITransient
}
#endregion
/// <summary>
/// 处理已取消但实际支付成功的退款(报名失败退款)
/// </summary>
/// <param name="registrationId">报名ID</param>
/// <param name="operatorUserId">操作人用户ID</param>
/// <returns>是否成功</returns>
[UnitOfWork]
public async Task<bool> ProcessFailedRegistrationRefundAsync(long registrationId, long operatorUserId)
{
try
{
_logger.LogInformation("开始处理报名失败退款,报名ID={RegistrationId}", registrationId);
var registration = await _registrationRepository.GetByIdAsync(registrationId);
if (registration == null)
{
_logger.LogWarning("报名记录不存在,报名ID={RegistrationId}", registrationId);
return false;
}
// 检查是否已经是已取消状态
if (registration.PaymentStatus != 4)
{
_logger.LogWarning("报名状态不是已取消,报名ID={RegistrationId}, 当前状态={Status}", registrationId, registration.PaymentStatus);
return false;
}
// 获取订单信息
var order = await _orderRepository.GetFirstAsync(o => o.RegistrationId == registrationId && o.OrderType == 1);
if (order == null)
{
_logger.LogWarning("找不到订单记录,报名ID={RegistrationId}", registrationId);
return false;
}
// 获取赛事信息
var eventInfo = await _eventRepository.GetByIdAsync(registration.EventId);
if (eventInfo == null)
{
_logger.LogWarning("找不到赛事信息,报名ID={RegistrationId}, 赛事ID={EventId}", registrationId, registration.EventId);
return false;
}
var now = DateTime.Now;
// 获取微信支付配置
var wechatConfig = await GetWeChatPayConfigByEventAsync(eventInfo);
if (wechatConfig == null)
{
_logger.LogError("找不到微信支付配置,报名ID={RegistrationId}", registrationId);
return false;
}
// 先从微信查询订单状态,确认是否真的支付成功
var wechatQueryResult = await _weChatPayService.QueryOrderAsync(wechatConfig, registration.OrderNo);
if (!wechatQueryResult.Success)
{
_logger.LogWarning("查询微信订单失败,报名ID={RegistrationId}, 订单号={OrderNo}, 错误={Error}",
registrationId, registration.OrderNo, wechatQueryResult.Message);
return false;
}
if (wechatQueryResult.TradeState != "SUCCESS")
{
_logger.LogInformation("微信订单不是支付成功状态,报名ID={RegistrationId}, 订单号={OrderNo}, 状态={TradeState}",
registrationId, registration.OrderNo, wechatQueryResult.TradeState);
return false;
}
_logger.LogInformation("确认微信订单已支付成功,报名ID={RegistrationId}, 订单号={OrderNo}",
registrationId, registration.OrderNo);
// 1. 更新报名状态为"已退款"(状态3)
registration.PaymentStatus = 3;
registration.UpdatedTime = now;
registration.UpdatedUserId = operatorUserId;
registration.RegistrationRemark = "报名失败,已全额退款";
await _registrationRepository.UpdateAsync(registration);
// 2. 更新订单状态为"报名失败"
order.OrderStatus = OrderStatus.RegistrationFailed;
order.UpdatedTime = now;
order.UpdatedUserId = operatorUserId;
await _orderRepository.UpdateAsync(order);
// 3. 生成退款单号
var refundOrderNo = await _orderNoGeneratorService.GenerateRefundOrderNoAsync();
// 4. 构建退款通知文案
var refundReason = $"{eventInfo.EventName} - 报名失败";
// 5. 创建退款订单记录
var refundOrder = new YD_Event.Core.Entities.Order
{
Id = _idGenerator.NewLong(),
OrderNo = refundOrderNo,
OrderType = 2,
MerchantId = order.MerchantId,
MerchantCertificationId = order.MerchantCertificationId,
EventId = order.EventId,
RegistrationId = order.RegistrationId,
RelatedOrderId = order.Id,
OriginalAmount = order.ActualAmount,
ActualAmount = order.ActualAmount,
DiscountAmount = 0,
OrderStatus = OrderStatus.Refunded,
PaymentTime = now,
Remark = refundReason,
CreatedTime = now,
CreatedUserId = operatorUserId,
Status = AvailabilityStatus.Enable
};
await _orderRepository.InsertAsync(refundOrder);
// 6. 调用微信退款接口
var refundRequest = new Core.Infrastructure.WeChat.Dto.CreateRefundRequest
{
OutTradeNo = registration.OrderNo,
OutRefundNo = refundOrderNo,
RefundAmount = AmountHelper.YuanToFen(order.ActualAmount),
TotalAmount = AmountHelper.YuanToFen(order.ActualAmount),
Reason = refundReason
};
var refundResult = await _weChatPayService.CreateRefundAsync(wechatConfig, refundRequest);
if (!refundResult.Success)
{
_logger.LogError("微信退款申请失败,报名ID={RegistrationId}, 错误={Error}", registrationId, refundResult.Message);
return false;
}
// 7. 获取或创建支付记录
var payment = await _paymentRepository.GetFirstAsync(p => p.RegistrationId == registrationId && !p.DeleteMark);
if (payment == null)
{
payment = new EventRegistrationPayment
{
Id = _idGenerator.NewLong(),
RegistrationId = registration.Id,
TransactionNo = wechatQueryResult.TransactionId ?? "",
PaymentChannel = 1,
PaymentAmount = order.ActualAmount,
PaymentTime = wechatQueryResult.SuccessTime?.DateTime ?? now,
PaymentStatus = 1,
RefundStatus = 0,
RefundAmount = 0,
PayeeAccount = "",
CreatedTime = now,
CreatedUserId = operatorUserId
};
await _paymentRepository.InsertAsync(payment);
}
// 8. 创建退款记录
await CreateRefundRecordAsync(
registration,
eventInfo,
wechatConfig,
refundOrderNo,
refundResult.RefundId,
order.ActualAmount,
order.ActualAmount,
refundReason,
operatorUserId
);
// 9. 更新支付记录的退款状态
payment.RefundStatus = 2;
payment.RefundAmount = order.ActualAmount;
payment.RefundTime = now;
payment.UpdatedTime = now;
payment.UpdatedUserId = operatorUserId;
await _paymentRepository.UpdateAsync(payment);
// 10. 软删除报名人员和项目
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registrationId);
if (persons.Count > 0)
{
await SoftDeletePersonsAsync(persons.Select(p => p.Id).ToList(), operatorUserId, now, refundOrder.Id);
}
var items = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == registrationId);
if (items.Count > 0)
{
await SoftDeleteItemsAsync(items.Select(i => i.Id).ToList(), operatorUserId, now, refundOrder.Id);
}
_logger.LogInformation("报名失败退款处理成功,报名ID={RegistrationId}, 退款金额={Amount}, 退款单号={RefundNo}",
registrationId, order.ActualAmount, refundOrderNo);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "处理报名失败退款时发生异常,报名ID={RegistrationId}", registrationId);
return false;
}
}
}

View File

@ -41,5 +41,11 @@ public enum OrderStatus
/// 已完成
/// </summary>
[Description("已完成")]
Completed = 5
Completed = 5,
/// <summary>
/// 报名失败
/// </summary>
[Description("报名失败")]
RegistrationFailed = 6
}