mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-04-29 01:00:03 +00:00
Fix addon discount calculations and server availability
This commit is contained in:
@@ -62,6 +62,7 @@ from app.utils.pricing_utils import (
|
||||
calculate_prorated_price,
|
||||
validate_pricing_calculation,
|
||||
format_period_description,
|
||||
apply_percentage_discount,
|
||||
)
|
||||
from app.utils.pagination import paginate_list
|
||||
from app.utils.subscription_utils import (
|
||||
@@ -74,13 +75,59 @@ logger = logging.getLogger(__name__)
|
||||
TRAFFIC_PRICES = get_traffic_prices()
|
||||
|
||||
|
||||
def _get_addon_discount_percent_for_user(
|
||||
user: Optional[User],
|
||||
category: str,
|
||||
period_days_hint: Optional[int] = None,
|
||||
) -> int:
|
||||
if user is None:
|
||||
return 0
|
||||
|
||||
promo_group = getattr(user, "promo_group", None)
|
||||
if promo_group is None:
|
||||
return 0
|
||||
|
||||
if not getattr(promo_group, "apply_discounts_to_addons", True):
|
||||
return 0
|
||||
|
||||
try:
|
||||
return user.get_promo_discount(category, period_days_hint)
|
||||
except AttributeError:
|
||||
return 0
|
||||
|
||||
|
||||
def _apply_addon_discount(
|
||||
user: Optional[User],
|
||||
category: str,
|
||||
amount: int,
|
||||
period_days_hint: Optional[int] = None,
|
||||
) -> Dict[str, int]:
|
||||
percent = _get_addon_discount_percent_for_user(user, category, period_days_hint)
|
||||
discounted_amount, discount_value = apply_percentage_discount(amount, percent)
|
||||
|
||||
return {
|
||||
"discounted": discounted_amount,
|
||||
"discount": discount_value,
|
||||
"percent": percent,
|
||||
}
|
||||
|
||||
|
||||
def _get_period_hint_from_subscription(subscription: Optional[Subscription]) -> Optional[int]:
|
||||
if not subscription:
|
||||
return None
|
||||
|
||||
months_remaining = get_remaining_months(subscription.end_date)
|
||||
if months_remaining <= 0:
|
||||
return None
|
||||
|
||||
return months_remaining * 30
|
||||
|
||||
|
||||
def _apply_discount_to_monthly_component(
|
||||
amount_per_month: int,
|
||||
percent: int,
|
||||
months: int,
|
||||
) -> Dict[str, int]:
|
||||
from app.utils.pricing_utils import apply_percentage_discount
|
||||
|
||||
discounted_per_month, discount_per_month = apply_percentage_discount(amount_per_month, percent)
|
||||
|
||||
return {
|
||||
@@ -1110,13 +1157,20 @@ async def handle_add_countries(
|
||||
|
||||
texts = get_texts(db_user.language)
|
||||
subscription = db_user.subscription
|
||||
|
||||
|
||||
if not subscription or subscription.is_trial:
|
||||
await callback.answer("⚠ Эта функция доступна только для платных подписок", show_alert=True)
|
||||
return
|
||||
|
||||
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
current_countries = subscription.connected_squads
|
||||
|
||||
period_hint_days = _get_period_hint_from_subscription(subscription)
|
||||
servers_discount_percent = _get_addon_discount_percent_for_user(
|
||||
db_user,
|
||||
"servers",
|
||||
period_hint_days,
|
||||
)
|
||||
|
||||
current_countries_names = []
|
||||
for country in countries:
|
||||
@@ -1142,11 +1196,12 @@ async def handle_add_countries(
|
||||
await callback.message.edit_text(
|
||||
text,
|
||||
reply_markup=get_manage_countries_keyboard(
|
||||
countries,
|
||||
current_countries.copy(),
|
||||
current_countries,
|
||||
countries,
|
||||
current_countries.copy(),
|
||||
current_countries,
|
||||
db_user.language,
|
||||
subscription.end_date
|
||||
subscription.end_date,
|
||||
servers_discount_percent,
|
||||
),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
@@ -1228,14 +1283,22 @@ async def handle_manage_country(
|
||||
|
||||
await state.update_data(countries=current_selected)
|
||||
|
||||
period_hint_days = _get_period_hint_from_subscription(subscription)
|
||||
servers_discount_percent = _get_addon_discount_percent_for_user(
|
||||
db_user,
|
||||
"servers",
|
||||
period_hint_days,
|
||||
)
|
||||
|
||||
try:
|
||||
await callback.message.edit_reply_markup(
|
||||
reply_markup=get_manage_countries_keyboard(
|
||||
countries,
|
||||
current_selected,
|
||||
subscription.connected_squads,
|
||||
current_selected,
|
||||
subscription.connected_squads,
|
||||
db_user.language,
|
||||
subscription.end_date
|
||||
subscription.end_date,
|
||||
servers_discount_percent,
|
||||
)
|
||||
)
|
||||
logger.info(f"✅ Клавиатура обновлена")
|
||||
@@ -1288,30 +1351,62 @@ async def apply_countries_changes(
|
||||
logger.info(f"🔧 Добавлено: {added}, Удалено: {removed}")
|
||||
|
||||
months_to_pay = get_remaining_months(subscription.end_date)
|
||||
|
||||
|
||||
period_hint_days = months_to_pay * 30 if months_to_pay > 0 else None
|
||||
servers_discount_percent = _get_addon_discount_percent_for_user(
|
||||
db_user,
|
||||
"servers",
|
||||
period_hint_days,
|
||||
)
|
||||
|
||||
cost_per_month = 0
|
||||
added_names = []
|
||||
removed_names = []
|
||||
|
||||
added_server_prices = []
|
||||
|
||||
|
||||
added_server_components: List[Dict[str, int]] = []
|
||||
|
||||
for country in countries:
|
||||
if not country.get('is_available', True):
|
||||
continue
|
||||
|
||||
if country['uuid'] in added:
|
||||
server_price_per_month = country['price_kopeks']
|
||||
cost_per_month += server_price_per_month
|
||||
discounted_per_month, discount_per_month = apply_percentage_discount(
|
||||
server_price_per_month,
|
||||
servers_discount_percent,
|
||||
)
|
||||
cost_per_month += discounted_per_month
|
||||
added_names.append(country['name'])
|
||||
added_server_components.append(
|
||||
{
|
||||
"discounted_per_month": discounted_per_month,
|
||||
"discount_per_month": discount_per_month,
|
||||
"original_per_month": server_price_per_month,
|
||||
}
|
||||
)
|
||||
if country['uuid'] in removed:
|
||||
removed_names.append(country['name'])
|
||||
|
||||
|
||||
total_cost, charged_months = calculate_prorated_price(cost_per_month, subscription.end_date)
|
||||
|
||||
for country in countries:
|
||||
if country['uuid'] in added:
|
||||
server_price_per_month = country['price_kopeks']
|
||||
server_total_price = server_price_per_month * charged_months
|
||||
added_server_prices.append(server_total_price)
|
||||
|
||||
logger.info(f"Стоимость новых серверов: {cost_per_month/100}₽/мес × {charged_months} мес = {total_cost/100}₽")
|
||||
|
||||
added_server_prices = [
|
||||
component["discounted_per_month"] * charged_months
|
||||
for component in added_server_components
|
||||
]
|
||||
|
||||
total_discount = sum(
|
||||
component["discount_per_month"] * charged_months
|
||||
for component in added_server_components
|
||||
)
|
||||
|
||||
if added_names:
|
||||
logger.info(
|
||||
"Стоимость новых серверов: %.2f₽/мес × %s мес = %.2f₽ (скидка %.2f₽)",
|
||||
cost_per_month / 100,
|
||||
charged_months,
|
||||
total_cost / 100,
|
||||
total_discount / 100,
|
||||
)
|
||||
|
||||
if total_cost > 0 and db_user.balance_kopeks < total_cost:
|
||||
missing_kopeks = total_cost - db_user.balance_kopeks
|
||||
@@ -1398,6 +1493,11 @@ async def apply_countries_changes(
|
||||
success_text += "\n".join(f"• {name}" for name in added_names)
|
||||
if total_cost > 0:
|
||||
success_text += f"\n💰 Списано: {texts.format_price(total_cost)} (за {charged_months} мес)"
|
||||
if total_discount > 0:
|
||||
success_text += (
|
||||
f" (скидка {servers_discount_percent}%:"
|
||||
f" -{texts.format_price(total_discount)})"
|
||||
)
|
||||
success_text += "\n"
|
||||
|
||||
if removed_names:
|
||||
@@ -1449,12 +1549,22 @@ async def handle_add_traffic(
|
||||
return
|
||||
|
||||
current_traffic = subscription.traffic_limit_gb
|
||||
|
||||
period_hint_days = _get_period_hint_from_subscription(subscription)
|
||||
traffic_discount_percent = _get_addon_discount_percent_for_user(
|
||||
db_user,
|
||||
"traffic",
|
||||
period_hint_days,
|
||||
)
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"📈 <b>Добавить трафик к подписке</b>\n\n"
|
||||
f"Текущий лимит: {texts.format_traffic(current_traffic)}\n"
|
||||
f"Выберите дополнительный трафик:",
|
||||
reply_markup=get_add_traffic_keyboard(db_user.language, subscription.end_date),
|
||||
reply_markup=get_add_traffic_keyboard(
|
||||
db_user.language,
|
||||
subscription.end_date,
|
||||
traffic_discount_percent,
|
||||
),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
@@ -1474,7 +1584,14 @@ async def handle_change_devices(
|
||||
return
|
||||
|
||||
current_devices = subscription.device_limit
|
||||
|
||||
|
||||
period_hint_days = _get_period_hint_from_subscription(subscription)
|
||||
devices_discount_percent = _get_addon_discount_percent_for_user(
|
||||
db_user,
|
||||
"devices",
|
||||
period_hint_days,
|
||||
)
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"📱 <b>Изменение количества устройств</b>\n\n"
|
||||
f"Текущий лимит: {current_devices} устройств\n"
|
||||
@@ -1482,7 +1599,12 @@ async def handle_change_devices(
|
||||
f"💡 <b>Важно:</b>\n"
|
||||
f"• При увеличении - доплата пропорционально оставшемуся времени\n"
|
||||
f"• При уменьшении - возврат средств не производится",
|
||||
reply_markup=get_change_devices_keyboard(current_devices, db_user.language, subscription.end_date),
|
||||
reply_markup=get_change_devices_keyboard(
|
||||
current_devices,
|
||||
db_user.language,
|
||||
subscription.end_date,
|
||||
devices_discount_percent,
|
||||
),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
@@ -1524,7 +1646,22 @@ async def confirm_change_devices(
|
||||
chargeable_devices = additional_devices
|
||||
|
||||
devices_price_per_month = chargeable_devices * settings.PRICE_PER_DEVICE
|
||||
price, charged_months = calculate_prorated_price(devices_price_per_month, subscription.end_date)
|
||||
months_hint = get_remaining_months(subscription.end_date)
|
||||
period_hint_days = months_hint * 30 if months_hint > 0 else None
|
||||
devices_discount_percent = _get_addon_discount_percent_for_user(
|
||||
db_user,
|
||||
"devices",
|
||||
period_hint_days,
|
||||
)
|
||||
discounted_per_month, discount_per_month = apply_percentage_discount(
|
||||
devices_price_per_month,
|
||||
devices_discount_percent,
|
||||
)
|
||||
price, charged_months = calculate_prorated_price(
|
||||
discounted_per_month,
|
||||
subscription.end_date,
|
||||
)
|
||||
total_discount = discount_per_month * charged_months
|
||||
|
||||
if price > 0 and db_user.balance_kopeks < price:
|
||||
missing_kopeks = price - db_user.balance_kopeks
|
||||
@@ -1556,7 +1693,15 @@ async def confirm_change_devices(
|
||||
return
|
||||
|
||||
action_text = f"увеличить до {new_devices_count}"
|
||||
cost_text = f"Доплата: {texts.format_price(price)} (за {charged_months} мес)" if price > 0 else "Бесплатно"
|
||||
if price > 0:
|
||||
cost_text = f"Доплата: {texts.format_price(price)} (за {charged_months} мес)"
|
||||
if total_discount > 0:
|
||||
cost_text += (
|
||||
f" (скидка {devices_discount_percent}%:"
|
||||
f" -{texts.format_price(total_discount)})"
|
||||
)
|
||||
else:
|
||||
cost_text = "Бесплатно"
|
||||
|
||||
else:
|
||||
price = 0
|
||||
@@ -2139,9 +2284,31 @@ async def confirm_add_devices(
|
||||
return
|
||||
|
||||
devices_price_per_month = devices_count * settings.PRICE_PER_DEVICE
|
||||
price, charged_months = calculate_prorated_price(devices_price_per_month, subscription.end_date)
|
||||
|
||||
logger.info(f"Добавление {devices_count} устройств: {devices_price_per_month/100}₽/мес × {charged_months} мес = {price/100}₽")
|
||||
months_hint = get_remaining_months(subscription.end_date)
|
||||
period_hint_days = months_hint * 30 if months_hint > 0 else None
|
||||
devices_discount_percent = _get_addon_discount_percent_for_user(
|
||||
db_user,
|
||||
"devices",
|
||||
period_hint_days,
|
||||
)
|
||||
discounted_per_month, discount_per_month = apply_percentage_discount(
|
||||
devices_price_per_month,
|
||||
devices_discount_percent,
|
||||
)
|
||||
price, charged_months = calculate_prorated_price(
|
||||
discounted_per_month,
|
||||
subscription.end_date,
|
||||
)
|
||||
total_discount = discount_per_month * charged_months
|
||||
|
||||
logger.info(
|
||||
"Добавление %s устройств: %.2f₽/мес × %s мес = %.2f₽ (скидка %.2f₽)",
|
||||
devices_count,
|
||||
discounted_per_month / 100,
|
||||
charged_months,
|
||||
price / 100,
|
||||
total_discount / 100,
|
||||
)
|
||||
|
||||
if db_user.balance_kopeks < price:
|
||||
missing_kopeks = price - db_user.balance_kopeks
|
||||
@@ -2200,11 +2367,20 @@ async def confirm_add_devices(
|
||||
await db.refresh(db_user)
|
||||
await db.refresh(subscription)
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"✅ Устройства успешно добавлены!\n\n"
|
||||
success_text = (
|
||||
"✅ Устройства успешно добавлены!\n\n"
|
||||
f"📱 Добавлено: {devices_count} устройств\n"
|
||||
f"Новый лимит: {subscription.device_limit} устройств\n"
|
||||
f"💰 Списано: {texts.format_price(price)} (за {charged_months} мес)",
|
||||
)
|
||||
success_text += f"💰 Списано: {texts.format_price(price)} (за {charged_months} мес)"
|
||||
if total_discount > 0:
|
||||
success_text += (
|
||||
f" (скидка {devices_discount_percent}%:"
|
||||
f" -{texts.format_price(total_discount)})"
|
||||
)
|
||||
|
||||
await callback.message.edit_text(
|
||||
success_text,
|
||||
reply_markup=get_back_keyboard(db_user.language)
|
||||
)
|
||||
|
||||
@@ -3501,12 +3677,34 @@ async def add_traffic(
|
||||
texts = get_texts(db_user.language)
|
||||
subscription = db_user.subscription
|
||||
|
||||
price = settings.get_traffic_price(traffic_gb)
|
||||
|
||||
if price == 0 and traffic_gb != 0:
|
||||
base_price = settings.get_traffic_price(traffic_gb)
|
||||
|
||||
if base_price == 0 and traffic_gb != 0:
|
||||
await callback.answer("⚠️ Цена для этого пакета не настроена", show_alert=True)
|
||||
return
|
||||
|
||||
|
||||
period_hint_days = _get_period_hint_from_subscription(subscription)
|
||||
discount_result = _apply_addon_discount(
|
||||
db_user,
|
||||
"traffic",
|
||||
base_price,
|
||||
period_hint_days,
|
||||
)
|
||||
|
||||
discounted_per_month = discount_result["discounted"]
|
||||
discount_per_month = discount_result["discount"]
|
||||
charged_months = 1
|
||||
|
||||
if subscription:
|
||||
price, charged_months = calculate_prorated_price(
|
||||
discounted_per_month,
|
||||
subscription.end_date,
|
||||
)
|
||||
else:
|
||||
price = discounted_per_month
|
||||
|
||||
total_discount_value = discount_per_month * charged_months
|
||||
|
||||
if db_user.balance_kopeks < price:
|
||||
missing_kopeks = price - db_user.balance_kopeks
|
||||
message_text = texts.t(
|
||||
@@ -3537,8 +3735,10 @@ async def add_traffic(
|
||||
|
||||
try:
|
||||
success = await subtract_user_balance(
|
||||
db, db_user, price,
|
||||
f"Добавление {traffic_gb} ГБ трафика"
|
||||
db,
|
||||
db_user,
|
||||
price,
|
||||
f"Добавление {traffic_gb} ГБ трафика",
|
||||
)
|
||||
|
||||
if not success:
|
||||
@@ -3558,7 +3758,7 @@ async def add_traffic(
|
||||
user_id=db_user.id,
|
||||
type=TransactionType.SUBSCRIPTION_PAYMENT,
|
||||
amount_kopeks=price,
|
||||
description=f"Добавление {traffic_gb} ГБ трафика"
|
||||
description=f"Добавление {traffic_gb} ГБ трафика",
|
||||
)
|
||||
|
||||
|
||||
@@ -3571,7 +3771,15 @@ async def add_traffic(
|
||||
else:
|
||||
success_text += f"📈 Добавлено: {traffic_gb} ГБ\n"
|
||||
success_text += f"Новый лимит: {texts.format_traffic(subscription.traffic_limit_gb)}"
|
||||
|
||||
|
||||
if price > 0:
|
||||
success_text += f"\n💰 Списано: {texts.format_price(price)}"
|
||||
if total_discount_value > 0:
|
||||
success_text += (
|
||||
f" (скидка {discount_result['percent']}%:"
|
||||
f" -{texts.format_price(total_discount_value)})"
|
||||
)
|
||||
|
||||
await callback.message.edit_text(
|
||||
success_text,
|
||||
reply_markup=get_back_keyboard(db_user.language)
|
||||
@@ -3830,7 +4038,15 @@ async def _get_available_countries(promo_group_id: Optional[int] = None):
|
||||
available_servers = await get_available_server_squads(
|
||||
db, promo_group_id=promo_group_id
|
||||
)
|
||||
|
||||
|
||||
if promo_group_id is not None and not available_servers:
|
||||
logger.info(
|
||||
"Промогруппа %s не имеет доступных серверов, возврат пустого списка",
|
||||
promo_group_id,
|
||||
)
|
||||
await cache.set(cache_key_value, [], 60)
|
||||
return []
|
||||
|
||||
countries = []
|
||||
for server in available_servers:
|
||||
countries.append({
|
||||
@@ -3926,21 +4142,50 @@ async def handle_add_country_to_subscription(
|
||||
logger.info(f"🔍 Добавлена страна: {country_uuid}")
|
||||
|
||||
total_price = 0
|
||||
subscription = db_user.subscription
|
||||
period_hint_days = _get_period_hint_from_subscription(subscription)
|
||||
servers_discount_percent = _get_addon_discount_percent_for_user(
|
||||
db_user,
|
||||
"servers",
|
||||
period_hint_days,
|
||||
)
|
||||
|
||||
for country in countries:
|
||||
if country['uuid'] in selected_countries and country['uuid'] not in db_user.subscription.connected_squads:
|
||||
total_price += country['price_kopeks']
|
||||
|
||||
if not country.get('is_available', True):
|
||||
continue
|
||||
|
||||
if (
|
||||
country['uuid'] in selected_countries
|
||||
and country['uuid'] not in subscription.connected_squads
|
||||
):
|
||||
server_price = country['price_kopeks']
|
||||
if servers_discount_percent > 0 and server_price > 0:
|
||||
discounted_price, _ = apply_percentage_discount(
|
||||
server_price,
|
||||
servers_discount_percent,
|
||||
)
|
||||
else:
|
||||
discounted_price = server_price
|
||||
total_price += discounted_price
|
||||
|
||||
data['countries'] = selected_countries
|
||||
data['total_price'] = total_price
|
||||
await state.set_data(data)
|
||||
|
||||
|
||||
logger.info(f"🔍 Новые выбранные страны: {selected_countries}")
|
||||
logger.info(f"🔍 Общая стоимость: {total_price}")
|
||||
|
||||
try:
|
||||
from app.keyboards.inline import get_manage_countries_keyboard
|
||||
await callback.message.edit_reply_markup(
|
||||
reply_markup=get_manage_countries_keyboard(countries, selected_countries, db_user.subscription.connected_squads, db_user.language)
|
||||
reply_markup=get_manage_countries_keyboard(
|
||||
countries,
|
||||
selected_countries,
|
||||
subscription.connected_squads,
|
||||
db_user.language,
|
||||
subscription.end_date,
|
||||
servers_discount_percent,
|
||||
)
|
||||
)
|
||||
logger.info(f"✅ Клавиатура обновлена")
|
||||
except Exception as e:
|
||||
@@ -3992,10 +4237,37 @@ async def confirm_add_countries_to_subscription(
|
||||
total_price = 0
|
||||
new_countries_names = []
|
||||
removed_countries_names = []
|
||||
|
||||
|
||||
period_hint_days = _get_period_hint_from_subscription(subscription)
|
||||
servers_discount_percent = _get_addon_discount_percent_for_user(
|
||||
db_user,
|
||||
"servers",
|
||||
period_hint_days,
|
||||
)
|
||||
total_discount_value = 0
|
||||
|
||||
for country in countries:
|
||||
if not country.get('is_available', True):
|
||||
continue
|
||||
|
||||
if country['uuid'] in new_countries:
|
||||
total_price += country['price_kopeks']
|
||||
server_price = country['price_kopeks']
|
||||
if servers_discount_percent > 0 and server_price > 0:
|
||||
discounted_per_month, discount_per_month = apply_percentage_discount(
|
||||
server_price,
|
||||
servers_discount_percent,
|
||||
)
|
||||
else:
|
||||
discounted_per_month = server_price
|
||||
discount_per_month = 0
|
||||
|
||||
charged_price, charged_months = calculate_prorated_price(
|
||||
discounted_per_month,
|
||||
subscription.end_date,
|
||||
)
|
||||
|
||||
total_price += charged_price
|
||||
total_discount_value += discount_per_month * charged_months
|
||||
new_countries_names.append(country['name'])
|
||||
if country['uuid'] in removed_countries:
|
||||
removed_countries_names.append(country['name'])
|
||||
@@ -4051,7 +4323,7 @@ async def confirm_add_countries_to_subscription(
|
||||
subscription.connected_squads = selected_countries
|
||||
subscription.updated_at = datetime.utcnow()
|
||||
await db.commit()
|
||||
|
||||
|
||||
subscription_service = SubscriptionService()
|
||||
await subscription_service.update_remnawave_user(db, subscription)
|
||||
|
||||
@@ -4063,7 +4335,13 @@ async def confirm_add_countries_to_subscription(
|
||||
if new_countries_names:
|
||||
success_text += f"➕ Добавлены страны:\n{chr(10).join(f'• {name}' for name in new_countries_names)}\n"
|
||||
if total_price > 0:
|
||||
success_text += f"💰 Списано: {texts.format_price(total_price)}\n"
|
||||
success_text += f"💰 Списано: {texts.format_price(total_price)}"
|
||||
if total_discount_value > 0:
|
||||
success_text += (
|
||||
f" (скидка {servers_discount_percent}%:"
|
||||
f" -{texts.format_price(total_discount_value)})"
|
||||
)
|
||||
success_text += "\n"
|
||||
|
||||
if removed_countries_names:
|
||||
success_text += f"\n➖ Отключены страны:\n{chr(10).join(f'• {name}' for name in removed_countries_names)}\n"
|
||||
@@ -4880,7 +5158,13 @@ async def handle_switch_traffic(
|
||||
return
|
||||
|
||||
current_traffic = subscription.traffic_limit_gb
|
||||
|
||||
period_hint_days = _get_period_hint_from_subscription(subscription)
|
||||
traffic_discount_percent = _get_addon_discount_percent_for_user(
|
||||
db_user,
|
||||
"traffic",
|
||||
period_hint_days,
|
||||
)
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"🔄 <b>Переключение лимита трафика</b>\n\n"
|
||||
f"Текущий лимит: {texts.format_traffic(current_traffic)}\n"
|
||||
@@ -4888,7 +5172,12 @@ async def handle_switch_traffic(
|
||||
f"💡 <b>Важно:</b>\n"
|
||||
f"• При увеличении - доплата за разницу\n"
|
||||
f"• При уменьшении - возврат средств не производится",
|
||||
reply_markup=get_traffic_switch_keyboard(current_traffic, db_user.language, subscription.end_date),
|
||||
reply_markup=get_traffic_switch_keyboard(
|
||||
current_traffic,
|
||||
db_user.language,
|
||||
subscription.end_date,
|
||||
traffic_discount_percent,
|
||||
),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
@@ -4914,13 +5203,31 @@ async def confirm_switch_traffic(
|
||||
|
||||
old_price_per_month = settings.get_traffic_price(current_traffic)
|
||||
new_price_per_month = settings.get_traffic_price(new_traffic_gb)
|
||||
|
||||
|
||||
months_remaining = get_remaining_months(subscription.end_date)
|
||||
price_difference_per_month = new_price_per_month - old_price_per_month
|
||||
period_hint_days = months_remaining * 30 if months_remaining > 0 else None
|
||||
traffic_discount_percent = _get_addon_discount_percent_for_user(
|
||||
db_user,
|
||||
"traffic",
|
||||
period_hint_days,
|
||||
)
|
||||
|
||||
discounted_old_per_month, _ = apply_percentage_discount(
|
||||
old_price_per_month,
|
||||
traffic_discount_percent,
|
||||
)
|
||||
discounted_new_per_month, _ = apply_percentage_discount(
|
||||
new_price_per_month,
|
||||
traffic_discount_percent,
|
||||
)
|
||||
price_difference_per_month = discounted_new_per_month - discounted_old_per_month
|
||||
discount_savings_per_month = (
|
||||
(new_price_per_month - old_price_per_month) - price_difference_per_month
|
||||
)
|
||||
|
||||
if price_difference_per_month > 0:
|
||||
total_price_difference = price_difference_per_month * months_remaining
|
||||
|
||||
|
||||
if db_user.balance_kopeks < total_price_difference:
|
||||
missing_kopeks = total_price_difference - db_user.balance_kopeks
|
||||
message_text = texts.t(
|
||||
@@ -4951,6 +5258,12 @@ async def confirm_switch_traffic(
|
||||
|
||||
action_text = f"увеличить до {texts.format_traffic(new_traffic_gb)}"
|
||||
cost_text = f"Доплата: {texts.format_price(total_price_difference)} (за {months_remaining} мес)"
|
||||
if discount_savings_per_month > 0:
|
||||
total_discount_savings = discount_savings_per_month * months_remaining
|
||||
cost_text += (
|
||||
f" (скидка {traffic_discount_percent}%:"
|
||||
f" -{texts.format_price(total_discount_savings)})"
|
||||
)
|
||||
else:
|
||||
total_price_difference = 0
|
||||
action_text = f"уменьшить до {texts.format_traffic(new_traffic_gb)}"
|
||||
@@ -5070,9 +5383,10 @@ async def execute_switch_traffic(
|
||||
|
||||
|
||||
def get_traffic_switch_keyboard(
|
||||
current_traffic_gb: int,
|
||||
language: str = "ru",
|
||||
subscription_end_date: datetime = None
|
||||
current_traffic_gb: int,
|
||||
language: str = "ru",
|
||||
subscription_end_date: datetime = None,
|
||||
discount_percent: int = 0,
|
||||
) -> InlineKeyboardMarkup:
|
||||
from app.utils.pricing_utils import get_remaining_months
|
||||
from app.config import settings
|
||||
@@ -5088,16 +5402,24 @@ def get_traffic_switch_keyboard(
|
||||
enabled_packages = [pkg for pkg in packages if pkg['enabled']]
|
||||
|
||||
current_price_per_month = settings.get_traffic_price(current_traffic_gb)
|
||||
discounted_current_per_month, _ = apply_percentage_discount(
|
||||
current_price_per_month,
|
||||
discount_percent,
|
||||
)
|
||||
|
||||
buttons = []
|
||||
|
||||
for package in enabled_packages:
|
||||
gb = package['gb']
|
||||
price_per_month = package['price']
|
||||
|
||||
price_diff_per_month = price_per_month - current_price_per_month
|
||||
discounted_price_per_month, _ = apply_percentage_discount(
|
||||
price_per_month,
|
||||
discount_percent,
|
||||
)
|
||||
|
||||
price_diff_per_month = discounted_price_per_month - discounted_current_per_month
|
||||
total_price_diff = price_diff_per_month * months_multiplier
|
||||
|
||||
|
||||
if gb == current_traffic_gb:
|
||||
emoji = "✅"
|
||||
action_text = " (текущий)"
|
||||
@@ -5106,6 +5428,13 @@ def get_traffic_switch_keyboard(
|
||||
emoji = "⬆️"
|
||||
action_text = ""
|
||||
price_text = f" (+{total_price_diff//100}₽{period_text})"
|
||||
if discount_percent > 0:
|
||||
discount_total = (
|
||||
(price_per_month - current_price_per_month) * months_multiplier
|
||||
- total_price_diff
|
||||
)
|
||||
if discount_total > 0:
|
||||
price_text += f" (скидка {discount_percent}%: -{discount_total//100}₽)"
|
||||
elif total_price_diff < 0:
|
||||
emoji = "⬇️"
|
||||
action_text = ""
|
||||
|
||||
@@ -8,7 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.config import settings, PERIOD_PRICES, TRAFFIC_PRICES
|
||||
from app.localization.loader import DEFAULT_LANGUAGE
|
||||
from app.localization.texts import get_texts
|
||||
from app.utils.pricing_utils import format_period_description
|
||||
from app.utils.pricing_utils import format_period_description, apply_percentage_discount
|
||||
from app.utils.subscription_utils import (
|
||||
get_display_subscription_link,
|
||||
get_happ_cryptolink_redirect_link,
|
||||
@@ -1123,7 +1123,11 @@ def get_extend_subscription_keyboard(language: str = DEFAULT_LANGUAGE) -> Inline
|
||||
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
||||
|
||||
|
||||
def get_add_traffic_keyboard(language: str = DEFAULT_LANGUAGE, subscription_end_date: datetime = None) -> InlineKeyboardMarkup:
|
||||
def get_add_traffic_keyboard(
|
||||
language: str = DEFAULT_LANGUAGE,
|
||||
subscription_end_date: datetime = None,
|
||||
discount_percent: int = 0,
|
||||
) -> InlineKeyboardMarkup:
|
||||
from app.utils.pricing_utils import get_remaining_months
|
||||
from app.config import settings
|
||||
texts = get_texts(language)
|
||||
@@ -1155,8 +1159,13 @@ def get_add_traffic_keyboard(language: str = DEFAULT_LANGUAGE, subscription_end_
|
||||
for package in enabled_packages:
|
||||
gb = package['gb']
|
||||
price_per_month = package['price']
|
||||
total_price = price_per_month * months_multiplier
|
||||
|
||||
discounted_per_month, discount_per_month = apply_percentage_discount(
|
||||
price_per_month,
|
||||
discount_percent,
|
||||
)
|
||||
total_price = discounted_per_month * months_multiplier
|
||||
total_discount = discount_per_month * months_multiplier
|
||||
|
||||
if gb == 0:
|
||||
if language == "ru":
|
||||
text = f"♾️ Безлимитный трафик - {total_price//100} ₽{period_text}"
|
||||
@@ -1167,7 +1176,10 @@ def get_add_traffic_keyboard(language: str = DEFAULT_LANGUAGE, subscription_end_
|
||||
text = f"📊 +{gb} ГБ трафика - {total_price//100} ₽{period_text}"
|
||||
else:
|
||||
text = f"📊 +{gb} GB traffic - {total_price//100} ₽{period_text}"
|
||||
|
||||
|
||||
if discount_percent > 0 and total_discount > 0:
|
||||
text += f" (скидка {discount_percent}%: -{total_discount//100}₽)"
|
||||
|
||||
buttons.append([
|
||||
InlineKeyboardButton(text=text, callback_data=f"add_traffic_{gb}")
|
||||
])
|
||||
@@ -1181,7 +1193,12 @@ def get_add_traffic_keyboard(language: str = DEFAULT_LANGUAGE, subscription_end_
|
||||
|
||||
return InlineKeyboardMarkup(inline_keyboard=buttons)
|
||||
|
||||
def get_change_devices_keyboard(current_devices: int, language: str = DEFAULT_LANGUAGE, subscription_end_date: datetime = None) -> InlineKeyboardMarkup:
|
||||
def get_change_devices_keyboard(
|
||||
current_devices: int,
|
||||
language: str = DEFAULT_LANGUAGE,
|
||||
subscription_end_date: datetime = None,
|
||||
discount_percent: int = 0,
|
||||
) -> InlineKeyboardMarkup:
|
||||
from app.utils.pricing_utils import get_remaining_months
|
||||
from app.config import settings
|
||||
texts = get_texts(language)
|
||||
@@ -1218,8 +1235,17 @@ def get_change_devices_keyboard(current_devices: int, language: str = DEFAULT_LA
|
||||
|
||||
if chargeable_devices > 0:
|
||||
price_per_month = chargeable_devices * device_price_per_month
|
||||
total_price = price_per_month * months_multiplier
|
||||
discounted_per_month, discount_per_month = apply_percentage_discount(
|
||||
price_per_month,
|
||||
discount_percent,
|
||||
)
|
||||
total_price = discounted_per_month * months_multiplier
|
||||
price_text = f" (+{total_price//100}₽{period_text})"
|
||||
if discount_percent > 0 and discount_per_month * months_multiplier > 0:
|
||||
price_text += (
|
||||
f" (скидка {discount_percent}%:"
|
||||
f" -{(discount_per_month * months_multiplier)//100}₽)"
|
||||
)
|
||||
action_text = ""
|
||||
else:
|
||||
price_text = " (бесплатно)"
|
||||
@@ -1296,7 +1322,8 @@ def get_manage_countries_keyboard(
|
||||
selected: List[str],
|
||||
current_subscription_countries: List[str],
|
||||
language: str = DEFAULT_LANGUAGE,
|
||||
subscription_end_date: datetime = None
|
||||
subscription_end_date: datetime = None,
|
||||
discount_percent: int = 0,
|
||||
) -> InlineKeyboardMarkup:
|
||||
from app.utils.pricing_utils import get_remaining_months
|
||||
|
||||
@@ -1311,10 +1338,18 @@ def get_manage_countries_keyboard(
|
||||
total_cost = 0
|
||||
|
||||
for country in countries:
|
||||
if not country.get('is_available', True):
|
||||
continue
|
||||
|
||||
uuid = country['uuid']
|
||||
name = country['name']
|
||||
price_per_month = country['price_kopeks']
|
||||
|
||||
|
||||
discounted_per_month, discount_per_month = apply_percentage_discount(
|
||||
price_per_month,
|
||||
discount_percent,
|
||||
)
|
||||
|
||||
if uuid in current_subscription_countries:
|
||||
if uuid in selected:
|
||||
icon = "✅"
|
||||
@@ -1323,17 +1358,31 @@ def get_manage_countries_keyboard(
|
||||
else:
|
||||
if uuid in selected:
|
||||
icon = "➕"
|
||||
total_cost += price_per_month * months_multiplier
|
||||
total_cost += discounted_per_month * months_multiplier
|
||||
else:
|
||||
icon = "⚪"
|
||||
|
||||
|
||||
if uuid not in current_subscription_countries and uuid in selected:
|
||||
total_price = price_per_month * months_multiplier
|
||||
total_price = discounted_per_month * months_multiplier
|
||||
if months_multiplier > 1:
|
||||
price_text = f" ({price_per_month//100}₽/мес × {months_multiplier} = {total_price//100}₽)"
|
||||
logger.info(f"🔍 Сервер {name}: {price_per_month/100}₽/мес × {months_multiplier} мес = {total_price/100}₽")
|
||||
price_text = (
|
||||
f" ({discounted_per_month//100}₽/мес × {months_multiplier} = {total_price//100}₽)"
|
||||
)
|
||||
logger.info(
|
||||
"🔍 Сервер %s: %.2f₽/мес × %s мес = %.2f₽ (скидка %.2f₽)",
|
||||
name,
|
||||
discounted_per_month / 100,
|
||||
months_multiplier,
|
||||
total_price / 100,
|
||||
(discount_per_month * months_multiplier) / 100,
|
||||
)
|
||||
else:
|
||||
price_text = f" ({total_price//100}₽)"
|
||||
if discount_percent > 0 and discount_per_month * months_multiplier > 0:
|
||||
price_text += (
|
||||
f" (скидка {discount_percent}%:"
|
||||
f" -{(discount_per_month * months_multiplier)//100}₽)"
|
||||
)
|
||||
display_name = f"{icon} {name}{price_text}"
|
||||
else:
|
||||
display_name = f"{icon} {name}"
|
||||
|
||||
Reference in New Issue
Block a user