feat: tune visual of web app admin panel, tune limit messages and etc

This commit is contained in:
3252a8
2026-05-12 15:42:17 +03:00
parent 8af59750c6
commit 475a1953d1
34 changed files with 2777 additions and 114 deletions
+449 -3
View File
@@ -192,6 +192,16 @@ def get_user_card_keyboard(user_id: int, i18n_instance, lang: str,
callback_data=f"user_action:refresh:{user_id}"
)
# Row 3b: Premium override + traffic grant
builder.button(
text=_(key="admin_user_premium_override_button"),
callback_data=f"user_action:premium_override:{user_id}"
)
builder.button(
text=_(key="admin_user_traffic_grant_button"),
callback_data=f"user_action:traffic_grant:{user_id}"
)
# Row 4: Quick links — only for users with a real Telegram profile
# (synthetic email-only users have a negative user_id with no tg profile).
has_self_link = user_id > 0
@@ -225,9 +235,9 @@ def get_user_card_keyboard(user_id: int, i18n_instance, lang: str,
quick_links_count = (1 if has_self_link else 0) + (1 if has_referrer_link else 0)
if quick_links_count == 0:
builder.adjust(2, 2, 2, 1, 2)
builder.adjust(2, 2, 2, 2, 1, 2)
else:
builder.adjust(2, 2, 2, quick_links_count, 1, 2)
builder.adjust(2, 2, 2, 2, quick_links_count, 1, 2)
return builder
@@ -343,6 +353,18 @@ async def format_user_card(user: User, session: AsyncSession,
limit_display = _("traffic_unlimited")
card_parts.append(f"{_('admin_user_traffic_label')} {hcode(f'{used_display} / {limit_display}')}")
premium_unlimited = bool(subscription_details.get("premium_unlimited_override"))
premium_bonus_bytes = int(subscription_details.get("premium_bonus_bytes") or 0)
if premium_unlimited:
card_parts.append(
f"{_('admin_user_premium_override_label')} {hcode(_('admin_user_premium_override_unlimited'))}"
)
elif premium_bonus_bytes > 0:
bonus_gb = premium_bonus_bytes / (1024 ** 3)
card_parts.append(
f"{_('admin_user_premium_override_label')} {hcode(_('admin_user_premium_override_bonus_value', gb=f'{bonus_gb:.2f}'))}"
)
else:
card_parts.append(f"{_('admin_user_subscription_label')} {hcode(_('admin_user_subscription_none'))}")
except Exception as e:
@@ -541,10 +563,237 @@ async def user_action_handler(callback: types.CallbackQuery, state: FSMContext,
await handle_delete_user_prompt(
callback, state, user, settings, i18n, current_lang, session
)
elif action == "premium_override":
await handle_premium_override_menu(
callback, state, user, subscription_service, session, i18n, current_lang
)
elif action == "premium_override_set_unlimited":
await handle_premium_override_apply(
callback, user, subscription_service, session, i18n, current_lang,
unlimited=True, bonus_bytes=0,
)
elif action == "premium_override_clear":
await handle_premium_override_apply(
callback, user, subscription_service, session, i18n, current_lang,
unlimited=False, bonus_bytes=0,
)
elif action == "premium_override_set_bonus":
await handle_premium_override_bonus_prompt(
callback, state, user, i18n, current_lang
)
elif action == "traffic_grant":
await handle_traffic_grant_menu(callback, user, i18n, current_lang)
elif action == "traffic_grant_regular":
await handle_traffic_grant_prompt(callback, state, user, "regular", i18n, current_lang)
elif action == "traffic_grant_premium":
await handle_traffic_grant_prompt(callback, state, user, "premium", i18n, current_lang)
else:
await callback.answer(_("admin_unknown_action"), show_alert=True)
async def handle_premium_override_menu(
callback: types.CallbackQuery,
state: FSMContext,
user: User,
subscription_service: SubscriptionService,
session: AsyncSession,
i18n_instance,
lang: str,
) -> None:
"""Show premium override sub-menu with current state and quick toggles."""
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
active_sub = await subscription_dal.get_active_subscription_by_user_id(session, user.user_id)
if not active_sub:
await callback.answer(_("admin_premium_override_no_subscription"), show_alert=True)
return
unlimited = bool(getattr(active_sub, "premium_unlimited_override", False))
bonus_bytes = int(getattr(active_sub, "premium_bonus_bytes", 0) or 0)
bonus_gb = bonus_bytes / (1024 ** 3) if bonus_bytes else 0
if unlimited:
current_text = _("admin_premium_override_state_unlimited")
elif bonus_bytes > 0:
current_text = _("admin_premium_override_state_bonus", gb=f"{bonus_gb:.2f}")
else:
current_text = _("admin_premium_override_state_none")
text = "\n".join(
[
f"<b>{_('admin_premium_override_title')}</b>",
"",
_("admin_premium_override_hint"),
"",
_("admin_premium_override_current", current=current_text),
]
)
builder = InlineKeyboardBuilder()
builder.button(
text=_("admin_premium_override_btn_unlimited"),
callback_data=f"user_action:premium_override_set_unlimited:{user.user_id}",
)
builder.button(
text=_("admin_premium_override_btn_bonus"),
callback_data=f"user_action:premium_override_set_bonus:{user.user_id}",
)
builder.button(
text=_("admin_premium_override_btn_clear"),
callback_data=f"user_action:premium_override_clear:{user.user_id}",
)
builder.button(
text=_("admin_user_back_to_card_button"),
callback_data=f"user_action:refresh:{user.user_id}",
)
builder.adjust(1, 1, 1, 1)
try:
await callback.message.edit_text(text, reply_markup=builder.as_markup(), parse_mode="HTML")
except Exception:
await callback.message.answer(text, reply_markup=builder.as_markup(), parse_mode="HTML")
await state.update_data(target_user_id=user.user_id)
await callback.answer()
async def handle_premium_override_apply(
callback: types.CallbackQuery,
user: User,
subscription_service: SubscriptionService,
session: AsyncSession,
i18n_instance,
lang: str,
*,
unlimited: bool,
bonus_bytes: int,
) -> None:
"""Persist a premium override (unlimited or explicit bonus) on the active subscription."""
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
try:
active_sub = await subscription_dal.get_active_subscription_by_user_id(session, user.user_id)
if not active_sub:
await callback.answer(_("admin_premium_override_no_subscription"), show_alert=True)
return
active_sub.premium_unlimited_override = bool(unlimited)
active_sub.premium_bonus_bytes = max(0, int(bonus_bytes or 0))
if unlimited:
active_sub.premium_is_limited = False
await message_log_dal.create_message_log_no_commit(
session,
{
"user_id": callback.from_user.id if callback.from_user else user.user_id,
"event_type": "admin:premium_override",
"content": (
f"unlimited={bool(unlimited)} bonus_bytes={int(bonus_bytes or 0)}"
),
"is_admin_event": True,
"target_user_id": user.user_id,
"timestamp": datetime.now(timezone.utc),
},
)
await session.commit()
await callback.answer(_("admin_premium_override_saved"), show_alert=False)
await handle_refresh_user_card(callback, user, subscription_service, session, i18n_instance, lang)
except Exception as exc:
logging.error(
"Failed to apply premium override for user %s: %s", user.user_id, exc, exc_info=True
)
await session.rollback()
await callback.answer(_("admin_premium_override_save_error"), show_alert=True)
async def handle_premium_override_bonus_prompt(
callback: types.CallbackQuery,
state: FSMContext,
user: User,
i18n_instance,
lang: str,
) -> None:
"""Ask admin for the bonus GB to grant."""
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
await state.update_data(target_user_id=user.user_id)
await state.set_state(AdminStates.waiting_for_premium_override_bonus_gb)
prompt = _("admin_premium_override_bonus_prompt", user_id=user.user_id)
try:
await callback.message.edit_text(prompt)
except Exception:
await callback.message.answer(prompt)
await callback.answer()
async def handle_traffic_grant_menu(
callback: types.CallbackQuery,
user: User,
i18n_instance,
lang: str,
) -> None:
"""Show traffic-grant menu (regular vs premium)."""
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
text = "\n".join(
[
f"<b>{_('admin_traffic_grant_title')}</b>",
"",
_("admin_traffic_grant_hint"),
]
)
builder = InlineKeyboardBuilder()
builder.button(
text=_("admin_traffic_grant_btn_regular"),
callback_data=f"user_action:traffic_grant_regular:{user.user_id}",
)
builder.button(
text=_("admin_traffic_grant_btn_premium"),
callback_data=f"user_action:traffic_grant_premium:{user.user_id}",
)
builder.button(
text=_("admin_user_back_to_card_button"),
callback_data=f"user_action:refresh:{user.user_id}",
)
builder.adjust(1, 1, 1)
try:
await callback.message.edit_text(text, reply_markup=builder.as_markup(), parse_mode="HTML")
except Exception:
await callback.message.answer(text, reply_markup=builder.as_markup(), parse_mode="HTML")
await callback.answer()
async def handle_traffic_grant_prompt(
callback: types.CallbackQuery,
state: FSMContext,
user: User,
kind: str,
i18n_instance,
lang: str,
) -> None:
"""Ask the admin for the amount of GB to grant (regular or premium)."""
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
kind_normalized = "premium" if kind == "premium" else "regular"
await state.update_data(target_user_id=user.user_id, traffic_grant_kind=kind_normalized)
await state.set_state(AdminStates.waiting_for_traffic_grant_gb)
prompt_key = (
"admin_traffic_grant_prompt_premium"
if kind_normalized == "premium"
else "admin_traffic_grant_prompt_regular"
)
prompt = _(prompt_key, user_id=user.user_id)
try:
await callback.message.edit_text(prompt)
except Exception:
await callback.message.answer(prompt)
await callback.answer()
# `process_traffic_grant_gb_handler` is declared near the other FSM-bound
# handlers at the bottom of the module, so the router decorator can attach to
# the same `router` instance the premium override flow uses.
async def handle_reset_trial(callback: types.CallbackQuery, user: User,
subscription_service: SubscriptionService,
session: AsyncSession, i18n_instance, lang: str):
@@ -1372,6 +1621,203 @@ async def process_unban_user_handler(message: types.Message, state: FSMContext,
await state.clear()
@router.message(AdminStates.waiting_for_premium_override_bonus_gb, F.text)
async def process_premium_override_bonus_handler(
message: types.Message,
state: FSMContext,
settings: Settings,
i18n_data: dict,
subscription_service: SubscriptionService,
session: AsyncSession,
):
"""Read bonus GB and apply premium override (non-unlimited path)."""
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await message.reply("Language service error.")
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
data = await state.get_data()
target_user_id = data.get("target_user_id")
if not target_user_id:
await message.answer(_("admin_premium_override_state_missing"))
await state.clear()
return
raw = (message.text or "").strip().replace(",", ".")
try:
gb = float(raw)
if gb < 0 or gb > 1_000_000:
raise ValueError("out_of_range")
except (TypeError, ValueError):
await message.answer(_("admin_premium_override_invalid_gb"))
return
bonus_bytes = int(round(gb * (1024 ** 3)))
target_user = await user_dal.get_user_by_id(session, target_user_id)
if not target_user:
await message.answer(_("admin_user_not_found_action"))
await state.clear()
return
try:
active_sub = await subscription_dal.get_active_subscription_by_user_id(
session, target_user_id
)
if not active_sub:
await message.answer(_("admin_premium_override_no_subscription"))
await state.clear()
return
active_sub.premium_unlimited_override = False
active_sub.premium_bonus_bytes = max(0, bonus_bytes)
await message_log_dal.create_message_log_no_commit(
session,
{
"user_id": message.from_user.id if message.from_user else target_user_id,
"event_type": "admin:premium_override",
"content": f"unlimited=False bonus_bytes={int(bonus_bytes)}",
"is_admin_event": True,
"target_user_id": target_user_id,
"timestamp": datetime.now(timezone.utc),
},
)
await session.commit()
await message.answer(
_("admin_premium_override_bonus_set", gb=f"{gb:.2f}", user_id=target_user_id)
)
referral_service = ReferralService(settings, subscription_service, message.bot, i18n)
bot_username = await _resolve_bot_username(message.bot)
user_card_text = await format_user_card(
target_user, session, subscription_service, i18n, current_lang, referral_service,
settings=settings, bot_username=bot_username,
)
keyboard = get_user_card_keyboard(
target_user.user_id, i18n, current_lang, target_user.referred_by_id
)
await _send_with_profile_link_fallback(
message.answer,
text=user_card_text,
markup=keyboard.as_markup(),
user_id=target_user.user_id,
parse_mode="HTML",
)
except Exception as exc:
logging.error(
"Error setting premium override bonus for user %s: %s", target_user_id, exc, exc_info=True
)
await session.rollback()
await message.answer(_("admin_premium_override_save_error"))
finally:
await state.clear()
@router.message(AdminStates.waiting_for_traffic_grant_gb, F.text)
async def process_traffic_grant_gb_handler(
message: types.Message,
state: FSMContext,
settings: Settings,
i18n_data: dict,
subscription_service: SubscriptionService,
session: AsyncSession,
):
"""Read GB amount and apply admin grant of regular or premium traffic."""
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await message.reply("Language service error.")
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
data = await state.get_data()
target_user_id = data.get("target_user_id")
kind = (data.get("traffic_grant_kind") or "regular").lower()
if not target_user_id:
await message.answer(_("admin_traffic_grant_no_user"))
await state.clear()
return
raw = (message.text or "").strip().replace(",", ".")
try:
gb_value = float(raw)
if gb_value <= 0 or gb_value > 1_000_000:
raise ValueError("out_of_range")
except (TypeError, ValueError):
await message.answer(_("admin_traffic_grant_invalid_gb"))
return
target_user = await user_dal.get_user_by_id(session, target_user_id)
if not target_user:
await message.answer(_("admin_user_not_found_action"))
await state.clear()
return
try:
if kind == "premium":
result = await subscription_service.admin_grant_premium_topup(
session, target_user_id, gb_value
)
else:
result = await subscription_service.admin_grant_topup(
session, target_user_id, gb_value
)
if not result:
await session.rollback()
await message.answer(_("admin_traffic_grant_failed"))
await state.clear()
return
await message_log_dal.create_message_log_no_commit(
session,
{
"user_id": message.from_user.id if message.from_user else target_user_id,
"event_type": "admin:traffic_grant",
"content": f"kind={kind} gb={gb_value:g}",
"is_admin_event": True,
"target_user_id": target_user_id,
"timestamp": datetime.now(timezone.utc),
},
)
await session.commit()
gb_text = f"{gb_value:g}"
success_key = (
"admin_traffic_grant_premium_done"
if kind == "premium"
else "admin_traffic_grant_regular_done"
)
await message.answer(_(success_key, gb=gb_text, user_id=target_user_id))
referral_service = ReferralService(settings, subscription_service, message.bot, i18n)
bot_username = await _resolve_bot_username(message.bot)
user_card_text = await format_user_card(
target_user, session, subscription_service, i18n, current_lang, referral_service,
settings=settings, bot_username=bot_username,
)
keyboard = get_user_card_keyboard(
target_user.user_id, i18n, current_lang, target_user.referred_by_id
)
await _send_with_profile_link_fallback(
message.answer,
text=user_card_text,
markup=keyboard.as_markup(),
user_id=target_user.user_id,
parse_mode="HTML",
)
except Exception as exc:
logging.error(
"Error granting traffic for user %s (kind=%s, gb=%s): %s",
target_user_id, kind, gb_value, exc, exc_info=True,
)
await session.rollback()
await message.answer(_("admin_traffic_grant_failed"))
finally:
await state.clear()
@router.callback_query(F.data.startswith("admin_user_card_from_list:"))
async def user_card_from_list_handler(callback: types.CallbackQuery,
state: FSMContext, i18n_data: dict,
@@ -1413,7 +1859,7 @@ async def user_card_from_list_handler(callback: types.CallbackQuery,
callback_data=f"admin_action:users_list:{page}"
)
quick_links_width = 2 if user.referred_by_id else 1
keyboard.adjust(2, 2, 2, quick_links_width, 1, 2, 1)
keyboard.adjust(2, 2, 2, 2, quick_links_width, 1, 2, 1)
# Format user card
try:
+15 -6
View File
@@ -266,7 +266,7 @@ async def process_successful_payment(session: AsyncSession, bot: Bot,
"applied_promo_bonus_days", 0)
referral_bonus_info = None
if sale_mode != "traffic":
if sale_mode_base == "subscription":
referral_bonus_info = await referral_service.apply_referral_bonuses_for_payment(
session,
user_id,
@@ -292,7 +292,7 @@ async def process_successful_payment(session: AsyncSession, bot: Bot,
if should_send_lknpd_receipt:
receipt_item_name = payment_info_from_webhook.get("description")
if not receipt_item_name:
if sale_mode == "traffic":
if sale_mode_base in {"traffic", "traffic_package", "topup", "premium_topup"}:
receipt_item_name = settings.LKNPD_RECEIPT_NAME_TRAFFIC.format(gb=traffic_label)
else:
receipt_item_name = settings.LKNPD_RECEIPT_NAME_SUBSCRIPTION.format(months=int(subscription_months))
@@ -313,14 +313,14 @@ async def process_successful_payment(session: AsyncSession, bot: Bot,
)
config_link_text = config_link_display or _("config_link_not_available")
# For auto-renew charges, avoid re-sending config link; send concise message
if sale_mode != "traffic" and is_auto_renew and final_end_date_for_user:
if sale_mode_base == "subscription" and is_auto_renew and final_end_date_for_user:
details_message = _(
"yookassa_auto_renewal",
months=int(subscription_months),
end_date=final_end_date_for_user.strftime('%Y-%m-%d'),
)
details_markup = None
elif sale_mode == "traffic":
elif sale_mode_base in {"traffic", "traffic_package", "topup", "premium_topup"}:
details_message = _(
"payment_successful_traffic_full",
traffic_gb=traffic_label,
@@ -403,14 +403,23 @@ async def process_successful_payment(session: AsyncSession, bot: Bot,
try:
notification_service = NotificationService(bot, settings, i18n)
user = await user_dal.get_user_by_id(session, user_id)
tariff_for_log = None
if payment_before_update and getattr(payment_before_update, "tariff_key", None):
tariff_for_log = payment_before_update.tariff_key
elif updated_payment_record and getattr(updated_payment_record, "tariff_key", None):
tariff_for_log = updated_payment_record.tariff_key
elif payment_record and getattr(payment_record, "tariff_key", None):
tariff_for_log = payment_record.tariff_key
await notification_service.notify_payment_received(
user_id=user_id,
amount=payment_value,
currency=settings.DEFAULT_CURRENCY_SYMBOL,
months=int(subscription_months) if sale_mode != "traffic" else 0,
months=int(subscription_months) if sale_mode_base == "subscription" else 0,
payment_provider="yookassa", # This is specifically for YooKassa webhook
username=user.username if user else None,
traffic_gb=traffic_amount_gb if sale_mode == "traffic" else None,
traffic_gb=traffic_amount_gb if sale_mode_base in {"traffic", "traffic_package", "topup", "premium_topup"} else None,
traffic_is_premium=sale_mode_base == "premium_topup",
tariff_key=tariff_for_log,
)
except Exception as e:
logging.error(f"Failed to send payment notification: {e}")
@@ -75,7 +75,7 @@ async def pay_fk_callback_handler(
"currency": currency_code,
"status": "pending_freekassa",
"description": payment_description,
"subscription_duration_months": int(months),
"subscription_duration_months": int(months) if sale_base == "subscription" else None,
"provider": "freekassa",
"sale_mode": sale_mode,
"tariff_key": sale_mode.split("@", 1)[1] if "@" in sale_mode else None,
@@ -147,7 +147,7 @@ async def pay_fk_callback_handler(
try:
await callback.message.edit_text(
f"{order_info_text}\n\n" + get_text(
key="payment_link_message_traffic" if sale_mode == "traffic" else "payment_link_message",
key="payment_link_message_traffic" if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else "payment_link_message",
months=int(months),
traffic_gb=human_value,
),
@@ -165,7 +165,7 @@ async def pay_fk_callback_handler(
try:
await callback.message.answer(
f"{order_info_text}\n\n" + get_text(
key="payment_link_message_traffic" if sale_mode == "traffic" else "payment_link_message",
key="payment_link_message_traffic" if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else "payment_link_message",
months=int(months),
traffic_gb=human_value,
),
@@ -102,7 +102,7 @@ async def pay_platega_callback_handler(
"currency": currency_code,
"status": "pending_platega",
"description": payment_description,
"subscription_duration_months": int(months),
"subscription_duration_months": int(months) if sale_base == "subscription" else None,
"provider": "platega",
"sale_mode": sale_mode,
"tariff_key": sale_mode.split("@", 1)[1] if "@" in sale_mode else None,
@@ -178,7 +178,7 @@ async def pay_platega_callback_handler(
try:
await callback.message.edit_text(
get_text(
key="payment_link_message_traffic" if sale_mode == "traffic" else "payment_link_message",
key="payment_link_message_traffic" if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else "payment_link_message",
months=int(months),
traffic_gb=human_value,
),
@@ -196,7 +196,7 @@ async def pay_platega_callback_handler(
try:
await callback.message.answer(
get_text(
key="payment_link_message_traffic" if sale_mode == "traffic" else "payment_link_message",
key="payment_link_message_traffic" if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else "payment_link_message",
months=int(months),
traffic_gb=human_value,
),
@@ -74,7 +74,7 @@ async def pay_severpay_callback_handler(
"currency": currency_code,
"status": "pending_severpay",
"description": payment_description,
"subscription_duration_months": int(months),
"subscription_duration_months": int(months) if sale_base == "subscription" else None,
"provider": "severpay",
"sale_mode": sale_mode,
"tariff_key": sale_mode.split("@", 1)[1] if "@" in sale_mode else None,
@@ -138,7 +138,7 @@ async def pay_severpay_callback_handler(
try:
await callback.message.edit_text(
get_text(
key="payment_link_message_traffic" if sale_mode == "traffic" else "payment_link_message",
key="payment_link_message_traffic" if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else "payment_link_message",
months=int(months),
traffic_gb=human_value,
),
@@ -156,7 +156,7 @@ async def pay_severpay_callback_handler(
try:
await callback.message.answer(
get_text(
key="payment_link_message_traffic" if sale_mode == "traffic" else "payment_link_message",
key="payment_link_message_traffic" if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else "payment_link_message",
months=int(months),
traffic_gb=human_value,
),
@@ -93,7 +93,7 @@ async def _initiate_yk_payment(
"currency": currency_code_for_yk,
"status": "pending_yookassa",
"description": payment_description,
"subscription_duration_months": int(months),
"subscription_duration_months": int(months) if sale_base == "subscription" else None,
"sale_mode": sale_base,
"tariff_key": sale_mode.split("@", 1)[1] if "@" in sale_mode else None,
"purchased_gb": float(months) if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else None,
@@ -223,7 +223,7 @@ async def _initiate_yk_payment(
try:
await callback.message.edit_text(
get_text(
key="payment_link_message_traffic" if sale_mode == "traffic" else "payment_link_message",
key="payment_link_message_traffic" if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else "payment_link_message",
months=int(months),
traffic_gb=_format_value(months),
),
@@ -243,7 +243,7 @@ async def _initiate_yk_payment(
try:
await callback.message.answer(
get_text(
key="payment_link_message_traffic" if sale_mode == "traffic" else "payment_link_message",
key="payment_link_message_traffic" if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else "payment_link_message",
months=int(months),
traffic_gb=_format_value(months),
),