feat: add tariff identification to all notifications for multi-tariff mode
When MULTI_TARIFF_ENABLED=true users can have multiple subscriptions,
so notifications must identify which tariff they relate to.
- Webhook notifications: _notify_user auto-injects tariff_label from
subscription.tariff.name into all 16 webhook notification strings
- Monitoring service: tariff labels in expired, expiring, trial ending,
follow-up waves, autopay success/failed notifications
- Daily subscription: tariff in insufficient balance notification
- Recurrent payments: tariff in card autopay success/failed
- Auto-purchase: tariff in all 4 auto-purchase notification paths,
with pre-captured names to avoid MissingGreenlet after db.commit()
- Channel checker: plural form for multi-subscription deactivation
- Payment providers: tariff in YooKassa and Stars activation messages
- Admin: tariff in bulk expiry reminder
- Localization: {tariff_label} in 20 notification keys across all 5
locales (ru, en, ua, zh, fa) + _MULTI channel keys
- Fix: selectinload(Subscription.tariff) in trial expiring query
- Fix: capture tariff_name before expire_subscription to prevent
MissingGreenlet from db.refresh() expiring ORM relationships
This commit is contained in:
@@ -333,8 +333,11 @@ class DailySubscriptionService:
|
||||
required_rubles = required_amount / 100
|
||||
balance_rubles = user.balance_kopeks / 100
|
||||
|
||||
tariff_label = ''
|
||||
if settings.is_multi_tariff_enabled() and hasattr(subscription, 'tariff') and subscription.tariff:
|
||||
tariff_label = f' «{subscription.tariff.name}»'
|
||||
message = (
|
||||
f'⚠️ <b>Подписка приостановлена</b>\n\n'
|
||||
f'⚠️ <b>Подписка{tariff_label} приостановлена</b>\n\n'
|
||||
f'Недостаточно средств для суточной оплаты.\n\n'
|
||||
f'Требуется: {required_rubles:.2f} ₽\n'
|
||||
f'Баланс: {balance_rubles:.2f} ₽\n\n'
|
||||
|
||||
@@ -294,11 +294,14 @@ class MonitoringService:
|
||||
|
||||
from app.database.crud.subscription import expire_subscription
|
||||
|
||||
# Capture tariff name before expire_subscription's db.refresh() expires the relationship
|
||||
_tariff_name = subscription.tariff.name if getattr(subscription, 'tariff', None) else None
|
||||
|
||||
await expire_subscription(db, subscription)
|
||||
|
||||
user = await get_user_by_id(db, subscription.user_id)
|
||||
if user and self.bot:
|
||||
await self._send_subscription_expired_notification(user, subscription)
|
||||
await self._send_subscription_expired_notification(user, subscription, tariff_name=_tariff_name)
|
||||
|
||||
logger.info(
|
||||
"🔴 Подписка пользователя истекла и статус изменен на 'expired'", user_id=subscription.user_id
|
||||
@@ -539,6 +542,7 @@ class MonitoringService:
|
||||
select(Subscription)
|
||||
.join(Subscription.user)
|
||||
.options(
|
||||
selectinload(Subscription.tariff),
|
||||
selectinload(Subscription.user).selectinload(User.promo_group),
|
||||
selectinload(Subscription.user)
|
||||
.selectinload(User.user_promo_groups)
|
||||
@@ -1233,7 +1237,9 @@ class MonitoringService:
|
||||
else:
|
||||
failed_count += 1
|
||||
if user.telegram_id and self.bot:
|
||||
await self._send_autopay_failed_notification(user, user.balance_kopeks, charge_amount)
|
||||
await self._send_autopay_failed_notification(
|
||||
user, user.balance_kopeks, charge_amount, subscription=subscription
|
||||
)
|
||||
elif not user.telegram_id:
|
||||
await notification_delivery_service.notify_autopay_failed(
|
||||
user=user,
|
||||
@@ -1267,7 +1273,9 @@ class MonitoringService:
|
||||
|
||||
if should_notify:
|
||||
if user.telegram_id and self.bot:
|
||||
await self._send_autopay_failed_notification(user, user.balance_kopeks, charge_amount)
|
||||
await self._send_autopay_failed_notification(
|
||||
user, user.balance_kopeks, charge_amount, subscription=subscription
|
||||
)
|
||||
elif not user.telegram_id:
|
||||
await notification_delivery_service.notify_autopay_failed(
|
||||
user=user,
|
||||
@@ -1303,10 +1311,18 @@ class MonitoringService:
|
||||
except Exception as e:
|
||||
logger.error('Ошибка обработки автоплатежей', error=e)
|
||||
|
||||
async def _send_subscription_expired_notification(self, user: User, subscription: Subscription) -> bool:
|
||||
async def _send_subscription_expired_notification(
|
||||
self, user: User, subscription: Subscription, *, tariff_name: str | None = None
|
||||
) -> bool:
|
||||
try:
|
||||
message = """
|
||||
⛔ <b>Подписка истекла</b>
|
||||
tariff_label = ''
|
||||
if settings.is_multi_tariff_enabled():
|
||||
if tariff_name:
|
||||
tariff_label = f' «{tariff_name}»'
|
||||
elif hasattr(subscription, 'tariff') and subscription.tariff:
|
||||
tariff_label = f' «{subscription.tariff.name}»'
|
||||
message = f"""
|
||||
⛔ <b>Подписка{tariff_label} истекла</b>
|
||||
|
||||
Ваша подписка истекла. Для восстановления доступа продлите подписку.
|
||||
|
||||
@@ -1466,8 +1482,11 @@ class MonitoringService:
|
||||
try:
|
||||
get_texts(user.language)
|
||||
|
||||
message = """
|
||||
🎁 <b>Тестовая подписка скоро закончится!</b>
|
||||
tariff_label = ''
|
||||
if settings.is_multi_tariff_enabled() and hasattr(subscription, 'tariff') and subscription.tariff:
|
||||
tariff_label = f' «{subscription.tariff.name}»'
|
||||
message = f"""
|
||||
🎁 <b>Тестовая подписка{tariff_label} скоро закончится!</b>
|
||||
|
||||
Ваша тестовая подписка истекает через 2 часа.
|
||||
|
||||
@@ -1593,16 +1612,20 @@ class MonitoringService:
|
||||
async def _send_expired_day1_notification(self, user: User, subscription: Subscription) -> bool:
|
||||
try:
|
||||
texts = get_texts(user.language)
|
||||
tariff_label = ''
|
||||
if settings.is_multi_tariff_enabled() and hasattr(subscription, 'tariff') and subscription.tariff:
|
||||
tariff_label = f' «{subscription.tariff.name}»'
|
||||
template = texts.get(
|
||||
'SUBSCRIPTION_EXPIRED_1D',
|
||||
(
|
||||
'⛔ <b>Подписка закончилась</b>\n\n'
|
||||
'⛔ <b>Подписка{tariff_label} закончилась</b>\n\n'
|
||||
'Доступ был отключён {end_date}. Продлите подписку, чтобы вернуться в сервис.'
|
||||
),
|
||||
)
|
||||
message = template.format(
|
||||
end_date=format_local_datetime(subscription.end_date, '%d.%m.%Y %H:%M'),
|
||||
price=settings.format_price(settings.PRICE_30_DAYS),
|
||||
tariff_label=tariff_label,
|
||||
)
|
||||
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
@@ -1672,11 +1695,15 @@ class MonitoringService:
|
||||
try:
|
||||
texts = get_texts(user.language)
|
||||
|
||||
tariff_label = ''
|
||||
if settings.is_multi_tariff_enabled() and hasattr(subscription, 'tariff') and subscription.tariff:
|
||||
tariff_label = f' «{subscription.tariff.name}»'
|
||||
|
||||
if wave == 'second':
|
||||
template = texts.get(
|
||||
'SUBSCRIPTION_EXPIRED_SECOND_WAVE',
|
||||
(
|
||||
'🔥 <b>Скидка {percent}% на продление</b>\n\n'
|
||||
'🔥 <b>Скидка {percent}% на продление{tariff_label}</b>\n\n'
|
||||
'Активируйте предложение, чтобы получить дополнительную скидку. '
|
||||
'Она суммируется с вашей промогруппой и действует до {expires_at}.'
|
||||
),
|
||||
@@ -1685,7 +1712,7 @@ class MonitoringService:
|
||||
template = texts.get(
|
||||
'SUBSCRIPTION_EXPIRED_THIRD_WAVE',
|
||||
(
|
||||
'🎁 <b>Индивидуальная скидка {percent}%</b>\n\n'
|
||||
'🎁 <b>Индивидуальная скидка {percent}%{tariff_label}</b>\n\n'
|
||||
'Прошло {trigger_days} дней без подписки — возвращайтесь и активируйте дополнительную скидку. '
|
||||
'Она суммируется с промогруппой и действует до {expires_at}.'
|
||||
),
|
||||
@@ -1695,6 +1722,7 @@ class MonitoringService:
|
||||
percent=percent,
|
||||
expires_at=format_local_datetime(expires_at, '%d.%m.%Y %H:%M'),
|
||||
trigger_days=trigger_days or '',
|
||||
tariff_label=tariff_label,
|
||||
)
|
||||
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
@@ -1767,7 +1795,7 @@ class MonitoringService:
|
||||
tariff_label = f' «{subscription.tariff.name}»'
|
||||
message = texts.AUTOPAY_SUCCESS.format(days=days, amount=settings.format_price(amount))
|
||||
if tariff_label:
|
||||
message += f'\n📦 Тариф:{tariff_label}'
|
||||
message += f'\n📦 Тариф: {tariff_label}'
|
||||
await self._send_message_with_logo(
|
||||
chat_id=user.telegram_id,
|
||||
text=message,
|
||||
@@ -1787,12 +1815,21 @@ class MonitoringService:
|
||||
except Exception as e:
|
||||
logger.error('Ошибка отправки уведомления об автоплатеже пользователю', telegram_id=user.telegram_id, e=e)
|
||||
|
||||
async def _send_autopay_failed_notification(self, user: User, balance: int, required: int):
|
||||
async def _send_autopay_failed_notification(
|
||||
self, user: User, balance: int, required: int, *, subscription: Subscription | None = None
|
||||
):
|
||||
try:
|
||||
texts = get_texts(user.language)
|
||||
message = texts.AUTOPAY_FAILED.format(
|
||||
balance=settings.format_price(balance), required=settings.format_price(required)
|
||||
)
|
||||
if (
|
||||
settings.is_multi_tariff_enabled()
|
||||
and subscription
|
||||
and hasattr(subscription, 'tariff')
|
||||
and subscription.tariff
|
||||
):
|
||||
message += f'\n📦 Тариф: «{subscription.tariff.name}»'
|
||||
|
||||
from aiogram.types import InlineKeyboardMarkup
|
||||
|
||||
|
||||
@@ -304,12 +304,23 @@ class TelegramStarsMixin:
|
||||
traffic_limit = getattr(subscription, 'traffic_limit_gb', 0) or 0
|
||||
traffic_label = 'Безлимит' if traffic_limit == 0 else f'{int(traffic_limit)} ГБ'
|
||||
|
||||
tariff_line = ''
|
||||
if settings.is_multi_tariff_enabled() and getattr(subscription, 'tariff_id', None):
|
||||
try:
|
||||
from app.database.crud.tariff import get_tariff_by_id
|
||||
|
||||
_t = await get_tariff_by_id(db, subscription.tariff_id)
|
||||
if _t:
|
||||
tariff_line = f'\n📦 Тариф: «{_t.name}»'
|
||||
except Exception:
|
||||
pass
|
||||
success_message = (
|
||||
'✅ <b>Подписка успешно активирована!</b>\n\n'
|
||||
f'📅 Период: {period_display} дней\n'
|
||||
f'📱 Устройства: {getattr(subscription, "device_limit", 1)}\n'
|
||||
f'📊 Трафик: {traffic_label}\n'
|
||||
f'⭐ Оплата: {stars_amount} ⭐ ({settings.format_price(amount_kopeks)})\n\n'
|
||||
f'⭐ Оплата: {stars_amount} ⭐ ({settings.format_price(amount_kopeks)})'
|
||||
f'{tariff_line}\n\n'
|
||||
"🔗 Для подключения перейдите в раздел 'Моя подписка'"
|
||||
)
|
||||
|
||||
|
||||
@@ -996,12 +996,23 @@ class YooKassaPaymentMixin:
|
||||
if getattr(self, 'bot', None) and user.telegram_id:
|
||||
from aiogram import types
|
||||
|
||||
tariff_line = ''
|
||||
if settings.is_multi_tariff_enabled() and getattr(subscription, 'tariff_id', None):
|
||||
try:
|
||||
from app.database.crud.tariff import get_tariff_by_id
|
||||
|
||||
_t = await get_tariff_by_id(db, subscription.tariff_id)
|
||||
if _t:
|
||||
tariff_line = f'\n📦 Тариф: «{_t.name}»'
|
||||
except Exception:
|
||||
pass
|
||||
success_message = (
|
||||
f'✅ <b>Подписка успешно активирована!</b>\n\n'
|
||||
f'📅 Период: {subscription_period} дней\n'
|
||||
f'📱 Устройства: 1\n'
|
||||
f'📊 Трафик: Безлимит\n'
|
||||
f'💳 Оплата: {settings.format_price(payment.amount_kopeks)} (YooKassa)\n\n'
|
||||
f'💳 Оплата: {settings.format_price(payment.amount_kopeks)} (YooKassa)'
|
||||
f'{tariff_line}\n\n'
|
||||
f"🔗 Для подключения перейдите в раздел 'Моя подписка'"
|
||||
)
|
||||
|
||||
|
||||
@@ -363,6 +363,8 @@ async def _process_single_subscription(
|
||||
'RECURRENT_TOPUP_SUCCESS',
|
||||
'✅ <b>Автоплатёж выполнен</b>\n\nБаланс пополнен на {amount} для продления подписки.',
|
||||
).format(amount=settings.format_price(topup_amount_kopeks))
|
||||
if settings.is_multi_tariff_enabled() and hasattr(subscription, 'tariff') and subscription.tariff:
|
||||
msg += f'\n📦 Тариф: «{subscription.tariff.name}»'
|
||||
await bot.send_message(
|
||||
chat_id=user.telegram_id,
|
||||
text=msg,
|
||||
@@ -391,6 +393,8 @@ async def _process_single_subscription(
|
||||
'RECURRENT_TOPUP_FAILED',
|
||||
'❌ <b>Автоплатёж не удался</b>\n\nНе удалось списать {amount} ни с одной сохранённой карты для продления подписки.\n\nПополните баланс вручную, чтобы подписка не прервалась.',
|
||||
).format(amount=settings.format_price(topup_amount_kopeks))
|
||||
if settings.is_multi_tariff_enabled() and hasattr(subscription, 'tariff') and subscription.tariff:
|
||||
msg += f'\n📦 Тариф: «{subscription.tariff.name}»'
|
||||
await bot.send_message(
|
||||
chat_id=user.telegram_id,
|
||||
text=msg,
|
||||
|
||||
@@ -552,6 +552,7 @@ class RemnaWaveWebhookService:
|
||||
*,
|
||||
reply_markup: InlineKeyboardMarkup | None = None,
|
||||
format_kwargs: dict[str, Any] | None = None,
|
||||
subscription: Subscription | None = None,
|
||||
) -> None:
|
||||
"""Send a notification to user via appropriate channel.
|
||||
|
||||
@@ -577,6 +578,15 @@ class RemnaWaveWebhookService:
|
||||
logger.warning('Missing locale key for language', text_key=text_key, language=user.language)
|
||||
return
|
||||
|
||||
# Inject tariff_label for multi-tariff subscription identification
|
||||
if format_kwargs is None:
|
||||
format_kwargs = {}
|
||||
if 'tariff_label' not in format_kwargs:
|
||||
tariff_label = ''
|
||||
if settings.is_multi_tariff_enabled() and subscription and getattr(subscription, 'tariff', None):
|
||||
tariff_label = f' «{subscription.tariff.name}»'
|
||||
format_kwargs['tariff_label'] = tariff_label
|
||||
|
||||
if format_kwargs:
|
||||
try:
|
||||
message = message.format(**format_kwargs)
|
||||
@@ -664,6 +674,7 @@ class RemnaWaveWebhookService:
|
||||
user,
|
||||
'WEBHOOK_SUB_EXPIRED',
|
||||
reply_markup=self._get_renew_keyboard(user, subscription.id),
|
||||
subscription=subscription,
|
||||
)
|
||||
|
||||
async def _handle_user_disabled(
|
||||
@@ -698,7 +709,9 @@ class RemnaWaveWebhookService:
|
||||
else:
|
||||
await db.commit()
|
||||
|
||||
await self._notify_user(user, 'WEBHOOK_SUB_DISABLED', reply_markup=self._get_subscription_keyboard(user))
|
||||
await self._notify_user(
|
||||
user, 'WEBHOOK_SUB_DISABLED', reply_markup=self._get_subscription_keyboard(user), subscription=subscription
|
||||
)
|
||||
|
||||
async def _handle_user_enabled(
|
||||
self, db: AsyncSession, user: User, subscription: Subscription | None, data: dict
|
||||
@@ -714,7 +727,9 @@ class RemnaWaveWebhookService:
|
||||
else:
|
||||
await db.commit()
|
||||
|
||||
await self._notify_user(user, 'WEBHOOK_SUB_ENABLED', reply_markup=self._get_connect_keyboard(user))
|
||||
await self._notify_user(
|
||||
user, 'WEBHOOK_SUB_ENABLED', reply_markup=self._get_connect_keyboard(user), subscription=subscription
|
||||
)
|
||||
|
||||
async def _handle_user_limited(
|
||||
self, db: AsyncSession, user: User, subscription: Subscription | None, data: dict
|
||||
@@ -735,7 +750,9 @@ class RemnaWaveWebhookService:
|
||||
else:
|
||||
await db.commit()
|
||||
|
||||
await self._notify_user(user, 'WEBHOOK_SUB_LIMITED', reply_markup=self._get_traffic_keyboard(user))
|
||||
await self._notify_user(
|
||||
user, 'WEBHOOK_SUB_LIMITED', reply_markup=self._get_traffic_keyboard(user), subscription=subscription
|
||||
)
|
||||
|
||||
async def _handle_user_traffic_reset(
|
||||
self, db: AsyncSession, user: User, subscription: Subscription | None, data: dict
|
||||
@@ -751,7 +768,12 @@ class RemnaWaveWebhookService:
|
||||
await reactivate_subscription(db, subscription)
|
||||
logger.info('Webhook: traffic reset for subscription , user', subscription_id=subscription.id, user_id=user.id)
|
||||
|
||||
await self._notify_user(user, 'WEBHOOK_SUB_TRAFFIC_RESET', reply_markup=self._get_subscription_keyboard(user))
|
||||
await self._notify_user(
|
||||
user,
|
||||
'WEBHOOK_SUB_TRAFFIC_RESET',
|
||||
reply_markup=self._get_subscription_keyboard(user),
|
||||
subscription=subscription,
|
||||
)
|
||||
|
||||
async def _handle_user_modified(
|
||||
self, db: AsyncSession, user: User, subscription: Subscription | None, data: dict
|
||||
@@ -1003,6 +1025,7 @@ class RemnaWaveWebhookService:
|
||||
reply_markup=self._get_renew_keyboard(
|
||||
user, getattr(subscription, 'id', None) if subscription else None
|
||||
),
|
||||
subscription=subscription,
|
||||
)
|
||||
else:
|
||||
await self._notify_user(
|
||||
@@ -1011,6 +1034,7 @@ class RemnaWaveWebhookService:
|
||||
reply_markup=self._get_renew_keyboard(
|
||||
user, getattr(subscription, 'id', None) if subscription else None
|
||||
),
|
||||
subscription=subscription,
|
||||
)
|
||||
|
||||
async def _attempt_panel_recreation(self, db: AsyncSession, user: User, subscription: Subscription) -> bool:
|
||||
@@ -1096,7 +1120,9 @@ class RemnaWaveWebhookService:
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
await self._notify_user(user, 'WEBHOOK_SUB_REVOKED', reply_markup=self._get_connect_keyboard(user))
|
||||
await self._notify_user(
|
||||
user, 'WEBHOOK_SUB_REVOKED', reply_markup=self._get_connect_keyboard(user), subscription=subscription
|
||||
)
|
||||
|
||||
async def _handle_user_created(
|
||||
self, db: AsyncSession, user: User, subscription: Subscription | None, data: dict
|
||||
@@ -1110,7 +1136,10 @@ class RemnaWaveWebhookService:
|
||||
logger.info('Webhook expires_72h: подписка не найдена в БД, пропуск', user_id=user.id)
|
||||
return
|
||||
await self._notify_user(
|
||||
user, 'WEBHOOK_SUB_EXPIRES_72H', reply_markup=self._get_renew_keyboard(user, subscription.id)
|
||||
user,
|
||||
'WEBHOOK_SUB_EXPIRES_72H',
|
||||
reply_markup=self._get_renew_keyboard(user, subscription.id),
|
||||
subscription=subscription,
|
||||
)
|
||||
|
||||
async def _handle_expires_in_48h(
|
||||
@@ -1120,7 +1149,10 @@ class RemnaWaveWebhookService:
|
||||
logger.info('Webhook expires_48h: подписка не найдена в БД, пропуск', user_id=user.id)
|
||||
return
|
||||
await self._notify_user(
|
||||
user, 'WEBHOOK_SUB_EXPIRES_48H', reply_markup=self._get_renew_keyboard(user, subscription.id)
|
||||
user,
|
||||
'WEBHOOK_SUB_EXPIRES_48H',
|
||||
reply_markup=self._get_renew_keyboard(user, subscription.id),
|
||||
subscription=subscription,
|
||||
)
|
||||
|
||||
async def _handle_expires_in_24h(
|
||||
@@ -1130,7 +1162,10 @@ class RemnaWaveWebhookService:
|
||||
logger.info('Webhook expires_24h: подписка не найдена в БД, пропуск', user_id=user.id)
|
||||
return
|
||||
await self._notify_user(
|
||||
user, 'WEBHOOK_SUB_EXPIRES_24H', reply_markup=self._get_renew_keyboard(user, subscription.id)
|
||||
user,
|
||||
'WEBHOOK_SUB_EXPIRES_24H',
|
||||
reply_markup=self._get_renew_keyboard(user, subscription.id),
|
||||
subscription=subscription,
|
||||
)
|
||||
|
||||
async def _handle_expired_24h_ago(
|
||||
@@ -1140,14 +1175,22 @@ class RemnaWaveWebhookService:
|
||||
logger.info('Webhook expired_24h_ago: подписка не найдена в БД, пропуск', user_id=user.id)
|
||||
return
|
||||
await self._notify_user(
|
||||
user, 'WEBHOOK_SUB_EXPIRED_24H_AGO', reply_markup=self._get_renew_keyboard(user, subscription.id)
|
||||
user,
|
||||
'WEBHOOK_SUB_EXPIRED_24H_AGO',
|
||||
reply_markup=self._get_renew_keyboard(user, subscription.id),
|
||||
subscription=subscription,
|
||||
)
|
||||
|
||||
async def _handle_first_connected(
|
||||
self, db: AsyncSession, user: User, subscription: Subscription | None, data: dict
|
||||
) -> None:
|
||||
logger.info('Webhook: user first VPN connection', user_id=user.id)
|
||||
await self._notify_user(user, 'WEBHOOK_SUB_FIRST_CONNECTED', reply_markup=self._get_subscription_keyboard(user))
|
||||
await self._notify_user(
|
||||
user,
|
||||
'WEBHOOK_SUB_FIRST_CONNECTED',
|
||||
reply_markup=self._get_subscription_keyboard(user),
|
||||
subscription=subscription,
|
||||
)
|
||||
|
||||
async def _handle_bandwidth_threshold(
|
||||
self, db: AsyncSession, user: User, subscription: Subscription | None, data: dict
|
||||
@@ -1168,6 +1211,7 @@ class RemnaWaveWebhookService:
|
||||
'WEBHOOK_SUB_BANDWIDTH_THRESHOLD',
|
||||
reply_markup=self._get_traffic_keyboard(user),
|
||||
format_kwargs={'percent': percent_str},
|
||||
subscription=subscription,
|
||||
)
|
||||
|
||||
async def _handle_user_not_connected(
|
||||
@@ -1188,6 +1232,7 @@ class RemnaWaveWebhookService:
|
||||
'WEBHOOK_USER_NOT_CONNECTED',
|
||||
reply_markup=self._get_connect_keyboard(user),
|
||||
format_kwargs=format_kwargs if format_kwargs else None,
|
||||
subscription=subscription,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@@ -1236,6 +1281,7 @@ class RemnaWaveWebhookService:
|
||||
'WEBHOOK_DEVICE_ADDED',
|
||||
reply_markup=self._get_subscription_keyboard(user),
|
||||
format_kwargs={'device': device_name or '—'},
|
||||
subscription=subscription,
|
||||
)
|
||||
|
||||
async def _handle_device_deleted(
|
||||
@@ -1248,4 +1294,5 @@ class RemnaWaveWebhookService:
|
||||
'WEBHOOK_DEVICE_DELETED',
|
||||
reply_markup=self._get_subscription_keyboard(user),
|
||||
format_kwargs={'device': device_name or '—'},
|
||||
subscription=subscription,
|
||||
)
|
||||
|
||||
@@ -66,6 +66,7 @@ class AutoExtendContext:
|
||||
squad_uuid: str | None = None
|
||||
consume_promo_offer: bool = False
|
||||
tariff_id: int | None = None
|
||||
tariff_name: str | None = None
|
||||
allowed_squads: list | None = None
|
||||
|
||||
|
||||
@@ -355,6 +356,7 @@ async def _prepare_auto_extend_context(
|
||||
squad_uuid=squad_uuid,
|
||||
consume_promo_offer=consume_promo_offer,
|
||||
tariff_id=tariff_id,
|
||||
tariff_name=tariff_name if tariff_id else None,
|
||||
allowed_squads=allowed_squads,
|
||||
)
|
||||
|
||||
@@ -623,6 +625,8 @@ async def _auto_extend_subscription(
|
||||
'AUTO_PURCHASE_SUBSCRIPTION_EXTENDED',
|
||||
'✅ Subscription automatically extended for {period}.',
|
||||
).format(period=period_label)
|
||||
if settings.is_multi_tariff_enabled() and prepared.tariff_name:
|
||||
auto_message += f'\n📦 Тариф: «{prepared.tariff_name}»'
|
||||
details_message = texts.t(
|
||||
'AUTO_PURCHASE_SUBSCRIPTION_EXTENDED_DETAILS',
|
||||
'New expiration date: {date}.',
|
||||
@@ -727,6 +731,8 @@ async def _auto_purchase_tariff(
|
||||
return False
|
||||
|
||||
tariff = await get_tariff_by_id(db, tariff_id)
|
||||
# Capture name before any db.commit() can expire the ORM object
|
||||
tariff_name_for_label = tariff.name if tariff else None
|
||||
if not tariff or not tariff.is_active:
|
||||
logger.warning(
|
||||
'🔁 Автопокупка тарифа: тариф недоступен для пользователя',
|
||||
@@ -984,6 +990,8 @@ async def _auto_purchase_tariff(
|
||||
'AUTO_PURCHASE_SUBSCRIPTION_SUCCESS',
|
||||
'✅ Подписка на {period} автоматически оформлена после пополнения баланса.',
|
||||
).format(period=period_label)
|
||||
if settings.is_multi_tariff_enabled() and tariff_name_for_label:
|
||||
message += f'\n📦 Тариф: «{tariff_name_for_label}»'
|
||||
|
||||
hint = texts.t(
|
||||
'AUTO_PURCHASE_SUBSCRIPTION_HINT',
|
||||
@@ -2124,6 +2132,8 @@ async def try_auto_extend_expired_after_topup(
|
||||
|
||||
# Determine renewal period from tariff or default to 30 days
|
||||
tariff = getattr(subscription, 'tariff', None)
|
||||
# Capture name before any db.commit() can expire the ORM object
|
||||
tariff_name_for_label = tariff.name if tariff else None
|
||||
if tariff:
|
||||
period_days = tariff.get_shortest_period() or 30
|
||||
else:
|
||||
@@ -2359,6 +2369,8 @@ async def try_auto_extend_expired_after_topup(
|
||||
'AUTO_PURCHASE_SUBSCRIPTION_EXTENDED',
|
||||
'✅ Subscription automatically extended for {period}.',
|
||||
).format(period=period_label)
|
||||
if settings.is_multi_tariff_enabled() and tariff_name_for_label:
|
||||
auto_message += f'\n📦 Тариф: «{tariff_name_for_label}»'
|
||||
details_message = texts.t(
|
||||
'AUTO_PURCHASE_SUBSCRIPTION_EXTENDED_DETAILS',
|
||||
'New expiration date: {date}.',
|
||||
@@ -3111,6 +3123,15 @@ async def _process_legacy_generic_cart(
|
||||
'AUTO_PURCHASE_SUBSCRIPTION_SUCCESS',
|
||||
'✅ Subscription purchased automatically after balance top-up ({period}).',
|
||||
).format(period=period_label)
|
||||
if settings.is_multi_tariff_enabled() and subscription and getattr(subscription, 'tariff_id', None):
|
||||
try:
|
||||
from app.database.crud.tariff import get_tariff_by_id as _get_tariff_label
|
||||
|
||||
_t = await _get_tariff_label(db, subscription.tariff_id)
|
||||
if _t:
|
||||
auto_message += f'\n📦 Тариф: «{_t.name}»'
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
hint_message = texts.t(
|
||||
'AUTO_PURCHASE_SUBSCRIPTION_HINT',
|
||||
|
||||
Reference in New Issue
Block a user