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:
@@ -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]:
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user