Refactor billing configuration management to centralize expiration handling
CI / Backend (build + test) (push) Failing after 1m8s
CI / Frontend (lint + typecheck + build) (push) Successful in 34s

- Updated `BillingConfigResumer` to manage billing expiration entirely on the application side, using `enable` as the sole control mechanism for client access.
- Changed all relevant methods to pass `DateTimeOffset.UnixEpoch` for `expiresAt`, ensuring the panel does not enforce expiration independently of our application logic.
- Modified `IXuiPanelGateway` interface to reflect the new expiration handling approach, clarifying the role of `expiresAt` in client management.
- Adjusted command handlers for creating and rotating VPN configurations to set `expiresAt` to `null`, preventing unintended expiration enforcement by the panel.
- Enhanced documentation to explain the new billing expiration management strategy and its implications for client configurations.
This commit is contained in:
Leonid Pershin
2026-07-22 23:43:45 +03:00
parent 33ad98cf62
commit 29291b5dec
9 changed files with 102 additions and 82 deletions
@@ -193,9 +193,10 @@ public class ConfirmPaymentRequestCommandHandlerTests
Assert.NotNull(expiredConfig.ExpiresAt);
Assert.NotNull(activeConfig.ExpiresAt);
Assert.Equal(expiredConfig.ExpiresAt, activeConfig.ExpiresAt);
// enable:true И expiresAt=новый newPaidUntil вместе (см. IXuiPanelGateway.UpdateClientAsync) —
// и для ранее Expired конфига (ext-1), и для уже Active (ext-2): пользователь мог доплатить
// заранее, панель должна узнать новый срок сразу, а не только когда конфиг реально просрочится.
// enable:true — истечение управляется полностью на нашей стороне, панельный expiresAt всегда
// сбрасывается в "без срока" (см. BillingConfigResumer.NoExpiry), и для ранее Expired конфига
// (ext-1), и для уже Active (ext-2): пользователь мог доплатить заранее, панель должна узнать
// об этом сразу, а не только когда конфиг реально просрочится.
await _gateway
.Received(1)
.UpdateClientAsync(
@@ -205,7 +206,7 @@ public class ConfirmPaymentRequestCommandHandlerTests
VpnProtocol.Vless,
Arg.Any<string>(),
true,
Arg.Is<DateTimeOffset?>(d => d == expiredConfig.ExpiresAt),
Arg.Is<DateTimeOffset?>(d => d == DateTimeOffset.UnixEpoch),
Arg.Any<CancellationToken>()
);
await _gateway
@@ -217,7 +218,7 @@ public class ConfirmPaymentRequestCommandHandlerTests
VpnProtocol.Vless,
Arg.Any<string>(),
true,
Arg.Is<DateTimeOffset?>(d => d == activeConfig.ExpiresAt),
Arg.Is<DateTimeOffset?>(d => d == DateTimeOffset.UnixEpoch),
Arg.Any<CancellationToken>()
);
await _notifier.Received(1).NotifyBillingStatusChangedAsync(userId, Arg.Any<CancellationToken>());
@@ -135,6 +135,8 @@ public class RejectPaymentRequestCommandHandlerTests
Assert.True(result.IsSuccess);
Assert.Equal(ConfigStatus.Expired, config.Status);
// enable:false — истечение управляется полностью на нашей стороне, панельный expiresAt
// сбрасывается в "без срока" (см. BillingConfigResumer.NoExpiry), не реальную просроченную дату.
await _gateway
.Received(1)
.UpdateClientAsync(
@@ -144,7 +146,7 @@ public class RejectPaymentRequestCommandHandlerTests
VpnProtocol.Vless,
Arg.Any<string>(),
false,
Arg.Any<DateTimeOffset?>(),
Arg.Is<DateTimeOffset?>(d => d == DateTimeOffset.UnixEpoch),
Arg.Any<CancellationToken>()
);
// Фронт (/billing) должен узнать о смене статуса сразу, не дожидаясь опроса.