""" Email notification templates for different notification types. Supports multiple languages: ru, en, zh, ua """ from typing import Any from app.config import settings class EmailNotificationTemplates: """HTML email templates for user notifications.""" def __init__(self): self.service_name = settings.SMTP_FROM_NAME or 'VPN Service' self.cabinet_url = getattr(settings, 'CABINET_URL', '') def get_template( self, notification_type: 'NotificationType', language: str, context: dict[str, Any], ) -> dict[str, str] | None: """ Get email template for notification type. Args: notification_type: Type of notification language: Language code (ru, en, zh, ua) context: Context data for template rendering Returns: Dict with 'subject', 'body_html', and optionally 'body_text' """ # Import here to avoid circular imports from app.services.notification_delivery_service import NotificationType template_map = { NotificationType.BALANCE_TOPUP: self._balance_topup_template, NotificationType.BALANCE_CHANGE: self._balance_change_template, NotificationType.SUBSCRIPTION_EXPIRING: self._subscription_expiring_template, NotificationType.SUBSCRIPTION_EXPIRED: self._subscription_expired_template, NotificationType.SUBSCRIPTION_RENEWED: self._subscription_renewed_template, NotificationType.SUBSCRIPTION_ACTIVATED: self._subscription_activated_template, NotificationType.AUTOPAY_SUCCESS: self._autopay_success_template, NotificationType.AUTOPAY_FAILED: self._autopay_failed_template, NotificationType.AUTOPAY_INSUFFICIENT_FUNDS: self._autopay_insufficient_funds_template, NotificationType.DAILY_DEBIT: self._daily_debit_template, NotificationType.DAILY_INSUFFICIENT_FUNDS: self._daily_insufficient_funds_template, NotificationType.BAN_NOTIFICATION: self._ban_template, NotificationType.UNBAN_NOTIFICATION: self._unban_template, NotificationType.WARNING_NOTIFICATION: self._warning_template, NotificationType.REFERRAL_BONUS: self._referral_bonus_template, NotificationType.REFERRAL_REGISTERED: self._referral_registered_template, NotificationType.TRAFFIC_RESET: self._traffic_reset_template, NotificationType.PAYMENT_RECEIVED: self._payment_received_template, } template_func = template_map.get(notification_type) if not template_func: return None return template_func(language, context) def _get_base_template(self, content: str) -> str: """Wrap content in base HTML template.""" return f"""
Сумма пополнения: +{amount}
Текущий баланс: {balance}
Спасибо за использование нашего сервиса!
{self._get_cabinet_button(language)} """, 'en': f"""Top-up amount: +{amount}
Current balance: {balance}
Thank you for using our service!
{self._get_cabinet_button(language)} """, 'zh': f"""充值金额: +{amount}
当前余额: {balance}
感谢使用我们的服务!
{self._get_cabinet_button(language)} """, 'ua': f"""Сума поповнення: +{amount}
Поточний баланс: {balance}
Дякуємо за використання нашого сервісу!
{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _balance_change_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for balance change notification.""" amount = context.get('formatted_amount', f'{context.get("amount_rubles", 0):.2f} ₽') balance = context.get('formatted_balance', f'{context.get("new_balance_rubles", 0):.2f} ₽') description = context.get('description', '') subjects = { 'ru': 'Изменение баланса', 'en': 'Balance Changed', 'zh': '余额变动', 'ua': 'Зміна балансу', } bodies = { 'ru': f"""Сумма: {amount}
Текущий баланс: {balance}
{f'Описание: {description}
' if description else ''}Amount: {amount}
Current balance: {balance}
{f'Description: {description}
' if description else ''}Ваша подписка истекает через {days_left} дн.
Дата истечения: {expires_at}
Продлите подписку, чтобы не потерять доступ к сервису.
{self._get_cabinet_button(language)} """, 'en': f"""Your subscription expires in {days_left} day(s).
Expiration date: {expires_at}
Renew your subscription to maintain access to our service.
{self._get_cabinet_button(language)} """, 'zh': f"""您的订阅将在 {days_left} 天后到期。
到期日期: {expires_at}
请续订以保持对服务的访问。
{self._get_cabinet_button(language)} """, 'ua': f"""Ваша підписка закінчується через {days_left} дн.
Дата закінчення: {expires_at}
Продовжіть підписку, щоб не втратити доступ до сервісу.
{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _subscription_expired_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for subscription expired notification.""" subjects = { 'ru': 'Подписка истекла', 'en': 'Subscription Expired', 'zh': '订阅已到期', 'ua': 'Підписка закінчилась', } bodies = { 'ru': f"""Ваша подписка истекла. Доступ к VPN отключён.
Оформите новую подписку, чтобы продолжить использование сервиса.
{self._get_cabinet_button(language)} """, 'en': f"""Your subscription has expired. VPN access has been disabled.
Purchase a new subscription to continue using our service.
{self._get_cabinet_button(language)} """, 'zh': f"""您的订阅已到期。VPN访问已被禁用。
请购买新订阅以继续使用我们的服务。
{self._get_cabinet_button(language)} """, 'ua': f"""Ваша підписка закінчилась. Доступ до VPN вимкнено.
Оформіть нову підписку, щоб продовжити використання сервісу.
{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _subscription_renewed_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for subscription renewed notification.""" new_expires_at = context.get('new_expires_at', '') subjects = { 'ru': 'Подписка продлена', 'en': 'Subscription Renewed', 'zh': '订阅已续订', 'ua': 'Підписку продовжено', } bodies = { 'ru': f"""Ваша подписка была успешно продлена.
Новая дата истечения: {new_expires_at}
Спасибо за использование нашего сервиса!
{self._get_cabinet_button(language)} """, 'en': f"""Your subscription has been successfully renewed.
New expiration date: {new_expires_at}
Thank you for using our service!
{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _subscription_activated_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for subscription activated notification.""" expires_at = context.get('expires_at', '') subjects = { 'ru': 'Подписка активирована', 'en': 'Subscription Activated', 'zh': '订阅已激活', 'ua': 'Підписку активовано', } bodies = { 'ru': f"""Ваша VPN подписка успешно активирована.
Действует до: {expires_at}
Теперь вы можете пользоваться VPN сервисом.
{self._get_cabinet_button(language)} """, 'en': f"""Your VPN subscription has been successfully activated.
Valid until: {expires_at}
You can now use the VPN service.
{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } # ============================================================================ # Autopay Templates # ============================================================================ def _autopay_success_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for successful autopay notification.""" amount = context.get('formatted_amount', f'{context.get("amount_rubles", 0):.2f} ₽') new_expires_at = context.get('new_expires_at', '') subjects = { 'ru': 'Автопродление выполнено', 'en': 'Auto-renewal Successful', 'zh': '自动续订成功', 'ua': 'Автопродовження виконано', } bodies = { 'ru': f"""Ваша подписка была автоматически продлена.
Списано с баланса: {amount}
Новая дата истечения: {new_expires_at}
Your subscription has been automatically renewed.
Charged from balance: {amount}
New expiration date: {new_expires_at}
Не удалось автоматически продлить подписку.
{f'Причина: {reason}
' if reason else ''}Пожалуйста, пополните баланс и продлите подписку вручную.
{self._get_cabinet_button(language)} """, 'en': f"""Failed to automatically renew your subscription.
{f'Reason: {reason}
' if reason else ''}Please top up your balance and renew manually.
{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _autopay_insufficient_funds_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for autopay insufficient funds notification.""" required = context.get('required_amount', '') balance = context.get('current_balance', '') subjects = { 'ru': 'Недостаточно средств для автопродления', 'en': 'Insufficient Funds for Auto-renewal', 'zh': '余额不足无法自动续订', 'ua': 'Недостатньо коштів для автопродовження', } bodies = { 'ru': f"""Недостаточно средств на балансе для автопродления подписки.
{f'Требуется: {required}
' if required else ''} {f'На балансе: {balance}
' if balance else ''}Пополните баланс, чтобы подписка была продлена автоматически.
{self._get_cabinet_button(language)} """, 'en': f"""Insufficient balance for subscription auto-renewal.
{f'Required: {required}
' if required else ''} {f'Balance: {balance}
' if balance else ''}Top up your balance for automatic renewal.
{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } # ============================================================================ # Daily Subscription Templates # ============================================================================ def _daily_debit_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for daily subscription debit notification.""" amount = context.get('formatted_amount', f'{context.get("amount_rubles", 0):.2f} ₽') balance = context.get('formatted_balance', f'{context.get("new_balance_rubles", 0):.2f} ₽') subjects = { 'ru': f'Списание за подписку: {amount}', 'en': f'Subscription charge: {amount}', 'zh': f'订阅扣费: {amount}', 'ua': f'Списання за підписку: {amount}', } bodies = { 'ru': f"""С вашего баланса списано: {amount}
Остаток на балансе: {balance}
Charged from your balance: {amount}
Remaining balance: {balance}
На балансе недостаточно средств для продления подписки.
Подписка будет приостановлена.
Пополните баланс, чтобы продолжить использование сервиса.
{self._get_cabinet_button(language)} """, 'en': f"""Insufficient balance to continue subscription.
Your subscription will be suspended.
Please top up your balance to continue using the service.
{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _traffic_reset_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for traffic reset notification.""" subjects = { 'ru': 'Трафик обновлён', 'en': 'Traffic Reset', 'zh': '流量已重置', 'ua': 'Трафік оновлено', } bodies = { 'ru': f"""Ваш трафик был сброшен. Вы можете продолжить использование VPN.
Your traffic has been reset. You can continue using the VPN.
Ваш аккаунт был заблокирован.
{f'Причина: {reason}
' if reason else ''}Если вы считаете, что это ошибка, обратитесь в поддержку.
""", 'en': f"""Your account has been suspended.
{f'Reason: {reason}
' if reason else ''}If you believe this is an error, please contact support.
""", } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _unban_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for unban notification.""" subjects = { 'ru': 'Аккаунт разблокирован', 'en': 'Account Reactivated', 'zh': '账户已解封', 'ua': 'Обліковий запис розблоковано', } bodies = { 'ru': f"""Ваш аккаунт был разблокирован.
Вы снова можете пользоваться сервисом.
Your account has been reactivated.
You can use the service again.
{message}
' if message else 'Вы получили предупреждение от администрации.
'}{message}
' if message else 'You have received a warning from the administration.
'}Вы получили реферальный бонус: +{bonus}
{f'Благодаря пользователю: {referral_name}
' if referral_name else ''}Продолжайте приглашать друзей и зарабатывайте больше!
{self._get_cabinet_button(language)} """, 'en': f"""You received a referral bonus: +{bonus}
{f'Thanks to: {referral_name}
' if referral_name else ''}Keep inviting friends and earn more!
{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _referral_registered_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for new referral registered notification.""" referral_name = context.get('referral_name', '') subjects = { 'ru': 'Новый реферал зарегистрирован', 'en': 'New Referral Registered', 'zh': '新推荐用户已注册', 'ua': 'Новий реферал зареєстрований', } bodies = { 'ru': f"""По вашей ссылке зарегистрировался новый пользователь{f': {referral_name}' if referral_name else ''}.
Вы будете получать бонусы с его пополнений!
{self._get_cabinet_button(language)} """, 'en': f"""A new user registered using your link{f': {referral_name}' if referral_name else ''}.
You will receive bonuses from their top-ups!
{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } # ============================================================================ # Payment Templates # ============================================================================ def _payment_received_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for payment received notification.""" amount = context.get('formatted_amount', f'{context.get("amount_rubles", 0):.2f} ₽') payment_method = context.get('payment_method', '') subjects = { 'ru': f'Платёж получен: {amount}', 'en': f'Payment received: {amount}', 'zh': f'收到付款: {amount}', 'ua': f'Платіж отримано: {amount}', } bodies = { 'ru': f"""Сумма: +{amount}
{f'Способ оплаты: {payment_method}
' if payment_method else ''}Спасибо за оплату!
{self._get_cabinet_button(language)} """, 'en': f"""Amount: +{amount}
{f'Payment method: {payment_method}
' if payment_method else ''}Thank you for your payment!
{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } # Singleton instance email_notification_templates = EmailNotificationTemplates()