fix: eliminate double panel API call on tariff change, harden cart notification

Bug 1 improvement: Replaced double API call pattern (sync + update_remnawave_user)
with single _sync_subscription_to_panel call that accepts reset_traffic parameter.
This prevents TRIAL status being overwritten to EXPIRED by the second call's
different status computation logic.

Bug 2 improvement: Moved keyboard construction inside try block to prevent
AttributeError crash if locale keys are missing. Switched button text from
attribute access (texts.KEY) to defensive texts.get('KEY', fallback).
Added empty template guard to prevent sending empty messages to Telegram API.
This commit is contained in:
Fringg
2026-03-02 20:53:59 +03:00
parent 1256ddcd1a
commit b2cf4aaa91
2 changed files with 46 additions and 26 deletions

View File

@@ -206,10 +206,17 @@ async def _build_subscription_info_async(db: AsyncSession, subscription: Subscri
return info
async def _sync_subscription_to_panel(db: AsyncSession, user: User, subscription: Subscription) -> dict:
async def _sync_subscription_to_panel(
db: AsyncSession,
user: User,
subscription: Subscription,
reset_traffic: bool = False,
reset_traffic_reason: str | None = None,
) -> dict:
"""
Sync user subscription to Remnawave panel.
Creates user if not exists, updates if exists.
Optionally resets traffic after sync.
Returns dict with changes/errors.
"""
try:
@@ -335,6 +342,16 @@ async def _sync_subscription_to_panel(db: AsyncSession, user: User, subscription
changes['panel_uuid'] = new_panel_user.uuid
logger.info('Created user in Remnawave panel', user_id=user.id, uuid=new_panel_user.uuid)
# Reset traffic on panel if requested
if reset_traffic and user.remnawave_uuid:
try:
await api.reset_user_traffic(user.remnawave_uuid)
changes['traffic_reset'] = True
reason_text = f' ({reset_traffic_reason})' if reset_traffic_reason else ''
logger.info('Reset RemnaWave traffic for user', user_id=user.id, reason=reason_text)
except Exception as reset_exc:
logger.warning('Failed to reset RemnaWave traffic', user_id=user.id, error=reset_exc)
user.last_remnawave_sync = datetime.now(UTC)
await db.commit()
@@ -1075,17 +1092,11 @@ async def update_user_subscription(
# Синхронизируем с RemnaWave (discovery/create + сброс трафика по админ-настройке)
try:
result = await _sync_subscription_to_panel(db, user, subscription)
if settings.RESET_TRAFFIC_ON_TARIFF_SWITCH and result.get('action') in ('updated', 'created'):
from app.services.subscription_service import SubscriptionService
subscription_service = SubscriptionService()
await subscription_service.update_remnawave_user(
db,
subscription,
reset_traffic=True,
reset_reason='смена тарифа (cabinet admin)',
)
await _sync_subscription_to_panel(
db, user, subscription,
reset_traffic=settings.RESET_TRAFFIC_ON_TARIFF_SWITCH,
reset_traffic_reason='смена тарифа (cabinet admin)',
)
except Exception as e:
logger.error('Failed to sync tariff switch with RemnaWave', error=e)

View File

@@ -352,21 +352,30 @@ async def send_cart_notification_after_topup(
amount=fmt(amount_kopeks), balance=fmt(balance), cart_total=fmt(cart_total), missing=fmt(missing),
)
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT,
callback_data='return_to_saved_cart',
)
],
[types.InlineKeyboardButton(text=texts.MY_BALANCE_BUTTON, callback_data='menu_balance')],
[types.InlineKeyboardButton(text=texts.MAIN_MENU_BUTTON, callback_data='back_to_menu')],
]
)
if not message_text:
logger.warning('Missing cart notification template', language=getattr(user, 'language', 'ru'))
return False
sent = False
try:
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.get('RETURN_TO_SUBSCRIPTION_CHECKOUT', '⬅️ Checkout'),
callback_data='return_to_saved_cart',
)
],
[types.InlineKeyboardButton(
text=texts.get('MY_BALANCE_BUTTON', '💰 Balance'),
callback_data='menu_balance',
)],
[types.InlineKeyboardButton(
text=texts.get('MAIN_MENU_BUTTON', '🏠 Menu'),
callback_data='back_to_menu',
)],
]
)
await bot.send_message(
chat_id=user.telegram_id,
text=message_text,
@@ -374,10 +383,10 @@ async def send_cart_notification_after_topup(
parse_mode='HTML',
)
sent = True
logger.info('Отправлено уведомление с кнопкой возврата к оформлению подписки пользователю', user_id=user.id)
logger.info('Sent cart notification to user', user_id=user.id)
except Exception as send_error:
logger.error(
'Ошибка отправки уведомления о корзине пользователю',
'Failed to send cart notification to user',
user_id=user.id,
error=send_error,
)