Implement extension request and gift functionalities in billing system
- Added new endpoints for creating and managing extension requests, allowing users to request billing period extensions. - Implemented admin approval processes for extension requests via Telegram, including inline buttons for approval and rejection. - Introduced a gifting feature for admins to grant additional billing days directly to users without a request. - Updated the support ticket model to accommodate extension requests and their associated properties. - Enhanced the Telegram notifier to inform admins of new extension requests and notify users of approval or rejection. - Updated frontend components to support the new extension request and gifting functionalities, including user interfaces for managing these features. - Revised API documentation to reflect the new endpoints and their usage in the billing context.
This commit is contained in:
+9
-59
@@ -7,7 +7,6 @@ using PnvPanel.Application.Common.Messaging;
|
||||
using PnvPanel.Application.Common.Models;
|
||||
using PnvPanel.Domain.Audit;
|
||||
using PnvPanel.Domain.Billing;
|
||||
using PnvPanel.Domain.Configs;
|
||||
|
||||
namespace PnvPanel.Application.Admin.Billing;
|
||||
|
||||
@@ -65,64 +64,15 @@ public sealed class ConfirmPaymentRequestCommandHandler(
|
||||
|
||||
request.Confirm(adminId);
|
||||
|
||||
var configs = await dbContext
|
||||
.VpnConfigs.Where(c =>
|
||||
c.UserId == request.UserId
|
||||
&& (c.Status == ConfigStatus.Active || c.Status == ConfigStatus.Expired)
|
||||
)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var config in configs)
|
||||
{
|
||||
if (config.Status == ConfigStatus.Expired)
|
||||
{
|
||||
var inbound = await dbContext
|
||||
.Inbounds.AsNoTracking()
|
||||
.FirstOrDefaultAsync(i => i.Id == config.InboundId, cancellationToken);
|
||||
var node = inbound is null
|
||||
? null
|
||||
: await dbContext
|
||||
.Nodes.AsNoTracking()
|
||||
.FirstOrDefaultAsync(n => n.Id == inbound.NodeId, cancellationToken);
|
||||
|
||||
if (inbound is not null && node is not null)
|
||||
{
|
||||
var updateResult = await gateway.UpdateClientAsync(
|
||||
node,
|
||||
inbound.RemoteInboundId,
|
||||
config.ClientExternalId,
|
||||
config.Protocol,
|
||||
config.Label ?? config.ClientEmail,
|
||||
enable: true,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
if (!updateResult.IsSuccess)
|
||||
{
|
||||
// Нода недоступна — не трогаем локальный статус, подхватится следующим
|
||||
// подтверждением/циклом BillingService (идемпотентно).
|
||||
logger.LogWarning(
|
||||
"Failed to enable client for config {ConfigId} on node {NodeId} while confirming payment {RequestId}: {Error}",
|
||||
config.Id,
|
||||
node.Id,
|
||||
request.Id,
|
||||
updateResult.Error
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
config.Resume();
|
||||
await notifier.NotifyConfigStatusChangedAsync(
|
||||
config.UserId,
|
||||
config.Id,
|
||||
config.Status,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
config.SetBillingExpiry(newPaidUntil);
|
||||
}
|
||||
await BillingConfigResumer.ResumeConfigsAsync(
|
||||
dbContext,
|
||||
gateway,
|
||||
notifier,
|
||||
logger,
|
||||
request.UserId,
|
||||
newPaidUntil,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
dbContext.AuditLogs.Add(
|
||||
AuditLog.Create(
|
||||
|
||||
Reference in New Issue
Block a user