Implement billing status notification and enhance user management integration
CI / Backend (build + test) (push) Successful in 1m30s
CI / Frontend (lint + typecheck + build) (push) Successful in 33s

- Added `NotifyBillingStatusChangedAsync` method to `IRealtimeNotifier` for notifying clients about changes in billing status.
- Updated `BillingConfigResumer` to call the new notification method after modifying billing configurations, ensuring users receive real-time updates.
- Enhanced `ListUsersQueryHandler` to include a `BillingPendingReview` property in `UserSummaryDto`, indicating if a user has a pending payment request awaiting confirmation.
- Refactored various command handlers to utilize `AdvisoryLock` for managing concurrent requests, preventing race conditions in billing operations.
- Updated tests to cover new notification behaviors and ensure proper functionality in billing status management.
This commit is contained in:
Leonid Pershin
2026-07-19 23:22:57 +03:00
parent b32756d5bc
commit e19860ba46
49 changed files with 1195 additions and 233 deletions
+3
View File
@@ -167,6 +167,9 @@ export type UserSummaryDto = {
activatedAt: string | null
billingEnabled: boolean
billingPaidUntil: string | null
/** Есть Subscription-заявка на оплату, ожидающая решения админа — конфиги не гасятся, пока он не
* решит (см. BillingService). Бейдж должен показывать нейтральный статус, а не тревожный "Истекло". */
billingPendingReview: boolean
}
export type RoleDto = {
+6
View File
@@ -132,6 +132,9 @@ const resources = {
requestStatus: {
AwaitingPayment: 'Ожидает оплаты',
AwaitingConfirmation: 'На проверке у администратора',
Confirmed: 'Подтверждена',
Rejected: 'Отклонена',
Cancelled: 'Отменена',
},
requisites: 'Реквизиты для оплаты',
requisitesMissing: 'Реквизиты ещё не настроены администратором.',
@@ -674,6 +677,9 @@ const resources = {
requestStatus: {
AwaitingPayment: 'Awaiting payment',
AwaitingConfirmation: 'Under admin review',
Confirmed: 'Confirmed',
Rejected: 'Rejected',
Cancelled: 'Cancelled',
},
requisites: 'Payment details',
requisitesMissing: 'The administrator has not set up payment details yet.',
@@ -8,6 +8,7 @@ type ConfigTrafficUpdated = { configId: string; usedUpBytes: number; usedDownByt
type ConfigStatusChanged = { configId: string; status: ConfigStatus }
type UserActivated = { userId: string }
type NewsPublished = { id: string; title: string; createdAt: string }
type BillingStatusChanged = { userId: string }
/** Живые обновления по SignalR: точечно патчит кэш TanStack Query вместо инвалидации всего списка. */
export function RealtimeProvider({ children }: { children: React.ReactNode }) {
@@ -54,10 +55,15 @@ export function RealtimeProvider({ children }: { children: React.ReactNode }) {
void queryClient.invalidateQueries({ queryKey: ['news'] })
}
const onBillingStatusChanged = (_payload: BillingStatusChanged) => {
void queryClient.invalidateQueries({ queryKey: ['my-billing-status'] })
}
connection.on('configTrafficUpdated', onTrafficUpdated)
connection.on('configStatusChanged', onStatusChanged)
connection.on('userActivated', onUserActivated)
connection.on('newsPublished', onNewsPublished)
connection.on('billingStatusChanged', onBillingStatusChanged)
void startConnection()
@@ -66,6 +72,7 @@ export function RealtimeProvider({ children }: { children: React.ReactNode }) {
connection.off('configStatusChanged', onStatusChanged)
connection.off('userActivated', onUserActivated)
connection.off('newsPublished', onNewsPublished)
connection.off('billingStatusChanged', onBillingStatusChanged)
}
}, [user, queryClient])