feat(websocket): add real-time notifications for subscription and balance events

- Import and call notify_user_subscription_renewed in auto-extend flows
- Import and call notify_user_subscription_activated for new subscriptions
- Add WebSocket notifications to _auto_purchase_tariff and _auto_purchase_daily_tariff
- Add WebSocket notifications to auto_activate_subscription_after_topup
- Add notify_user_balance_topup call in payment common mixin
This commit is contained in:
c0mrade
2026-01-30 19:04:44 +03:00
parent d2ade1d9ed
commit 86350424d5
2 changed files with 137 additions and 0 deletions
+21
View File
@@ -14,6 +14,8 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from sqlalchemy.exc import MissingGreenlet
from sqlalchemy.ext.asyncio import AsyncSession
# WebSocket notifications for cabinet
from app.cabinet.routes.websocket import notify_user_balance_topup
from app.config import settings
from app.database.crud.user import get_user_by_telegram_id
from app.database.database import get_db
@@ -132,6 +134,25 @@ class PaymentCommonMixin:
payment_method_title: str | None = None,
) -> None:
"""Отправляет пользователю уведомление об успешном платеже."""
# Send WebSocket notification to cabinet frontend (works for both Telegram and email-only users)
user_id = getattr(user, 'id', None) if user else None
if user_id:
try:
# Get new balance from user
new_balance = getattr(user, 'balance_kopeks', 0)
await notify_user_balance_topup(
user_id=user_id,
amount_kopeks=amount_kopeks,
new_balance_kopeks=new_balance,
description=payment_method_title or '',
)
except Exception as ws_error:
logger.warning(
'Не удалось отправить WS уведомление о пополнении баланса для user_id=%s: %s',
user_id,
ws_error,
)
if not getattr(self, 'bot', None):
# Если бот не передан (например, внутри фоновых задач), уведомление пропускаем.
return
@@ -9,6 +9,11 @@ from aiogram import Bot
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from sqlalchemy.ext.asyncio import AsyncSession
# WebSocket notifications for cabinet
from app.cabinet.routes.websocket import (
notify_user_subscription_activated,
notify_user_subscription_renewed,
)
from app.config import settings
from app.database.crud.subscription import extend_subscription
from app.database.crud.transaction import create_transaction
@@ -559,6 +564,20 @@ async def _auto_extend_subscription(
_format_user_id(user),
)
# Send WebSocket notification to cabinet frontend
try:
await notify_user_subscription_renewed(
user_id=user.id,
new_expires_at=new_end_date.isoformat() if new_end_date else '',
amount_kopeks=prepared.price_kopeks,
)
except Exception as ws_error:
logger.warning(
'⚠️ Автопокупка: не удалось отправить WS уведомление о продлении для %s: %s',
_format_user_id(user),
ws_error,
)
return True
@@ -814,6 +833,29 @@ async def _auto_purchase_tariff(
_format_user_id(user),
)
# Send WebSocket notification to cabinet frontend
try:
if existing_subscription:
# Renewal of existing subscription
await notify_user_subscription_renewed(
user_id=user.id,
new_expires_at=subscription.end_date.isoformat() if subscription.end_date else '',
amount_kopeks=final_price,
)
else:
# New subscription activation
await notify_user_subscription_activated(
user_id=user.id,
expires_at=subscription.end_date.isoformat() if subscription.end_date else '',
tariff_name=tariff.name,
)
except Exception as ws_error:
logger.warning(
'⚠️ Автопокупка тарифа: не удалось отправить WS уведомление для %s: %s',
_format_user_id(user),
ws_error,
)
return True
@@ -1051,6 +1093,29 @@ async def _auto_purchase_daily_tariff(
_format_user_id(user),
)
# Send WebSocket notification to cabinet frontend
try:
if existing_subscription:
# Renewal/upgrade of existing subscription
await notify_user_subscription_renewed(
user_id=user.id,
new_expires_at=subscription.end_date.isoformat() if subscription.end_date else '',
amount_kopeks=daily_price,
)
else:
# New subscription activation
await notify_user_subscription_activated(
user_id=user.id,
expires_at=subscription.end_date.isoformat() if subscription.end_date else '',
tariff_name=tariff.name,
)
except Exception as ws_error:
logger.warning(
'⚠️ Автопокупка суточного тарифа: не удалось отправить WS уведомление для %s: %s',
_format_user_id(user),
ws_error,
)
return True
@@ -1243,6 +1308,29 @@ async def auto_purchase_saved_cart_after_topup(
_format_user_id(user),
)
# Send WebSocket notification to cabinet frontend
try:
if was_trial_conversion:
# Trial conversion = activation
await notify_user_subscription_activated(
user_id=user.id,
expires_at=subscription.end_date.isoformat() if subscription and subscription.end_date else '',
tariff_name='',
)
else:
# Regular purchase = renewal or new activation
await notify_user_subscription_renewed(
user_id=user.id,
new_expires_at=subscription.end_date.isoformat() if subscription and subscription.end_date else '',
amount_kopeks=pricing.final_total,
)
except Exception as ws_error:
logger.warning(
'⚠️ Автопокупка: не удалось отправить WS уведомление для %s: %s',
_format_user_id(user),
ws_error,
)
return True
@@ -1397,6 +1485,20 @@ async def auto_activate_subscription_after_topup(
best_price,
)
# Send WebSocket notification to cabinet frontend
try:
await notify_user_subscription_renewed(
user_id=user.id,
new_expires_at=result.subscription.end_date.isoformat() if result.subscription.end_date else '',
amount_kopeks=best_price,
)
except Exception as ws_error:
logger.warning(
'⚠️ Автоактивация: не удалось отправить WS уведомление о продлении для %s: %s',
_format_user_id(user),
ws_error,
)
# Уведомление пользователю (только для Telegram-пользователей)
if bot and user.telegram_id:
try:
@@ -1475,6 +1577,20 @@ async def auto_activate_subscription_after_topup(
best_price,
)
# Send WebSocket notification to cabinet frontend
try:
await notify_user_subscription_activated(
user_id=user.id,
expires_at=new_subscription.end_date.isoformat() if new_subscription.end_date else '',
tariff_name='',
)
except Exception as ws_error:
logger.warning(
'⚠️ Автоактивация: не удалось отправить WS уведомление об активации для %s: %s',
_format_user_id(user),
ws_error,
)
# Уведомление пользователю (только для Telegram-пользователей)
if bot and user.telegram_id:
try: