报名失败
This commit is contained in:
parent
77996f25f2
commit
6e576690d2
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,5 +41,11 @@ public enum OrderStatus
|
||||
/// 已完成
|
||||
/// </summary>
|
||||
[Description("已完成")]
|
||||
Completed = 5
|
||||
Completed = 5,
|
||||
|
||||
/// <summary>
|
||||
/// 报名失败
|
||||
/// </summary>
|
||||
[Description("报名失败")]
|
||||
RegistrationFailed = 6
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user