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:
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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": "Ввод промокода отменен.",
|
||||||
|
|||||||
Reference in New Issue
Block a user