diff --git a/src/components/WebSocketNotifications.tsx b/src/components/WebSocketNotifications.tsx
index f8b38ea..31e0ab1 100644
--- a/src/components/WebSocketNotifications.tsx
+++ b/src/components/WebSocketNotifications.tsx
@@ -7,7 +7,7 @@ import { useCallback } from 'react';
import { useNavigate } from 'react-router';
import { useTranslation } from 'react-i18next';
import { useQueryClient } from '@tanstack/react-query';
-import { useWebSocket, WSMessage } from '../hooks/useWebSocket';
+import { useWebSocket, type WSMessage } from '../hooks/useWebSocket';
import { useToast } from './Toast';
import { useAuthStore } from '../store/auth';
import { useCurrency } from '../hooks/useCurrency';
@@ -296,6 +296,131 @@ export default function WebSocketNotifications() {
return;
}
+ // SBP recurring events
+ if (type === 'sbp_recurring.confirmed') {
+ const amount = message.amount_rubles ?? (message.amount_kopeks ?? 0) / 100;
+ showToast({
+ type: 'success',
+ title: t('wsNotifications.sbpRecurring.confirmedTitle', 'SBP payment confirmed'),
+ message: t(
+ 'wsNotifications.sbpRecurring.confirmedMessage',
+ 'Payment of {{amount}} {{currency}} was charged successfully',
+ {
+ amount: formatAmount(amount),
+ currency: currencySymbol,
+ },
+ ),
+ icon: 🔁,
+ onClick: () => navigate('/subscriptions'),
+ duration: 8000,
+ });
+ queryClient.invalidateQueries({
+ predicate: (query) =>
+ Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription',
+ });
+ queryClient.invalidateQueries({
+ predicate: (query) =>
+ Array.isArray(query.queryKey) && query.queryKey[0] === 'sbp-recurring',
+ });
+ queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] });
+ refreshUser();
+ return;
+ }
+
+ if (type === 'sbp_recurring.activated') {
+ showToast({
+ type: 'success',
+ title: t('wsNotifications.sbpRecurring.activatedTitle', 'SBP auto-payment connected'),
+ message: t(
+ 'wsNotifications.sbpRecurring.activatedMessage',
+ 'SBP auto-payment has been successfully connected',
+ ),
+ icon: 🔁,
+ onClick: () => navigate('/subscriptions'),
+ duration: 8000,
+ });
+ queryClient.invalidateQueries({
+ predicate: (query) =>
+ Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription',
+ });
+ queryClient.invalidateQueries({
+ predicate: (query) =>
+ Array.isArray(query.queryKey) && query.queryKey[0] === 'sbp-recurring',
+ });
+ queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] });
+ refreshUser();
+ return;
+ }
+
+ if (type === 'sbp_recurring.failed') {
+ showToast({
+ type: 'error',
+ title: t('wsNotifications.sbpRecurring.failedTitle', 'SBP payment failed'),
+ message: t(
+ 'wsNotifications.sbpRecurring.failedMessage',
+ 'Failed to charge your SBP auto-payment',
+ ),
+ icon: ❌,
+ onClick: () => navigate('/subscriptions'),
+ duration: 10000,
+ });
+ queryClient.invalidateQueries({
+ predicate: (query) =>
+ Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription',
+ });
+ queryClient.invalidateQueries({
+ predicate: (query) =>
+ Array.isArray(query.queryKey) && query.queryKey[0] === 'sbp-recurring',
+ });
+ return;
+ }
+
+ if (type === 'sbp_recurring.past_due') {
+ showToast({
+ type: 'warning',
+ title: t('wsNotifications.sbpRecurring.pastDueTitle', 'SBP payment overdue'),
+ message: t(
+ 'wsNotifications.sbpRecurring.pastDueMessage',
+ 'Your SBP auto-payment is overdue. Please check your banking app.',
+ ),
+ icon: ⚠️,
+ onClick: () => navigate('/subscriptions'),
+ duration: 10000,
+ });
+ queryClient.invalidateQueries({
+ predicate: (query) =>
+ Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription',
+ });
+ queryClient.invalidateQueries({
+ predicate: (query) =>
+ Array.isArray(query.queryKey) && query.queryKey[0] === 'sbp-recurring',
+ });
+ return;
+ }
+
+ if (type === 'sbp_recurring.cancelled') {
+ showToast({
+ type: 'warning',
+ title: t('wsNotifications.sbpRecurring.cancelledTitle', 'SBP auto-payment disabled'),
+ message: t(
+ 'wsNotifications.sbpRecurring.cancelledMessage',
+ 'SBP auto-payment has been disabled',
+ ),
+ icon: 🔕,
+ onClick: () => navigate('/subscriptions'),
+ duration: 8000,
+ });
+ queryClient.invalidateQueries({
+ predicate: (query) =>
+ Array.isArray(query.queryKey) && query.queryKey[0] === 'subscription',
+ });
+ queryClient.invalidateQueries({
+ predicate: (query) =>
+ Array.isArray(query.queryKey) && query.queryKey[0] === 'sbp-recurring',
+ });
+ return;
+ }
+
// Account events
if (type === 'account.banned') {
showToast({
diff --git a/src/providers/WebSocketContext.ts b/src/providers/WebSocketContext.ts
index 6c4cbb1..8a23823 100644
--- a/src/providers/WebSocketContext.ts
+++ b/src/providers/WebSocketContext.ts
@@ -21,6 +21,9 @@ export interface WSMessage {
new_expires_at?: string;
tariff_name?: string;
days_left?: number;
+ // SBP recurring events
+ status?: string;
+ next_charge_at?: string | null;
// Device purchase events
devices_added?: number;
new_device_limit?: number;