refactor: unify first-purchase discount algorithm with PricingEngine

apply_percentage_discount now delegates to PricingEngine.apply_discount
(floor division). Removes ruble-rounding that caused inconsistency between
first-purchase and renewal pricing.

subscription_purchase_service._apply_percentage_discount now delegates
to the shared apply_percentage_discount.

All 60+ callers across handlers, keyboards, cabinet, miniapp, balance
automatically use the unified algorithm without code changes.
This commit is contained in:
Fringg
2026-03-13 05:30:44 +03:00
parent e24b911283
commit fe4e6acb53
2 changed files with 12 additions and 30 deletions
+4 -10
View File
@@ -267,16 +267,10 @@ class PurchaseBalanceError(Exception):
def _apply_percentage_discount(amount: int, percent: int) -> tuple[int, int]:
if amount <= 0 or percent <= 0:
return amount, 0
clamped = max(0, min(100, percent))
discount_value = amount * clamped // 100
discounted = amount - discount_value
if discount_value >= 100 and discounted % 100:
discounted += 100 - (discounted % 100)
discounted = min(discounted, amount)
discount_value = amount - discounted
return discounted, discount_value
"""Delegate to shared apply_percentage_discount (uses PricingEngine internally)."""
from app.utils.pricing_utils import apply_percentage_discount
return apply_percentage_discount(amount, percent)
def _apply_discount_to_monthly_component(amount_per_month: int, percent: int, months: int) -> dict[str, int]:
+8 -20
View File
@@ -43,29 +43,17 @@ def calculate_prorated_price(monthly_price: int, end_date: datetime, min_charge_
def apply_percentage_discount(amount: int, percent: int) -> tuple[int, int]:
"""Apply percentage discount using PricingEngine's floor division.
Returns (discounted_amount, discount_value).
"""
from app.services.pricing_engine import PricingEngine
if amount <= 0 or percent <= 0:
return amount, 0
clamped_percent = max(0, min(100, percent))
discount_value = amount * clamped_percent // 100
discounted_amount = amount - discount_value
# Round the discounted price up to the nearest full ruble (100 kopeks)
# to avoid undercharging users because of fractional kopeks.
if discount_value >= 100 and discounted_amount % 100:
discounted_amount += 100 - (discounted_amount % 100)
discounted_amount = min(discounted_amount, amount)
discount_value = amount - discounted_amount
logger.debug(
'Применена скидка %: → (скидка)',
clamped_percent=clamped_percent,
amount=amount,
discounted_amount=discounted_amount,
discount_value=discount_value,
)
return discounted_amount, discount_value
discounted = PricingEngine.apply_discount(amount, percent)
return discounted, amount - discounted
def resolve_discount_percent(