feat(promo): Integrate promo code service into subscription options

- Enhanced the display_subscription_options and reshow_subscription_options_callback functions to accept a promo_code_service parameter for improved discount handling.
- Updated the logic to calculate and display discounted prices based on active promo codes, including support for both regular and star-based pricing.
- Modified relevant locales to include currency symbols in discount notices for better user clarity.
This commit is contained in:
kavore
2026-02-02 00:35:31 +03:00
parent 484327a032
commit 5bb8f2add0
5 changed files with 82 additions and 22 deletions
+1 -1
View File
@@ -675,7 +675,7 @@ async def main_action_callback_handler(
if action == "subscribe": if action == "subscribe":
await user_subscription_handlers.display_subscription_options( await user_subscription_handlers.display_subscription_options(
callback, i18n_data, settings, session) callback, i18n_data, settings, session, promo_code_service=promo_code_service)
elif action == "my_subscription": elif action == "my_subscription":
await user_subscription_handlers.my_subscription_command_handler( await user_subscription_handlers.my_subscription_command_handler(
callback, i18n_data, settings, panel_service, subscription_service, callback, i18n_data, settings, panel_service, subscription_service,
+45 -5
View File
@@ -1,5 +1,6 @@
import hashlib import hashlib
import logging import logging
import math
from aiogram import Router, F, types, Bot from aiogram import Router, F, types, Bot
from aiogram.filters import Command from aiogram.filters import Command
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, WebAppInfo from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, WebAppInfo
@@ -39,7 +40,13 @@ def _hwid_callback_token(hwid: Optional[str]) -> str:
return hashlib.sha256(hwid_str.encode()).hexdigest()[:32] return hashlib.sha256(hwid_str.encode()).hexdigest()[:32]
async def display_subscription_options(event: Union[types.Message, types.CallbackQuery], i18n_data: dict, settings: Settings, session: AsyncSession): async def display_subscription_options(
event: Union[types.Message, types.CallbackQuery],
i18n_data: dict,
settings: Settings,
session: AsyncSession,
promo_code_service=None,
):
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE) current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
@@ -61,21 +68,46 @@ async def display_subscription_options(event: Union[types.Message, types.Callbac
stars_traffic_packages = getattr(settings, "stars_traffic_packages", {}) or {} stars_traffic_packages = getattr(settings, "stars_traffic_packages", {}) or {}
traffic_mode = bool(getattr(settings, "traffic_sale_mode", False) or stars_traffic_packages) traffic_mode = bool(getattr(settings, "traffic_sale_mode", False) or stars_traffic_packages)
options_are_stars = False
if traffic_mode: if traffic_mode:
if traffic_packages: if traffic_packages:
options = traffic_packages options = traffic_packages
elif stars_traffic_packages: elif stars_traffic_packages:
options = stars_traffic_packages options = stars_traffic_packages
currency_symbol_val = "" currency_symbol_val = ""
options_are_stars = True
else: else:
options = {} options = {}
else: else:
options = settings.subscription_options options = settings.subscription_options
if options: display_options = options
if options and promo_code_service:
try:
active_discount_info = await promo_code_service.get_user_active_discount(
session, event.from_user.id
)
except Exception:
active_discount_info = None
if active_discount_info:
discount_pct, _promo_code = active_discount_info
discounted_options = {}
for period, price in options.items():
if price is None:
discounted_options[period] = price
else:
discounted_price, _ = promo_code_service.calculate_discounted_price(
price, discount_pct
)
if options_are_stars:
discounted_price = math.ceil(discounted_price)
discounted_options[period] = discounted_price
display_options = discounted_options
if display_options:
text_content = get_text("select_traffic_package") if traffic_mode else get_text("select_subscription_period") text_content = get_text("select_traffic_package") if traffic_mode else get_text("select_subscription_period")
reply_markup = get_subscription_options_keyboard( reply_markup = get_subscription_options_keyboard(
options, currency_symbol_val, current_lang, i18n, traffic_mode=traffic_mode display_options, currency_symbol_val, current_lang, i18n, traffic_mode=traffic_mode
) )
else: else:
text_content = get_text("no_subscription_options_available") text_content = get_text("no_subscription_options_available")
@@ -104,8 +136,16 @@ async def display_subscription_options(event: Union[types.Message, types.Callbac
@router.callback_query(F.data == "main_action:subscribe") @router.callback_query(F.data == "main_action:subscribe")
async def reshow_subscription_options_callback(callback: types.CallbackQuery, i18n_data: dict, settings: Settings, session: AsyncSession): async def reshow_subscription_options_callback(
await display_subscription_options(callback, i18n_data, settings, session) callback: types.CallbackQuery,
i18n_data: dict,
settings: Settings,
session: AsyncSession,
promo_code_service=None,
):
await display_subscription_options(
callback, i18n_data, settings, session, promo_code_service=promo_code_service
)
async def my_subscription_command_handler( async def my_subscription_command_handler(
@@ -1,4 +1,5 @@
import logging import logging
import math
from typing import Optional from typing import Optional
from aiogram import F, Router, types from aiogram import F, Router, types
@@ -52,26 +53,45 @@ async def select_subscription_period_callback_handler(
# Check for active discount and apply if exists # Check for active discount and apply if exists
discount_text = "" discount_text = ""
if promo_code_service and price_rub: if promo_code_service and (price_rub is not None or stars_price is not None):
active_discount_info = await promo_code_service.get_user_active_discount( active_discount_info = await promo_code_service.get_user_active_discount(
session, callback.from_user.id session, callback.from_user.id
) )
if active_discount_info: if active_discount_info:
discount_pct, promo_code = active_discount_info discount_pct, promo_code = active_discount_info
original_price_rub = price_rub if price_rub is not None:
price_rub, discount_amt = promo_code_service.calculate_discounted_price( original_price_rub = price_rub
price_rub, discount_pct price_rub, discount_amt = promo_code_service.calculate_discounted_price(
) price_rub, discount_pct
discount_text = get_text( )
"active_discount_notice", discount_text = get_text(
code=promo_code, "active_discount_notice",
discount_pct=discount_pct, code=promo_code,
original_price=original_price_rub, discount_pct=discount_pct,
discounted_price=price_rub, original_price=original_price_rub,
discount_amount=discount_amt discounted_price=price_rub,
) discount_amount=discount_amt,
# Note: Stars prices typically don't get discounts (can be added if needed) currency_symbol=currency_symbol_val,
)
if stars_price is not None:
original_stars_price = stars_price
discounted_stars_price, _ = promo_code_service.calculate_discounted_price(
float(stars_price), discount_pct
)
discounted_stars_price = math.ceil(discounted_stars_price)
stars_price = discounted_stars_price
if not discount_text:
discount_amt = original_stars_price - discounted_stars_price
discount_text = get_text(
"active_discount_notice",
code=promo_code,
discount_pct=discount_pct,
original_price=original_stars_price,
discounted_price=discounted_stars_price,
discount_amount=discount_amt,
currency_symbol="",
)
if price_rub is None: if price_rub is None:
if traffic_mode and not price_source and stars_price is not None: if traffic_mode and not price_source and stars_price is not None:
+1 -1
View File
@@ -79,7 +79,7 @@
"discount_promo_code_applied_success": "✅ Promo code <code>{code}</code> activated!\n\n💰 A {discount}% discount will be applied to your next purchase.\n\nSelect a plan for payment.", "discount_promo_code_applied_success": "✅ Promo code <code>{code}</code> activated!\n\n💰 A {discount}% discount will be applied to your next purchase.\n\nSelect a plan for payment.",
"discount_promo_already_active": "❌ You already have an active discount promo code (<code>{code}</code>, -{discount_pct}%). Use it first or wait until the payment is complete.", "discount_promo_already_active": "❌ You already have an active discount promo code (<code>{code}</code>, -{discount_pct}%). Use it first or wait until the payment is complete.",
"promo_code_not_found_or_not_discount": "❌ Promo code <code>{code}</code> not found or is invalid.", "promo_code_not_found_or_not_discount": "❌ Promo code <code>{code}</code> not found or is invalid.",
"active_discount_notice": "🎁 Active discount: <code>{code}</code> (-{discount_pct}%)\n💵 Price: <s>{original_price}</s> ➔ <b>{discounted_price}</b>\n💰 Savings: {discount_amount}", "active_discount_notice": "🎁 Active discount: <code>{code}</code> (-{discount_pct}%)\n💵 Price: <s>{original_price}{currency_symbol}</s> ➔ <b>{discounted_price}{currency_symbol}</b>\n💰 Savings: {discount_amount}{currency_symbol}",
"error_applying_promo_bonus": "Failed to apply promo bonus. Please try again later or contact support.", "error_applying_promo_bonus": "Failed to apply promo bonus. Please try again later or contact support.",
"promo_input_cancelled_short": "Promo code entry cancelled.", "promo_input_cancelled_short": "Promo code entry cancelled.",
"trial_feature_disabled": "Free trial is currently unavailable.", "trial_feature_disabled": "Free trial is currently unavailable.",
+1 -1
View File
@@ -79,7 +79,7 @@
"discount_promo_code_applied_success": "✅ Промокод <code>{code}</code> активирован!\n\n💰 Скидка {discount}% будет применена к вашей следующей покупке.\n\nВыберите тариф для оплаты.", "discount_promo_code_applied_success": "✅ Промокод <code>{code}</code> активирован!\n\n💰 Скидка {discount}% будет применена к вашей следующей покупке.\n\nВыберите тариф для оплаты.",
"discount_promo_already_active": "❌ У вас уже есть активированный промокод на скидку (<code>{code}</code>, -{discount_pct}%). Используйте его сначала или дождитесь окончания платежа.", "discount_promo_already_active": "❌ У вас уже есть активированный промокод на скидку (<code>{code}</code>, -{discount_pct}%). Используйте его сначала или дождитесь окончания платежа.",
"promo_code_not_found_or_not_discount": "❌ Промокод <code>{code}</code> не найден или недействителен.", "promo_code_not_found_or_not_discount": "❌ Промокод <code>{code}</code> не найден или недействителен.",
"active_discount_notice": "🎁 Активна скидка: <code>{code}</code> (-{discount_pct}%)\n💵 Цена: <s>{original_price}</s> ➔ <b>{discounted_price}</b>\n💰 Экономия: {discount_amount}", "active_discount_notice": "🎁 Активна скидка: <code>{code}</code> (-{discount_pct}%)\n💵 Цена: <s>{original_price}{currency_symbol}</s> ➔ <b>{discounted_price}{currency_symbol}</b>\n💰 Экономия: {discount_amount}{currency_symbol}",
"error_applying_promo_bonus": "Не удалось применить бонус по промокоду. Пожалуйста, попробуйте позже или свяжитесь с поддержкой.", "error_applying_promo_bonus": "Не удалось применить бонус по промокоду. Пожалуйста, попробуйте позже или свяжитесь с поддержкой.",
"error_applying_promo_discount": "❌ Ошибка при активации промокода. Попробуйте позже.", "error_applying_promo_discount": "❌ Ошибка при активации промокода. Попробуйте позже.",
"promo_input_cancelled_short": "Ввод промокода отменен.", "promo_input_cancelled_short": "Ввод промокода отменен.",