fix: renewal cost estimate double-counts servers and traffic in tariff mode

In tariff mode, period_prices already includes servers and traffic costs.
But show_payment_methods() and get_subscription_cost() were using the classic
additive formula, adding server and traffic prices on top of the tariff price.

Example: 49₽ tariff + 150₽ server + 150₽ traffic = 349₽ shown, should be 49₽.

Now both functions detect tariff mode and only add extra device costs beyond
the tariff's device_limit. Classic mode formula unchanged.
This commit is contained in:
Fringg
2026-03-08 23:14:57 +03:00
parent f9f07f360c
commit bfbefeb1e2
2 changed files with 130 additions and 79 deletions
+64 -41
View File
@@ -439,11 +439,15 @@ async def show_payment_methods(callback: types.CallbackQuery, db_user: User, db:
from app.database.crud.tariff import get_tariff_by_id
# В режиме тарифов берём цену из тарифа пользователя
tariff = None
tariff_price_found = False
base_price_original = 0
if settings.is_tariffs_mode() and subscription.tariff_id:
tariff = await get_tariff_by_id(db, subscription.tariff_id)
if tariff and tariff.period_prices:
base_price_original = tariff.period_prices.get(str(duration_days), 0)
if base_price_original > 0:
tariff_price_found = True
# Если не нашли в тарифе - используем PERIOD_PRICES
if base_price_original <= 0:
@@ -455,52 +459,71 @@ async def show_payment_methods(callback: types.CallbackQuery, db_user: User, db:
period_discount_percent,
)
# Рассчитываем стоимость серверов
from app.services.subscription_service import SubscriptionService
if tariff_price_found:
# В тарифном режиме серверы и трафик уже включены в цену тарифа.
# Добавляем только стоимость доп. устройств сверх тарифного лимита.
tariff_device_limit = tariff.device_limit if tariff and tariff.device_limit else 0
extra_devices = max(0, (current_device_limit or 0) - tariff_device_limit)
if extra_devices > 0:
device_price = (
tariff.device_price_kopeks
if tariff and tariff.device_price_kopeks is not None
else settings.PRICE_PER_DEVICE
)
devices_discount_percent = db_user.get_promo_discount('devices', duration_days)
devices_cost, _ = apply_percentage_discount(
extra_devices * device_price,
devices_discount_percent,
)
else:
devices_cost = 0
subscription_service = SubscriptionService()
(
servers_price_per_month,
per_server_monthly_prices,
) = await subscription_service.get_countries_price_by_uuids(
current_connected_squads,
db,
promo_group_id=db_user.promo_group_id,
)
servers_discount_percent = db_user.get_promo_discount('servers', duration_days)
total_servers_price = 0
for server_price in per_server_monthly_prices:
discounted_per_month, discount_per_month = apply_percentage_discount(
server_price,
servers_discount_percent,
months_in_period = calculate_months_from_days(duration_days)
total_price = base_price + devices_cost * months_in_period
else:
# Классический режим: серверы + трафик + устройства считаются отдельно
from app.services.subscription_service import SubscriptionService
subscription_service = SubscriptionService()
(
servers_price_per_month,
per_server_monthly_prices,
) = await subscription_service.get_countries_price_by_uuids(
current_connected_squads,
db,
promo_group_id=db_user.promo_group_id,
)
total_servers_price += discounted_per_month
servers_discount_percent = db_user.get_promo_discount('servers', duration_days)
total_servers_price = 0
for server_price in per_server_monthly_prices:
discounted_per_month, discount_per_month = apply_percentage_discount(
server_price,
servers_discount_percent,
)
total_servers_price += discounted_per_month
# Рассчитываем стоимость трафика
traffic_price_per_month = settings.get_traffic_price(current_traffic)
traffic_discount_percent = db_user.get_promo_discount('traffic', duration_days)
traffic_discounted_per_month, traffic_discount_per_month = apply_percentage_discount(
traffic_price_per_month,
traffic_discount_percent,
)
traffic_price_per_month = settings.get_traffic_price(current_traffic)
traffic_discount_percent = db_user.get_promo_discount('traffic', duration_days)
traffic_discounted_per_month, traffic_discount_per_month = apply_percentage_discount(
traffic_price_per_month,
traffic_discount_percent,
)
# Рассчитываем стоимость устройств
additional_devices = max(0, (current_device_limit or 0) - settings.DEFAULT_DEVICE_LIMIT)
devices_price_per_month = additional_devices * settings.PRICE_PER_DEVICE
devices_discount_percent = db_user.get_promo_discount('devices', duration_days)
devices_discounted_per_month, devices_discount_per_month = apply_percentage_discount(
devices_price_per_month,
devices_discount_percent,
)
additional_devices = max(0, (current_device_limit or 0) - settings.DEFAULT_DEVICE_LIMIT)
devices_price_per_month = additional_devices * settings.PRICE_PER_DEVICE
devices_discount_percent = db_user.get_promo_discount('devices', duration_days)
devices_discounted_per_month, devices_discount_per_month = apply_percentage_discount(
devices_price_per_month,
devices_discount_percent,
)
# Общая стоимость
months_in_period = calculate_months_from_days(duration_days)
total_price = (
base_price
+ total_servers_price * months_in_period
+ traffic_discounted_per_month * months_in_period
+ devices_discounted_per_month * months_in_period
)
months_in_period = calculate_months_from_days(duration_days)
total_price = (
base_price
+ total_servers_price * months_in_period
+ traffic_discounted_per_month * months_in_period
+ devices_discounted_per_month * months_in_period
)
traffic_value = current_traffic or 0
if traffic_value <= 0:
+66 -38
View File
@@ -309,11 +309,11 @@ async def get_subscription_cost(subscription, db: AsyncSession) -> int:
return 0
from app.config import settings
from app.database.crud.tariff import get_tariff_by_id
from app.services.subscription_service import SubscriptionService
subscription_service = SubscriptionService()
base_cost_original = PERIOD_PRICES.get(30, 0)
try:
owner = subscription.user
except AttributeError:
@@ -328,58 +328,86 @@ async def get_subscription_cost(subscription, db: AsyncSession) -> int:
except AttributeError:
period_discount_percent = 0
# В тарифном режиме цена тарифа уже включает серверы и трафик
tariff = None
tariff_price_found = False
if settings.is_tariffs_mode() and subscription.tariff_id:
tariff = await get_tariff_by_id(db, subscription.tariff_id)
if tariff and tariff.period_prices:
base_cost_original = tariff.period_prices.get('30', 0) or tariff.period_prices.get(30, 0)
if base_cost_original > 0:
tariff_price_found = True
if not tariff_price_found:
base_cost_original = PERIOD_PRICES.get(30, 0)
base_cost, _ = apply_percentage_discount(
base_cost_original,
period_discount_percent,
)
try:
servers_cost, _ = await subscription_service.get_countries_price_by_uuids(
subscription.connected_squads,
db,
promo_group_id=promo_group_id,
)
except AttributeError:
servers_cost, _ = await get_countries_price_by_uuids_fallback(
subscription.connected_squads,
db,
promo_group_id=promo_group_id,
)
traffic_cost = settings.get_traffic_price(subscription.traffic_limit_gb)
device_limit = subscription.device_limit
if device_limit is None:
if settings.is_devices_selection_enabled():
device_limit = settings.DEFAULT_DEVICE_LIMIT
if tariff_price_found:
# Тарифный режим: серверы и трафик уже включены в цену.
# Добавляем только доп. устройства сверх тарифного лимита.
tariff_device_limit = tariff.device_limit if tariff and tariff.device_limit else 0
extra_devices = max(0, (subscription.device_limit or 0) - tariff_device_limit)
if extra_devices > 0:
device_price = (
tariff.device_price_kopeks
if tariff and tariff.device_price_kopeks is not None
else settings.PRICE_PER_DEVICE
)
devices_discount_percent = 0
if owner:
try:
devices_discount_percent = owner.get_promo_discount('devices', 30)
except AttributeError:
pass
devices_cost, _ = apply_percentage_discount(
extra_devices * device_price,
devices_discount_percent,
)
else:
forced_limit = settings.get_disabled_mode_device_limit()
if forced_limit is None:
devices_cost = 0
total_cost = base_cost + devices_cost
else:
# Классический режим: серверы + трафик + устройства считаются отдельно
try:
servers_cost, _ = await subscription_service.get_countries_price_by_uuids(
subscription.connected_squads,
db,
promo_group_id=promo_group_id,
)
except AttributeError:
servers_cost, _ = await get_countries_price_by_uuids_fallback(
subscription.connected_squads,
db,
promo_group_id=promo_group_id,
)
traffic_cost = settings.get_traffic_price(subscription.traffic_limit_gb)
device_limit = subscription.device_limit
if device_limit is None:
if settings.is_devices_selection_enabled():
device_limit = settings.DEFAULT_DEVICE_LIMIT
else:
device_limit = forced_limit
forced_limit = settings.get_disabled_mode_device_limit()
if forced_limit is None:
device_limit = settings.DEFAULT_DEVICE_LIMIT
else:
device_limit = forced_limit
devices_cost = max(0, (device_limit or 0) - settings.DEFAULT_DEVICE_LIMIT) * settings.PRICE_PER_DEVICE
devices_cost = max(0, (device_limit or 0) - settings.DEFAULT_DEVICE_LIMIT) * settings.PRICE_PER_DEVICE
total_cost = base_cost + servers_cost + traffic_cost + devices_cost
total_cost = base_cost + servers_cost + traffic_cost + devices_cost
logger.info('📊 Месячная стоимость конфигурации подписки', subscription_id=subscription.id)
base_log = f' 📅 Базовый тариф (30 дней): {base_cost_original / 100}'
if period_discount_percent > 0:
discount_value = base_cost_original * period_discount_percent // 100
base_log += f'{base_cost / 100}₽ (скидка {period_discount_percent}%: -{discount_value / 100}₽)'
logger.info(base_log)
if servers_cost > 0:
logger.info('🌍 Серверы: ₽', servers_cost=servers_cost / 100)
if traffic_cost > 0:
logger.info('📊 Трафик: ₽', traffic_cost=traffic_cost / 100)
if devices_cost > 0:
logger.info('📱 Устройства: ₽', devices_cost=devices_cost / 100)
logger.info('💎 ИТОГО: ₽', total_cost=total_cost / 100)
logger.info('Месячная стоимость подписки', subscription_id=subscription.id, total_cost_kopeks=total_cost)
return total_cost
except Exception as e:
logger.error('⚠️ Ошибка расчета стоимости подписки', error=e)
logger.error('Ошибка расчета стоимости подписки', error=e)
return 0