Enhance billing request handling to support immediate config suspension and protection
- Updated the `RejectPaymentRequestCommandHandler` to immediately suspend user configs if a payment request is rejected and no other pending subscription requests exist. - Introduced `SuspendIfStillUnpaidAsync` method to handle the logic for suspending configs based on the user's billing status. - Enhanced the `MarkPaymentSentCommandHandler` to protect configs during the payment confirmation process, ensuring users remain active while awaiting admin approval. - Refactored `BillingConfigResumer` to include methods for protecting and suspending configs, improving the overall billing management flow. - Updated tests to cover new behaviors and ensure proper functionality in various scenarios related to payment requests and config management.
This commit is contained in:
+50
-1
@@ -1,4 +1,5 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PnvPanel.Application.Auth;
|
||||
using PnvPanel.Application.Billing;
|
||||
using PnvPanel.Application.Common.Interfaces;
|
||||
@@ -11,8 +12,12 @@ namespace PnvPanel.Application.Admin.Billing;
|
||||
|
||||
public sealed class RejectPaymentRequestCommandHandler(
|
||||
IAppDbContext dbContext,
|
||||
IIdentityService identityService,
|
||||
IXuiPanelGateway gateway,
|
||||
IRealtimeNotifier notifier,
|
||||
ITelegramNotifier telegramNotifier,
|
||||
ICurrentUser currentUser
|
||||
ICurrentUser currentUser,
|
||||
ILogger<RejectPaymentRequestCommandHandler> logger
|
||||
) : ICommandHandler<RejectPaymentRequestCommand, Result>
|
||||
{
|
||||
public async Task<Result> Handle(
|
||||
@@ -38,6 +43,13 @@ public sealed class RejectPaymentRequestCommandHandler(
|
||||
|
||||
request.Reject(adminId, command.Reason);
|
||||
|
||||
// Пока заявка висела на проверке, конфиги могли быть временно "защищены" на панели
|
||||
// (ProtectPendingConfigsAsync — enable/expiresAt подвинуты вперёд без изменения локального
|
||||
// статуса). Раз оплату отклонили и период всё ещё просрочен, а других Subscription-заявок на
|
||||
// проверке нет — снимаем защиту немедленно, не дожидаясь часового тика BillingService.
|
||||
if (request.Kind == PaymentRequestKind.Subscription)
|
||||
await SuspendIfStillUnpaidAsync(request.Id, request.UserId, cancellationToken);
|
||||
|
||||
dbContext.AuditLogs.Add(
|
||||
AuditLog.Create(
|
||||
adminId,
|
||||
@@ -61,4 +73,41 @@ public sealed class RejectPaymentRequestCommandHandler(
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
private async Task SuspendIfStillUnpaidAsync(
|
||||
Guid rejectedRequestId,
|
||||
Guid userId,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var profile = await identityService.GetProfileAsync(userId, cancellationToken);
|
||||
if (profile is not { BillingEnabled: true })
|
||||
return;
|
||||
if (profile.BillingPaidUntil is { } paidUntil && paidUntil > DateTimeOffset.UtcNow)
|
||||
return;
|
||||
|
||||
// Саму отклоняемую заявку исключаем явно: request.Reject(...) уже поменял её статус на
|
||||
// Rejected в трекере EF, но до SaveChangesAsync (в конце пайплайна) в БД всё ещё лежит старое
|
||||
// значение AwaitingConfirmation — без Id-исключения запрос ниже ложно принял бы её за "ещё
|
||||
// одну" висящую заявку и никогда бы не приостанавливал конфиги.
|
||||
var hasOtherPending = await dbContext.PaymentRequests.AnyAsync(
|
||||
r =>
|
||||
r.Id != rejectedRequestId
|
||||
&& r.UserId == userId
|
||||
&& r.Kind == PaymentRequestKind.Subscription
|
||||
&& r.Status == PaymentRequestStatus.AwaitingConfirmation,
|
||||
cancellationToken
|
||||
);
|
||||
if (hasOtherPending)
|
||||
return;
|
||||
|
||||
await BillingConfigResumer.SuspendConfigsAsync(
|
||||
dbContext,
|
||||
gateway,
|
||||
notifier,
|
||||
logger,
|
||||
userId,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user