Files
remnawave-bedolaga-telegram…/app/cabinet/services/email_templates.py
T
c0mrade d071269b8c fix: comprehensive multi-subscription audit fixes across routes, handlers, and services
- Fix UUID resolution in monitoring and webhook services for multi-tariff mode
- Update cabinet routes to properly resolve per-subscription UUIDs
- Fix account merge service for multi-tariff subscription transfers
- Update admin handlers (users, promo_offers, servers) for multi-subscription
- Fix traffic, devices, servers, daily subscription modules
- Update payment handlers (stars, yookassa) and purchase services
- Fix broadcast, promocode, and subscription auto-purchase services
- Add multi-subscription support to keyboards and localization
- Fix CRUD operations for subscription queries
- Resolve code quality issues (ruff linting)
2026-03-23 18:40:23 +03:00

1861 lines
88 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Email notification templates for different notification types.
Supports multiple languages: ru, en, zh, ua, fa
"""
import html
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, fa)
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.PARTNER_APPLICATION_APPROVED: self._partner_approved_template,
NotificationType.PARTNER_APPLICATION_REJECTED: self._partner_rejected_template,
NotificationType.WITHDRAWAL_APPROVED: self._withdrawal_approved_template,
NotificationType.WITHDRAWAL_REJECTED: self._withdrawal_rejected_template,
NotificationType.TRAFFIC_RESET: self._traffic_reset_template,
NotificationType.PAYMENT_RECEIVED: self._payment_received_template,
NotificationType.EMAIL_VERIFICATION: self._email_verification_template,
NotificationType.PASSWORD_RESET: self._password_reset_template,
NotificationType.GUEST_SUBSCRIPTION_DELIVERED: self._guest_subscription_delivered_template,
NotificationType.GUEST_ACTIVATION_REQUIRED: self._guest_activation_required_template,
NotificationType.GUEST_GIFT_RECEIVED: self._guest_gift_received_template,
NotificationType.GUEST_CABINET_CREDENTIALS: self._guest_cabinet_credentials_template,
}
template_func = template_map.get(notification_type)
if not template_func:
return None
return template_func(language, context)
def _wrap_override_template(self, content: str, language: str = 'ru') -> str:
"""Wrap override template content appropriately based on its structure.
Three-tier detection:
1. Full HTML document (<!DOCTYPE or <html>) — return as-is, no wrapping
2. Styled content (has <style> tag or background CSS) — minimal HTML wrapper
without forced colors, headers, or footers
3. Simple HTML fragment — wrap with base template (header, footer, white bg)
for backward compatibility
"""
content_stripped = content.strip()
content_lower = content_stripped.lower()
# Tier 1: Full HTML document — return as-is
if content_lower.startswith('<!doctype') or content_lower.startswith('<html'):
return content_stripped
# Tier 2: Styled content — minimal wrapper without forced styling
if '<style' in content_lower or 'background' in content_lower:
return f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin: 0; padding: 0;">
{content}
</body>
</html>"""
# Tier 3: Simple HTML fragment — use base template for structure
return self._get_base_template(content, language)
def _get_base_template(self, content: str, language: str = 'ru') -> str:
"""Wrap content in base HTML template."""
footer_texts = {
'ru': 'Это автоматическое сообщение. Пожалуйста, не отвечайте на это письмо.',
'en': 'This is an automated message. Please do not reply to this email.',
'zh': '这是一封自动发送的邮件,请勿回复。',
'ua': 'Це автоматичне повідомлення. Будь ласка, не відповідайте на цей лист.',
'fa': 'این یک پیام خودکار است. لطفاً به این ایمیل پاسخ ندهید.',
}
footer_text = footer_texts.get(language, footer_texts['ru'])
return f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
margin: 0;
padding: 0;
}}
.container {{
max-width: 600px;
margin: 0 auto;
padding: 20px;
background-color: #ffffff;
}}
.header {{
text-align: center;
padding: 20px 0;
border-bottom: 2px solid #007bff;
}}
.header h1 {{
color: #007bff;
margin: 0;
font-size: 24px;
}}
.content {{
padding: 30px 20px;
}}
.highlight {{
background-color: #f8f9fa;
border-left: 4px solid #007bff;
padding: 15px;
margin: 20px 0;
}}
.success {{
border-left-color: #28a745;
}}
.warning {{
border-left-color: #ffc107;
}}
.danger {{
border-left-color: #dc3545;
}}
.button {{
display: inline-block;
padding: 12px 24px;
background-color: #007bff;
color: white !important;
text-decoration: none;
border-radius: 5px;
margin: 20px 0;
font-weight: bold;
}}
.button:hover {{
background-color: #0056b3;
}}
.footer {{
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
font-size: 12px;
color: #666;
text-align: center;
}}
.amount {{
font-size: 24px;
font-weight: bold;
color: #28a745;
}}
.amount.negative {{
color: #dc3545;
}}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>{self.service_name}</h1>
</div>
<div class="content">
{content}
</div>
<div class="footer">
<p>&copy; {self.service_name}</p>
<p>{footer_text}</p>
</div>
</div>
</body>
</html>
"""
def _get_cabinet_button(self, language: str) -> str:
"""Get cabinet link button HTML."""
if not self.cabinet_url:
return ''
texts = {
'ru': 'Открыть личный кабинет',
'en': 'Open Dashboard',
'zh': '打开控制面板',
'ua': 'Відкрити особистий кабінет',
'fa': 'باز کردن پنل کاربری',
}
text = texts.get(language, texts['en'])
return f'<p style="text-align: center;"><a href="{self.cabinet_url}" class="button">{text}</a></p>'
# ============================================================================
# Balance Templates
# ============================================================================
def _balance_topup_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for balance top-up 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'Balance topped up by {amount}',
'zh': f'余额已充值 {amount}',
'ua': f'Баланс поповнено на {amount}',
}
bodies = {
'ru': f"""
<h2>Баланс успешно пополнен!</h2>
<div class="highlight success">
<p>Сумма пополнения: <span class="amount">+{amount}</span></p>
<p>Текущий баланс: <strong>{balance}</strong></p>
</div>
<p>Спасибо за использование нашего сервиса!</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Balance Successfully Topped Up!</h2>
<div class="highlight success">
<p>Top-up amount: <span class="amount">+{amount}</span></p>
<p>Current balance: <strong>{balance}</strong></p>
</div>
<p>Thank you for using our service!</p>
{self._get_cabinet_button(language)}
""",
'zh': f"""
<h2>充值成功!</h2>
<div class="highlight success">
<p>充值金额: <span class="amount">+{amount}</span></p>
<p>当前余额: <strong>{balance}</strong></p>
</div>
<p>感谢使用我们的服务!</p>
{self._get_cabinet_button(language)}
""",
'ua': f"""
<h2>Баланс успішно поповнено!</h2>
<div class="highlight success">
<p>Сума поповнення: <span class="amount">+{amount}</span></p>
<p>Поточний баланс: <strong>{balance}</strong></p>
</div>
<p>Дякуємо за використання нашого сервісу!</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
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}')
subjects = {
'ru': 'Изменение баланса',
'en': 'Balance Changed',
'zh': '余额变动',
'ua': 'Зміна балансу',
}
bodies = {
'ru': f"""
<h2>Изменение баланса</h2>
<div class="highlight">
<p>Сумма: <strong>{amount}</strong></p>
<p>Текущий баланс: <strong>{balance}</strong></p>
</div>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Balance Changed</h2>
<div class="highlight">
<p>Amount: <strong>{amount}</strong></p>
<p>Current balance: <strong>{balance}</strong></p>
</div>
{self._get_cabinet_button(language)}
""",
'zh': f"""
<h2>余额变动</h2>
<div class="highlight">
<p>金额: <strong>{amount}</strong></p>
<p>当前余额: <strong>{balance}</strong></p>
</div>
{self._get_cabinet_button(language)}
""",
'ua': f"""
<h2>Зміна балансу</h2>
<div class="highlight">
<p>Сума: <strong>{amount}</strong></p>
<p>Поточний баланс: <strong>{balance}</strong></p>
</div>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
# ============================================================================
# Subscription Templates
# ============================================================================
def _subscription_expiring_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for subscription expiring notification."""
days_left = context.get('days_left', 0)
expires_at = context.get('expires_at', '')
tariff_name = html.escape(context.get('tariff_name', ''))
tariff_suffix_ru = f' «{tariff_name}»' if tariff_name else ''
tariff_suffix_en = f' "{tariff_name}"' if tariff_name else ''
tariff_line_ru = f'<p>Тариф: <strong>{tariff_name}</strong></p>' if tariff_name else ''
tariff_line_en = f'<p>Plan: <strong>{tariff_name}</strong></p>' if tariff_name else ''
tariff_line_zh = f'<p>套餐: <strong>{tariff_name}</strong></p>' if tariff_name else ''
tariff_line_ua = f'<p>Тариф: <strong>{tariff_name}</strong></p>' if tariff_name else ''
subjects = {
'ru': f'Подписка{tariff_suffix_ru} истекает через {days_left} дн.',
'en': f'Subscription{tariff_suffix_en} expires in {days_left} day(s)',
'zh': f'订阅将在 {days_left} 天后到期',
'ua': f'Підписка{tariff_suffix_ru} закінчується через {days_left} дн.',
}
bodies = {
'ru': f"""
<h2>Подписка скоро истекает</h2>
<div class="highlight warning">
{tariff_line_ru}
<p>Ваша подписка истекает через <strong>{days_left}</strong> дн.</p>
<p>Дата истечения: <strong>{expires_at}</strong></p>
</div>
<p>Продлите подписку, чтобы не потерять доступ к сервису.</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Subscription Expiring Soon</h2>
<div class="highlight warning">
{tariff_line_en}
<p>Your subscription expires in <strong>{days_left}</strong> day(s).</p>
<p>Expiration date: <strong>{expires_at}</strong></p>
</div>
<p>Renew your subscription to maintain access to our service.</p>
{self._get_cabinet_button(language)}
""",
'zh': f"""
<h2>订阅即将到期</h2>
<div class="highlight warning">
{tariff_line_zh}
<p>您的订阅将在 <strong>{days_left}</strong> 天后到期。</p>
<p>到期日期: <strong>{expires_at}</strong></p>
</div>
<p>请续订以保持对服务的访问。</p>
{self._get_cabinet_button(language)}
""",
'ua': f"""
<h2>Підписка скоро закінчується</h2>
<div class="highlight warning">
{tariff_line_ua}
<p>Ваша підписка закінчується через <strong>{days_left}</strong> дн.</p>
<p>Дата закінчення: <strong>{expires_at}</strong></p>
</div>
<p>Продовжіть підписку, щоб не втратити доступ до сервісу.</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
def _subscription_expired_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for subscription expired notification."""
tariff_name = html.escape(context.get('tariff_name', ''))
tariff_suffix_ru = f' «{tariff_name}»' if tariff_name else ''
tariff_suffix_en = f' "{tariff_name}"' if tariff_name else ''
tariff_line_ru = f'<p>Тариф: <strong>{tariff_name}</strong></p>' if tariff_name else ''
tariff_line_en = f'<p>Plan: <strong>{tariff_name}</strong></p>' if tariff_name else ''
tariff_line_zh = f'<p>套餐: <strong>{tariff_name}</strong></p>' if tariff_name else ''
tariff_line_ua = f'<p>Тариф: <strong>{tariff_name}</strong></p>' if tariff_name else ''
subjects = {
'ru': f'Подписка{tariff_suffix_ru} истекла',
'en': f'Subscription{tariff_suffix_en} Expired',
'zh': '订阅已到期',
'ua': f'Підписка{tariff_suffix_ru} закінчилась',
}
bodies = {
'ru': f"""
<h2>Подписка истекла</h2>
<div class="highlight danger">
{tariff_line_ru}
<p>Ваша подписка истекла. Доступ к VPN отключён.</p>
</div>
<p>Оформите новую подписку, чтобы продолжить использование сервиса.</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Subscription Expired</h2>
<div class="highlight danger">
{tariff_line_en}
<p>Your subscription has expired. VPN access has been disabled.</p>
</div>
<p>Purchase a new subscription to continue using our service.</p>
{self._get_cabinet_button(language)}
""",
'zh': f"""
<h2>订阅已到期</h2>
<div class="highlight danger">
{tariff_line_zh}
<p>您的订阅已到期。VPN访问已被禁用。</p>
</div>
<p>请购买新订阅以继续使用我们的服务。</p>
{self._get_cabinet_button(language)}
""",
'ua': f"""
<h2>Підписка закінчилась</h2>
<div class="highlight danger">
{tariff_line_ua}
<p>Ваша підписка закінчилась. Доступ до VPN вимкнено.</p>
</div>
<p>Оформіть нову підписку, щоб продовжити використання сервісу.</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
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', '')
tariff_name = html.escape(context.get('tariff_name', ''))
tariff_suffix_ru = f' «{tariff_name}»' if tariff_name else ''
tariff_suffix_en = f' "{tariff_name}"' if tariff_name else ''
tariff_line_ru = f'<p>Тариф: <strong>{tariff_name}</strong></p>' if tariff_name else ''
tariff_line_en = f'<p>Plan: <strong>{tariff_name}</strong></p>' if tariff_name else ''
subjects = {
'ru': f'Подписка{tariff_suffix_ru} продлена',
'en': f'Subscription{tariff_suffix_en} Renewed',
'zh': '订阅已续订',
'ua': f'Підписку{tariff_suffix_ru} продовжено',
}
bodies = {
'ru': f"""
<h2>Подписка успешно продлена!</h2>
<div class="highlight success">
{tariff_line_ru}
<p>Ваша подписка была успешно продлена.</p>
<p>Новая дата истечения: <strong>{new_expires_at}</strong></p>
</div>
<p>Спасибо за использование нашего сервиса!</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Subscription Successfully Renewed!</h2>
<div class="highlight success">
{tariff_line_en}
<p>Your subscription has been successfully renewed.</p>
<p>New expiration date: <strong>{new_expires_at}</strong></p>
</div>
<p>Thank you for using our service!</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
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', '')
tariff_name = html.escape(context.get('tariff_name', ''))
tariff_suffix_ru = f' «{tariff_name}»' if tariff_name else ''
tariff_suffix_en = f' "{tariff_name}"' if tariff_name else ''
tariff_line_ru = f'<p>Тариф: <strong>{tariff_name}</strong></p>' if tariff_name else ''
tariff_line_en = f'<p>Plan: <strong>{tariff_name}</strong></p>' if tariff_name else ''
subjects = {
'ru': f'Подписка{tariff_suffix_ru} активирована',
'en': f'Subscription{tariff_suffix_en} Activated',
'zh': '订阅已激活',
'ua': f'Підписку{tariff_suffix_ru} активовано',
}
bodies = {
'ru': f"""
<h2>Подписка активирована!</h2>
<div class="highlight success">
{tariff_line_ru}
<p>Ваша VPN подписка успешно активирована.</p>
<p>Действует до: <strong>{expires_at}</strong></p>
</div>
<p>Теперь вы можете пользоваться VPN сервисом.</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Subscription Activated!</h2>
<div class="highlight success">
{tariff_line_en}
<p>Your VPN subscription has been successfully activated.</p>
<p>Valid until: <strong>{expires_at}</strong></p>
</div>
<p>You can now use the VPN service.</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
# ============================================================================
# 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"""
<h2>Автопродление выполнено</h2>
<div class="highlight success">
<p>Ваша подписка была автоматически продлена.</p>
<p>Списано с баланса: <strong>{amount}</strong></p>
<p>Новая дата истечения: <strong>{new_expires_at}</strong></p>
</div>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Auto-renewal Successful</h2>
<div class="highlight success">
<p>Your subscription has been automatically renewed.</p>
<p>Charged from balance: <strong>{amount}</strong></p>
<p>New expiration date: <strong>{new_expires_at}</strong></p>
</div>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
def _autopay_failed_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for failed autopay notification."""
reason = html.escape(context.get('reason', ''))
subjects = {
'ru': 'Ошибка автопродления',
'en': 'Auto-renewal Failed',
'zh': '自动续订失败',
'ua': 'Помилка автопродовження',
}
bodies = {
'ru': f"""
<h2>Ошибка автопродления</h2>
<div class="highlight danger">
<p>Не удалось автоматически продлить подписку.</p>
{f'<p>Причина: {reason}</p>' if reason else ''}
</div>
<p>Пожалуйста, пополните баланс и продлите подписку вручную.</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Auto-renewal Failed</h2>
<div class="highlight danger">
<p>Failed to automatically renew your subscription.</p>
{f'<p>Reason: {reason}</p>' if reason else ''}
</div>
<p>Please top up your balance and renew manually.</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
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"""
<h2>Недостаточно средств</h2>
<div class="highlight warning">
<p>Недостаточно средств на балансе для автопродления подписки.</p>
{f'<p>Требуется: <strong>{required}</strong></p>' if required else ''}
{f'<p>На балансе: <strong>{balance}</strong></p>' if balance else ''}
</div>
<p>Пополните баланс, чтобы подписка была продлена автоматически.</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Insufficient Funds</h2>
<div class="highlight warning">
<p>Insufficient balance for subscription auto-renewal.</p>
{f'<p>Required: <strong>{required}</strong></p>' if required else ''}
{f'<p>Balance: <strong>{balance}</strong></p>' if balance else ''}
</div>
<p>Top up your balance for automatic renewal.</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
# ============================================================================
# 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"""
<h2>Ежедневное списание</h2>
<div class="highlight">
<p>С вашего баланса списано: <strong>{amount}</strong></p>
<p>Остаток на балансе: <strong>{balance}</strong></p>
</div>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Daily Charge</h2>
<div class="highlight">
<p>Charged from your balance: <strong>{amount}</strong></p>
<p>Remaining balance: <strong>{balance}</strong></p>
</div>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
def _daily_insufficient_funds_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for daily subscription insufficient funds."""
subjects = {
'ru': 'Недостаточно средств для продления',
'en': 'Insufficient Funds',
'zh': '余额不足',
'ua': 'Недостатньо коштів',
}
bodies = {
'ru': f"""
<h2>Недостаточно средств</h2>
<div class="highlight danger">
<p>На балансе недостаточно средств для продления подписки.</p>
<p>Подписка будет приостановлена.</p>
</div>
<p>Пополните баланс, чтобы продолжить использование сервиса.</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Insufficient Funds</h2>
<div class="highlight danger">
<p>Insufficient balance to continue subscription.</p>
<p>Your subscription will be suspended.</p>
</div>
<p>Please top up your balance to continue using the service.</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
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"""
<h2>Трафик обновлён</h2>
<div class="highlight success">
<p>Ваш трафик был сброшен. Вы можете продолжить использование VPN.</p>
</div>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Traffic Reset</h2>
<div class="highlight success">
<p>Your traffic has been reset. You can continue using the VPN.</p>
</div>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
# ============================================================================
# Account Status Templates
# ============================================================================
def _ban_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for ban notification."""
reason = html.escape(context.get('reason', ''))
subjects = {
'ru': 'Аккаунт заблокирован',
'en': 'Account Suspended',
'zh': '账户已被封禁',
'ua': 'Обліковий запис заблоковано',
}
bodies = {
'ru': f"""
<h2>Аккаунт заблокирован</h2>
<div class="highlight danger">
<p>Ваш аккаунт был заблокирован.</p>
{f'<p>Причина: {reason}</p>' if reason else ''}
</div>
<p>Если вы считаете, что это ошибка, обратитесь в поддержку.</p>
""",
'en': f"""
<h2>Account Suspended</h2>
<div class="highlight danger">
<p>Your account has been suspended.</p>
{f'<p>Reason: {reason}</p>' if reason else ''}
</div>
<p>If you believe this is an error, please contact support.</p>
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
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"""
<h2>Аккаунт разблокирован</h2>
<div class="highlight success">
<p>Ваш аккаунт был разблокирован.</p>
<p>Вы снова можете пользоваться сервисом.</p>
</div>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Account Reactivated</h2>
<div class="highlight success">
<p>Your account has been reactivated.</p>
<p>You can use the service again.</p>
</div>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
def _warning_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for warning notification."""
message = html.escape(context.get('message', ''))
subjects = {
'ru': 'Предупреждение',
'en': 'Warning',
'zh': '警告',
'ua': 'Попередження',
}
bodies = {
'ru': f"""
<h2>Предупреждение</h2>
<div class="highlight warning">
{f'<p>{message}</p>' if message else '<p>Вы получили предупреждение от администрации.</p>'}
</div>
""",
'en': f"""
<h2>Warning</h2>
<div class="highlight warning">
{f'<p>{message}</p>' if message else '<p>You have received a warning from the administration.</p>'}
</div>
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
# ============================================================================
# Referral Templates
# ============================================================================
def _referral_bonus_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for referral bonus notification."""
bonus = context.get('formatted_bonus', f'{context.get("bonus_rubles", 0):.2f}')
referral_name = html.escape(context.get('referral_name', ''))
subjects = {
'ru': f'Реферальный бонус: +{bonus}',
'en': f'Referral bonus: +{bonus}',
'zh': f'推荐奖励: +{bonus}',
'ua': f'Реферальний бонус: +{bonus}',
}
bodies = {
'ru': f"""
<h2>Реферальный бонус!</h2>
<div class="highlight success">
<p>Вы получили реферальный бонус: <span class="amount">+{bonus}</span></p>
{f'<p>Благодаря пользователю: {referral_name}</p>' if referral_name else ''}
</div>
<p>Продолжайте приглашать друзей и зарабатывайте больше!</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Referral Bonus!</h2>
<div class="highlight success">
<p>You received a referral bonus: <span class="amount">+{bonus}</span></p>
{f'<p>Thanks to: {referral_name}</p>' if referral_name else ''}
</div>
<p>Keep inviting friends and earn more!</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
def _referral_registered_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for new referral registered notification."""
referral_name = html.escape(context.get('referral_name', ''))
subjects = {
'ru': 'Новый реферал зарегистрирован',
'en': 'New Referral Registered',
'zh': '新推荐用户已注册',
'ua': 'Новий реферал зареєстрований',
}
bodies = {
'ru': f"""
<h2>Новый реферал!</h2>
<div class="highlight success">
<p>По вашей ссылке зарегистрировался новый пользователь{f': <strong>{referral_name}</strong>' if referral_name else ''}.</p>
</div>
<p>Вы будете получать бонусы с его пополнений!</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>New Referral!</h2>
<div class="highlight success">
<p>A new user registered using your link{f': <strong>{referral_name}</strong>' if referral_name else ''}.</p>
</div>
<p>You will receive bonuses from their top-ups!</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
# ============================================================================
# Partner Templates
# ============================================================================
def _partner_approved_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for partner application approved notification."""
commission = context.get('commission_percent', 0)
comment = html.escape(context.get('comment', ''))
subjects = {
'ru': 'Заявка на партнёрство одобрена',
'en': 'Partner Application Approved',
'zh': '合作伙伴申请已批准',
'ua': 'Заявка на партнерство схвалена',
}
bodies = {
'ru': f"""
<h2>Заявка на партнёрство одобрена!</h2>
<div class="highlight success">
<p>Ваша заявка на партнёрство была одобрена.</p>
<p>Ваша комиссия: <strong>{commission}%</strong></p>
{f'<p>Комментарий: {comment}</p>' if comment else ''}
</div>
<p>Теперь вы можете приглашать пользователей и получать вознаграждение!</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Partner Application Approved!</h2>
<div class="highlight success">
<p>Your partner application has been approved.</p>
<p>Your commission rate: <strong>{commission}%</strong></p>
{f'<p>Comment: {comment}</p>' if comment else ''}
</div>
<p>You can now invite users and earn rewards!</p>
{self._get_cabinet_button(language)}
""",
'zh': f"""
<h2>合作伙伴申请已批准!</h2>
<div class="highlight success">
<p>您的合作伙伴申请已获批准。</p>
<p>您的佣金比例: <strong>{commission}%</strong></p>
{f'<p>备注: {comment}</p>' if comment else ''}
</div>
<p>您现在可以邀请用户并获得奖励!</p>
{self._get_cabinet_button(language)}
""",
'ua': f"""
<h2>Заявка на партнерство схвалена!</h2>
<div class="highlight success">
<p>Вашу заявку на партнерство було схвалено.</p>
<p>Ваша комісія: <strong>{commission}%</strong></p>
{f'<p>Коментар: {comment}</p>' if comment else ''}
</div>
<p>Тепер ви можете запрошувати користувачів та отримувати винагороду!</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
def _partner_rejected_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for partner application rejected notification."""
comment = html.escape(context.get('comment', ''))
subjects = {
'ru': 'Заявка на партнёрство отклонена',
'en': 'Partner Application Rejected',
'zh': '合作伙伴申请被拒绝',
'ua': 'Заявка на партнерство відхилена',
}
bodies = {
'ru': f"""
<h2>Заявка на партнёрство отклонена</h2>
<div class="highlight danger">
<p>К сожалению, ваша заявка на партнёрство была отклонена.</p>
{f'<p>Причина: {comment}</p>' if comment else ''}
</div>
<p>Вы можете подать новую заявку позже.</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Partner Application Rejected</h2>
<div class="highlight danger">
<p>Unfortunately, your partner application has been rejected.</p>
{f'<p>Reason: {comment}</p>' if comment else ''}
</div>
<p>You can submit a new application later.</p>
{self._get_cabinet_button(language)}
""",
'zh': f"""
<h2>合作伙伴申请被拒绝</h2>
<div class="highlight danger">
<p>很抱歉,您的合作伙伴申请已被拒绝。</p>
{f'<p>原因: {comment}</p>' if comment else ''}
</div>
<p>您可以稍后提交新的申请。</p>
{self._get_cabinet_button(language)}
""",
'ua': f"""
<h2>Заявка на партнерство відхилена</h2>
<div class="highlight danger">
<p>На жаль, вашу заявку на партнерство було відхилено.</p>
{f'<p>Причина: {comment}</p>' if comment else ''}
</div>
<p>Ви можете подати нову заявку пізніше.</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
# ============================================================================
# Withdrawal Templates
# ============================================================================
def _withdrawal_approved_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for withdrawal approved notification."""
amount = context.get('formatted_amount', f'{context.get("amount_rubles", 0):.2f}')
comment = html.escape(context.get('comment', ''))
subjects = {
'ru': f'Запрос на вывод {amount} одобрен',
'en': f'Withdrawal request for {amount} approved',
'zh': f'提现请求 {amount} 已批准',
'ua': f'Запит на виведення {amount} схвалено',
}
bodies = {
'ru': f"""
<h2>Запрос на вывод одобрен!</h2>
<div class="highlight success">
<p>Ваш запрос на вывод средств одобрен.</p>
<p>Сумма: <span class="amount">{amount}</span></p>
{f'<p>Комментарий: {comment}</p>' if comment else ''}
</div>
<p>Средства будут переведены в ближайшее время.</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Withdrawal Request Approved!</h2>
<div class="highlight success">
<p>Your withdrawal request has been approved.</p>
<p>Amount: <span class="amount">{amount}</span></p>
{f'<p>Comment: {comment}</p>' if comment else ''}
</div>
<p>Funds will be transferred shortly.</p>
{self._get_cabinet_button(language)}
""",
'zh': f"""
<h2>提现请求已批准!</h2>
<div class="highlight success">
<p>您的提现请求已获批准。</p>
<p>金额: <span class="amount">{amount}</span></p>
{f'<p>备注: {comment}</p>' if comment else ''}
</div>
<p>资金将很快转入。</p>
{self._get_cabinet_button(language)}
""",
'ua': f"""
<h2>Запит на виведення схвалено!</h2>
<div class="highlight success">
<p>Ваш запит на виведення коштів було схвалено.</p>
<p>Сума: <span class="amount">{amount}</span></p>
{f'<p>Коментар: {comment}</p>' if comment else ''}
</div>
<p>Кошти будуть переведені найближчим часом.</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
def _withdrawal_rejected_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for withdrawal rejected notification."""
amount = context.get('formatted_amount', f'{context.get("amount_rubles", 0):.2f}')
comment = html.escape(context.get('comment', ''))
subjects = {
'ru': f'Запрос на вывод {amount} отклонён',
'en': f'Withdrawal request for {amount} rejected',
'zh': f'提现请求 {amount} 被拒绝',
'ua': f'Запит на виведення {amount} відхилено',
}
bodies = {
'ru': f"""
<h2>Запрос на вывод отклонён</h2>
<div class="highlight danger">
<p>Ваш запрос на вывод средств был отклонён.</p>
<p>Сумма: <strong>{amount}</strong></p>
{f'<p>Причина: {comment}</p>' if comment else ''}
</div>
<p>Средства возвращены на ваш баланс.</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Withdrawal Request Rejected</h2>
<div class="highlight danger">
<p>Your withdrawal request has been rejected.</p>
<p>Amount: <strong>{amount}</strong></p>
{f'<p>Reason: {comment}</p>' if comment else ''}
</div>
<p>Funds have been returned to your balance.</p>
{self._get_cabinet_button(language)}
""",
'zh': f"""
<h2>提现请求被拒绝</h2>
<div class="highlight danger">
<p>您的提现请求已被拒绝。</p>
<p>金额: <strong>{amount}</strong></p>
{f'<p>原因: {comment}</p>' if comment else ''}
</div>
<p>资金已退回您的余额。</p>
{self._get_cabinet_button(language)}
""",
'ua': f"""
<h2>Запит на виведення відхилено</h2>
<div class="highlight danger">
<p>Ваш запит на виведення коштів було відхилено.</p>
<p>Сума: <strong>{amount}</strong></p>
{f'<p>Причина: {comment}</p>' if comment else ''}
</div>
<p>Кошти повернуто на ваш баланс.</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
# ============================================================================
# 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"""
<h2>Платёж успешно обработан</h2>
<div class="highlight success">
<p>Сумма: <span class="amount">+{amount}</span></p>
{f'<p>Способ оплаты: {payment_method}</p>' if payment_method else ''}
</div>
<p>Спасибо за оплату!</p>
{self._get_cabinet_button(language)}
""",
'en': f"""
<h2>Payment Successfully Processed</h2>
<div class="highlight success">
<p>Amount: <span class="amount">+{amount}</span></p>
{f'<p>Payment method: {payment_method}</p>' if payment_method else ''}
</div>
<p>Thank you for your payment!</p>
{self._get_cabinet_button(language)}
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
# ============================================================================
# Auth Email Templates
# ============================================================================
def _email_verification_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for email verification."""
username = html.escape(context.get('username', ''))
verification_url = html.escape(context.get('verification_url', '#'))
expire_hours = context.get('expire_hours', 24)
subjects = {
'ru': 'Подтверждение email адреса',
'en': 'Verify your email address',
'zh': '验证您的邮箱地址',
'ua': 'Підтвердження email адреси',
}
greeting = {
'ru': f'Здравствуйте{", " + username if username else ""}!',
'en': f'Hello{", " + username if username else ""}!',
'zh': f'您好{", " + username if username else ""}!',
'ua': f'Вітаємо{", " + username if username else ""}!',
}
bodies = {
'ru': f"""
<h2>{greeting.get('ru')}</h2>
<p>Спасибо за регистрацию! Пожалуйста, подтвердите ваш email адрес, нажав на кнопку ниже:</p>
<p style="text-align: center;">
<a href="{verification_url}" class="button">Подтвердить email</a>
</p>
<p>Или скопируйте и вставьте эту ссылку в браузер:</p>
<p><a href="{verification_url}">{verification_url}</a></p>
<p>Ссылка действительна в течение {expire_hours} часов.</p>
<p style="color: #666;">Если вы не создавали аккаунт, просто проигнорируйте это письмо.</p>
""",
'en': f"""
<h2>{greeting.get('en')}</h2>
<p>Thank you for registering! Please verify your email address by clicking the button below:</p>
<p style="text-align: center;">
<a href="{verification_url}" class="button">Verify Email</a>
</p>
<p>Or copy and paste this link in your browser:</p>
<p><a href="{verification_url}">{verification_url}</a></p>
<p>This link will expire in {expire_hours} hours.</p>
<p style="color: #666;">If you didn't create an account, you can safely ignore this email.</p>
""",
'zh': f"""
<h2>{greeting.get('zh')}</h2>
<p>感谢您的注册!请点击下方按钮验证您的邮箱地址:</p>
<p style="text-align: center;">
<a href="{verification_url}" class="button">验证邮箱</a>
</p>
<p>或将此链接复制并粘贴到浏览器中:</p>
<p><a href="{verification_url}">{verification_url}</a></p>
<p>此链接将在 {expire_hours} 小时后过期。</p>
<p style="color: #666;">如果您没有创建账户,请忽略此邮件。</p>
""",
'ua': f"""
<h2>{greeting.get('ua')}</h2>
<p>Дякуємо за реєстрацію! Будь ласка, підтвердіть вашу email адресу, натиснувши на кнопку нижче:</p>
<p style="text-align: center;">
<a href="{verification_url}" class="button">Підтвердити email</a>
</p>
<p>Або скопіюйте та вставте це посилання в браузер:</p>
<p><a href="{verification_url}">{verification_url}</a></p>
<p>Посилання дійсне протягом {expire_hours} годин.</p>
<p style="color: #666;">Якщо ви не створювали акаунт, просто проігноруйте цей лист.</p>
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
def _password_reset_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for password reset."""
username = html.escape(context.get('username', ''))
reset_url = html.escape(context.get('reset_url', '#'))
expire_hours = context.get('expire_hours', 1)
subjects = {
'ru': 'Сброс пароля',
'en': 'Reset your password',
'zh': '重置您的密码',
'ua': 'Скидання пароля',
}
greeting = {
'ru': f'Здравствуйте{", " + username if username else ""}!',
'en': f'Hello{", " + username if username else ""}!',
'zh': f'您好{", " + username if username else ""}!',
'ua': f'Вітаємо{", " + username if username else ""}!',
}
bodies = {
'ru': f"""
<h2>{greeting.get('ru')}</h2>
<p>Мы получили запрос на сброс вашего пароля. Нажмите на кнопку ниже, чтобы установить новый пароль:</p>
<p style="text-align: center;">
<a href="{reset_url}" class="button" style="background-color: #dc3545;">Сбросить пароль</a>
</p>
<p>Или скопируйте и вставьте эту ссылку в браузер:</p>
<p><a href="{reset_url}">{reset_url}</a></p>
<p>Ссылка действительна в течение {expire_hours} часов.</p>
<p class="warning" style="color: #dc3545; font-weight: bold;">Если вы не запрашивали сброс пароля, проигнорируйте это письмо или свяжитесь с поддержкой.</p>
""",
'en': f"""
<h2>{greeting.get('en')}</h2>
<p>We received a request to reset your password. Click the button below to set a new password:</p>
<p style="text-align: center;">
<a href="{reset_url}" class="button" style="background-color: #dc3545;">Reset Password</a>
</p>
<p>Or copy and paste this link in your browser:</p>
<p><a href="{reset_url}">{reset_url}</a></p>
<p>This link will expire in {expire_hours} hour(s).</p>
<p class="warning" style="color: #dc3545; font-weight: bold;">If you didn't request a password reset, please ignore this email or contact support.</p>
""",
'zh': f"""
<h2>{greeting.get('zh')}</h2>
<p>我们收到了重置您密码的请求。点击下方按钮设置新密码:</p>
<p style="text-align: center;">
<a href="{reset_url}" class="button" style="background-color: #dc3545;">重置密码</a>
</p>
<p>或将此链接复制并粘贴到浏览器中:</p>
<p><a href="{reset_url}">{reset_url}</a></p>
<p>此链接将在 {expire_hours} 小时后过期。</p>
<p class="warning" style="color: #dc3545; font-weight: bold;">如果您没有请求重置密码,请忽略此邮件或联系客服。</p>
""",
'ua': f"""
<h2>{greeting.get('ua')}</h2>
<p>Ми отримали запит на скидання вашого пароля. Натисніть на кнопку нижче, щоб встановити новий пароль:</p>
<p style="text-align: center;">
<a href="{reset_url}" class="button" style="background-color: #dc3545;">Скинути пароль</a>
</p>
<p>Або скопіюйте та вставте це посилання в браузер:</p>
<p><a href="{reset_url}">{reset_url}</a></p>
<p>Посилання дійсне протягом {expire_hours} годин.</p>
<p class="warning" style="color: #dc3545; font-weight: bold;">Якщо ви не запитували скидання пароля, проігноруйте цей лист або зв'яжіться з підтримкою.</p>
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
# ============================================================================
# Guest Purchase Templates
# ============================================================================
def _guest_subscription_delivered_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for guest subscription delivered notification."""
tariff_name = html.escape(context.get('tariff_name', ''))
period_days = context.get('period_days', 0)
cabinet_url = html.escape(context.get('cabinet_url', ''))
cabinet_email = html.escape(context.get('cabinet_email', ''))
cabinet_password = context.get('cabinet_password', '')
subjects = {
'ru': 'Ваша VPN подписка готова',
'en': 'Your VPN subscription is ready',
'zh': '您的VPN订阅已准备就绪',
'ua': 'Ваша VPN підписка готова',
'fa': 'اشتراک VPN شما آماده است',
}
creds_block_ru = (
f"""
<div class="highlight">
<p><strong>Данные для входа в личный кабинет:</strong></p>
<p><strong>Email:</strong> <code>{cabinet_email}</code></p>
<p><strong>Пароль:</strong> <code>{cabinet_password}</code></p>
</div>
"""
if cabinet_password
else ''
)
creds_block_en = (
f"""
<div class="highlight">
<p><strong>Your cabinet login credentials:</strong></p>
<p><strong>Email:</strong> <code>{cabinet_email}</code></p>
<p><strong>Password:</strong> <code>{cabinet_password}</code></p>
</div>
"""
if cabinet_password
else ''
)
creds_block_zh = (
f"""
<div class="highlight">
<p><strong>个人中心登录信息:</strong></p>
<p><strong>Email:</strong> <code>{cabinet_email}</code></p>
<p><strong>密码:</strong> <code>{cabinet_password}</code></p>
</div>
"""
if cabinet_password
else ''
)
creds_block_ua = (
f"""
<div class="highlight">
<p><strong>Дані для входу в особистий кабінет:</strong></p>
<p><strong>Email:</strong> <code>{cabinet_email}</code></p>
<p><strong>Пароль:</strong> <code>{cabinet_password}</code></p>
</div>
"""
if cabinet_password
else ''
)
creds_block_fa = (
f"""
<div class="highlight">
<p><strong>اطلاعات ورود به پنل کاربری:</strong></p>
<p><strong>Email:</strong> <code>{cabinet_email}</code></p>
<p><strong>رمز عبور:</strong> <code>{cabinet_password}</code></p>
</div>
"""
if cabinet_password
else ''
)
bodies = {
'ru': f"""
<h2>Ваша VPN подписка готова!</h2>
<div class="highlight success">
<p>Тариф: <strong>{tariff_name}</strong></p>
<p>Период: <strong>{period_days} дней</strong></p>
</div>
{creds_block_ru}
<p>Подписка активирована в вашем личном кабинете.</p>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">Перейти в личный кабинет</a></p>
""",
'en': f"""
<h2>Your VPN subscription is ready!</h2>
<div class="highlight success">
<p>Plan: <strong>{tariff_name}</strong></p>
<p>Period: <strong>{period_days} days</strong></p>
</div>
{creds_block_en}
<p>Your subscription has been activated in your cabinet.</p>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">Go to Cabinet</a></p>
""",
'zh': f"""
<h2>您的VPN订阅已准备就绪!</h2>
<div class="highlight success">
<p>套餐: <strong>{tariff_name}</strong></p>
<p>期限: <strong>{period_days} 天</strong></p>
</div>
{creds_block_zh}
<p>订阅已在您的个人中心激活。</p>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">前往个人中心</a></p>
""",
'ua': f"""
<h2>Ваша VPN підписка готова!</h2>
<div class="highlight success">
<p>Тариф: <strong>{tariff_name}</strong></p>
<p>Період: <strong>{period_days} днів</strong></p>
</div>
{creds_block_ua}
<p>Підписка активована у вашому особистому кабінеті.</p>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">Перейти до кабінету</a></p>
""",
'fa': f"""
<h2>اشتراک VPN شما آماده است!</h2>
<div class="highlight success">
<p>طرح: <strong>{tariff_name}</strong></p>
<p>مدت: <strong>{period_days} روز</strong></p>
</div>
{creds_block_fa}
<p>اشتراک شما در پنل کاربری فعال شده است.</p>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">رفتن به پنل کاربری</a></p>
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
def _guest_activation_required_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for guest purchase pending activation (user already has a subscription)."""
tariff_name = html.escape(context.get('tariff_name', ''))
period_days = context.get('period_days', 0)
success_page_url = html.escape(context.get('success_page_url', ''))
gift_message = context.get('gift_message')
is_gift = context.get('is_gift', False)
gift_block_ru = ''
gift_block_en = ''
gift_block_zh = ''
gift_block_ua = ''
gift_block_fa = ''
if is_gift and gift_message:
escaped_msg = html.escape(gift_message)
gift_block_ru = f'<div class="highlight"><p><em>Сообщение: {escaped_msg}</em></p></div>'
gift_block_en = f'<div class="highlight"><p><em>Message: {escaped_msg}</em></p></div>'
gift_block_zh = f'<div class="highlight"><p><em>留言: {escaped_msg}</em></p></div>'
gift_block_ua = f'<div class="highlight"><p><em>Повідомлення: {escaped_msg}</em></p></div>'
gift_block_fa = f'<div class="highlight"><p><em>پیام: {escaped_msg}</em></p></div>'
subjects = {
'ru': 'Требуется активация подписки',
'en': 'Subscription activation required',
'zh': '需要激活订阅',
'ua': 'Потрібна активація підписки',
'fa': 'فعال‌سازی اشتراک لازم است',
}
bodies = {
'ru': f"""
<h2>Требуется активация подписки</h2>
{gift_block_ru}
<div class="highlight">
<p>Тариф: <strong>{tariff_name}</strong></p>
<p>Период: <strong>{period_days} дней</strong></p>
</div>
<p class="warning">У вас уже есть активная подписка. Активация новой заменит текущую.</p>
<p style="text-align: center;"><a href="{success_page_url}" class="button">Активировать подписку</a></p>
""",
'en': f"""
<h2>Subscription activation required</h2>
{gift_block_en}
<div class="highlight">
<p>Plan: <strong>{tariff_name}</strong></p>
<p>Period: <strong>{period_days} days</strong></p>
</div>
<p class="warning">You already have an active subscription. Activating will replace your current one.</p>
<p style="text-align: center;"><a href="{success_page_url}" class="button">Activate subscription</a></p>
""",
'zh': f"""
<h2>需要激活订阅</h2>
{gift_block_zh}
<div class="highlight">
<p>套餐: <strong>{tariff_name}</strong></p>
<p>期限: <strong>{period_days} 天</strong></p>
</div>
<p class="warning">您已有活跃订阅。激活新订阅将替换当前订阅。</p>
<p style="text-align: center;"><a href="{success_page_url}" class="button">激活订阅</a></p>
""",
'ua': f"""
<h2>Потрібна активація підписки</h2>
{gift_block_ua}
<div class="highlight">
<p>Тариф: <strong>{tariff_name}</strong></p>
<p>Період: <strong>{period_days} днів</strong></p>
</div>
<p class="warning">У вас вже є активна підписка. Активація нової замінить поточну.</p>
<p style="text-align: center;"><a href="{success_page_url}" class="button">Активувати підписку</a></p>
""",
'fa': f"""
<h2>فعال‌سازی اشتراک لازم است</h2>
{gift_block_fa}
<div class="highlight">
<p>طرح: <strong>{tariff_name}</strong></p>
<p>مدت: <strong>{period_days} روز</strong></p>
</div>
<p class="warning">شما از قبل اشتراک فعالی دارید. فعال‌سازی اشتراک جدید جایگزین فعلی خواهد شد.</p>
<p style="text-align: center;"><a href="{success_page_url}" class="button">فعال‌سازی اشتراک</a></p>
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
def _guest_gift_received_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for gift subscription received notification."""
tariff_name = html.escape(context.get('tariff_name', ''))
period_days = context.get('period_days', 0)
gift_message = context.get('gift_message')
cabinet_password = context.get('cabinet_password')
cabinet_email = html.escape(context.get('cabinet_email', ''))
cabinet_url = html.escape(context.get('cabinet_url', ''))
# Credentials block for gift recipients who got a new cabinet account
cred_block = {'ru': '', 'en': '', 'zh': '', 'ua': '', 'fa': ''}
if cabinet_password and cabinet_email:
escaped_pw = html.escape(cabinet_password)
cred_block = {
'ru': f"""
<div class="highlight">
<p><strong>Данные для входа в личный кабинет:</strong></p>
<p>Email: <code>{cabinet_email}</code></p>
<p>Пароль: <code>{escaped_pw}</code></p>
</div>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">Перейти в личный кабинет</a></p>
""",
'en': f"""
<div class="highlight">
<p><strong>Your cabinet login credentials:</strong></p>
<p>Email: <code>{cabinet_email}</code></p>
<p>Password: <code>{escaped_pw}</code></p>
</div>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">Go to Cabinet</a></p>
""",
'zh': f"""
<div class="highlight">
<p><strong>个人中心登录信息:</strong></p>
<p>邮箱: <code>{cabinet_email}</code></p>
<p>密码: <code>{escaped_pw}</code></p>
</div>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">前往个人中心</a></p>
""",
'ua': f"""
<div class="highlight">
<p><strong>Дані для входу в особистий кабінет:</strong></p>
<p>Email: <code>{cabinet_email}</code></p>
<p>Пароль: <code>{escaped_pw}</code></p>
</div>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">Перейти до кабінету</a></p>
""",
'fa': f"""
<div class="highlight">
<p><strong>اطلاعات ورود به پنل کاربری:</strong></p>
<p>ایمیل: <code dir="ltr">{cabinet_email}</code></p>
<p>رمز عبور: <code dir="ltr">{escaped_pw}</code></p>
</div>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">رفتن به پنل کاربری</a></p>
""",
}
gift_block_ru = ''
gift_block_en = ''
gift_block_zh = ''
gift_block_ua = ''
gift_block_fa = ''
if gift_message:
escaped_msg = html.escape(gift_message)
gift_block_ru = f'<div class="highlight"><p><em>Сообщение: {escaped_msg}</em></p></div>'
gift_block_en = f'<div class="highlight"><p><em>Message: {escaped_msg}</em></p></div>'
gift_block_zh = f'<div class="highlight"><p><em>留言: {escaped_msg}</em></p></div>'
gift_block_ua = f'<div class="highlight"><p><em>Повідомлення: {escaped_msg}</em></p></div>'
gift_block_fa = f'<div class="highlight"><p><em>پیام: {escaped_msg}</em></p></div>'
subjects = {
'ru': 'Вам подарили VPN подписку!',
'en': "You've been gifted a VPN subscription!",
'zh': '您收到了VPN订阅礼物!',
'ua': 'Вам подарували VPN підписку!',
'fa': 'یک اشتراک VPN به شما هدیه داده شده است!',
}
bodies = {
'ru': f"""
<h2>Вам подарили VPN подписку!</h2>
{gift_block_ru}
<div class="highlight success">
<p>Тариф: <strong>{tariff_name}</strong></p>
<p>Период: <strong>{period_days} дней</strong></p>
</div>
<p>Подписка активирована в личном кабинете.</p>
{cred_block['ru']}
<p style="text-align: center;"><a href="{cabinet_url}" class="button">Перейти в личный кабинет</a></p>
""",
'en': f"""
<h2>You've been gifted a VPN subscription!</h2>
{gift_block_en}
<div class="highlight success">
<p>Plan: <strong>{tariff_name}</strong></p>
<p>Period: <strong>{period_days} days</strong></p>
</div>
<p>Your subscription has been activated in the cabinet.</p>
{cred_block['en']}
<p style="text-align: center;"><a href="{cabinet_url}" class="button">Go to Cabinet</a></p>
""",
'zh': f"""
<h2>您收到了VPN订阅礼物!</h2>
{gift_block_zh}
<div class="highlight success">
<p>套餐: <strong>{tariff_name}</strong></p>
<p>期限: <strong>{period_days} 天</strong></p>
</div>
<p>订阅已在个人中心激活。</p>
{cred_block['zh']}
<p style="text-align: center;"><a href="{cabinet_url}" class="button">前往个人中心</a></p>
""",
'ua': f"""
<h2>Вам подарували VPN підписку!</h2>
{gift_block_ua}
<div class="highlight success">
<p>Тариф: <strong>{tariff_name}</strong></p>
<p>Період: <strong>{period_days} днів</strong></p>
</div>
<p>Підписка активована в особистому кабінеті.</p>
{cred_block['ua']}
<p style="text-align: center;"><a href="{cabinet_url}" class="button">Перейти до кабінету</a></p>
""",
'fa': f"""
<h2>یک اشتراک VPN به شما هدیه داده شده است!</h2>
{gift_block_fa}
<div class="highlight success">
<p>طرح: <strong>{tariff_name}</strong></p>
<p>مدت: <strong>{period_days} روز</strong></p>
</div>
<p>اشتراک در پنل کاربری فعال شده است.</p>
{cred_block['fa']}
<p style="text-align: center;"><a href="{cabinet_url}" class="button">رفتن به پنل کاربری</a></p>
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
def _guest_cabinet_credentials_template(self, language: str, context: dict[str, Any]) -> dict[str, str]:
"""Template for cabinet login credentials email (sent separately from subscription)."""
cabinet_email = html.escape(context.get('cabinet_email', ''))
cabinet_password = html.escape(context.get('cabinet_password', ''))
cabinet_url = html.escape(context.get('cabinet_url', ''))
tariff_name = html.escape(context.get('tariff_name', ''))
period_days = context.get('period_days', 0)
subjects = {
'ru': 'Данные для входа в личный кабинет',
'en': 'Your cabinet login credentials',
'zh': '您的个人中心登录信息',
'ua': 'Дані для входу в особистий кабінет',
'fa': 'اطلاعات ورود به پنل کاربری',
}
bodies = {
'ru': f"""
<h2>Данные для входа в личный кабинет</h2>
<div class="highlight success">
<p>Тариф: <strong>{tariff_name}</strong></p>
<p>Период: <strong>{period_days} дней</strong></p>
</div>
<div class="highlight">
<p><strong>Email:</strong> <code>{cabinet_email}</code></p>
<p><strong>Пароль:</strong> <code>{cabinet_password}</code></p>
</div>
<p>Сохраните эти данные для входа. Вы можете изменить пароль в настройках кабинета.</p>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">Перейти в личный кабинет</a></p>
""",
'en': f"""
<h2>Your cabinet login credentials</h2>
<div class="highlight success">
<p>Plan: <strong>{tariff_name}</strong></p>
<p>Period: <strong>{period_days} days</strong></p>
</div>
<div class="highlight">
<p><strong>Email:</strong> <code>{cabinet_email}</code></p>
<p><strong>Password:</strong> <code>{cabinet_password}</code></p>
</div>
<p>Save these credentials. You can change your password in cabinet settings.</p>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">Go to Cabinet</a></p>
""",
'zh': f"""
<h2>您的个人中心登录信息</h2>
<div class="highlight success">
<p>套餐: <strong>{tariff_name}</strong></p>
<p>期限: <strong>{period_days} 天</strong></p>
</div>
<div class="highlight">
<p><strong>邮箱:</strong> <code>{cabinet_email}</code></p>
<p><strong>密码:</strong> <code>{cabinet_password}</code></p>
</div>
<p>请保存这些登录信息。您可以在个人中心设置中更改密码。</p>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">前往个人中心</a></p>
""",
'ua': f"""
<h2>Дані для входу в особистий кабінет</h2>
<div class="highlight success">
<p>Тариф: <strong>{tariff_name}</strong></p>
<p>Період: <strong>{period_days} днів</strong></p>
</div>
<div class="highlight">
<p><strong>Email:</strong> <code>{cabinet_email}</code></p>
<p><strong>Пароль:</strong> <code>{cabinet_password}</code></p>
</div>
<p>Збережіть ці дані. Ви можете змінити пароль у налаштуваннях кабінету.</p>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">Перейти до кабінету</a></p>
""",
'fa': f"""
<h2>اطلاعات ورود به پنل کاربری</h2>
<div class="highlight success">
<p>طرح: <strong>{tariff_name}</strong></p>
<p>مدت: <strong>{period_days} روز</strong></p>
</div>
<div class="highlight">
<p><strong>ایمیل:</strong> <code dir="ltr">{cabinet_email}</code></p>
<p><strong>رمز عبور:</strong> <code dir="ltr">{cabinet_password}</code></p>
</div>
<p>این اطلاعات را ذخیره کنید. می‌توانید رمز عبور خود را در تنظیمات پنل تغییر دهید.</p>
<p style="text-align: center;"><a href="{cabinet_url}" class="button">رفتن به پنل کاربری</a></p>
""",
}
return {
'subject': subjects.get(language, subjects['ru']),
'body_html': self._get_base_template(bodies.get(language, bodies['ru']), language),
}
# Singleton instance
email_notification_templates = EmailNotificationTemplates()