from __future__ import annotations from dataclasses import dataclass from datetime import datetime, timezone from decimal import Decimal, ROUND_UP from src.application.contracts import ICache, ILogger from src.application.domain.exceptions import OrderTotalOutOfRangeException, ServiceUnavailableException _MIN_USDT_AMOUNT: Decimal = Decimal('1') _FEE_TIERS: tuple[tuple[Decimal, Decimal, Decimal, bool, bool], ...] = ( (Decimal('0.08'), Decimal('0'), Decimal('30000'), True, True), (Decimal('0.06'), Decimal('30000'), Decimal('100000'), False, True), (Decimal('0.04'), Decimal('30000'), Decimal('600000'), False, True), ) def _total_in_bracket( total: Decimal, lo: Decimal, hi: Decimal, *, lo_inclusive: bool, hi_inclusive: bool, ) -> bool: if lo_inclusive: ok_lo = total >= lo else: ok_lo = total > lo if hi_inclusive: ok_hi = total <= hi else: ok_hi = total < hi return ok_lo and ok_hi @dataclass(slots=True) class PaymentQuote: usdt_amount: Decimal usdt_exchange_rate: Decimal gas_fee: Decimal service_fee: Decimal total_price: Decimal service_fee_rate: Decimal created_at: datetime class PaymentQuoteService: def __init__(self, *, remote_cache: ICache, logger: ILogger): self._remote_cache = remote_cache self._logger = logger async def get_quote(self, usdt_amount: Decimal) -> PaymentQuote: if usdt_amount < _MIN_USDT_AMOUNT: raise OrderTotalOutOfRangeException( message='Order total is below minimum allowed amount', ) rate_raw = await self._remote_cache.hget('tradex:rub:rate', 'value') gas_raw = await self._remote_cache.hget('gwei:eth:last', 'normal_rub') self._logger.info(f'Actual market values: rate={rate_raw}, gas={gas_raw}') if rate_raw is None: self._logger.error('Exchange rate unavailable') raise ServiceUnavailableException(message='Exchange rate unavailable') if gas_raw is None: self._logger.error('Exchange gas unavailable') raise ServiceUnavailableException(message='Exchange gas unavailable') gas_fee = Decimal(gas_raw).quantize(Decimal('0.00'), rounding=ROUND_UP) usdt_exchange_rate = Decimal(rate_raw).quantize(Decimal('0.00'), rounding=ROUND_UP) base_rub = usdt_amount * usdt_exchange_rate chosen_rate: Decimal | None = None service_fee: Decimal | None = None total_price: Decimal | None = None for fee_rate, lo, hi, li, ri in _FEE_TIERS: sf = (base_rub * fee_rate).quantize(Decimal('0.01')) tp = (base_rub + sf + gas_fee).quantize(Decimal('0.01')) if _total_in_bracket(tp, lo, hi, lo_inclusive=li, hi_inclusive=ri): chosen_rate = fee_rate service_fee = sf total_price = tp break if chosen_rate is None or service_fee is None or total_price is None: raise OrderTotalOutOfRangeException( message='Order total exceeds maximum allowed amount', ) return PaymentQuote( usdt_amount=usdt_amount, usdt_exchange_rate=usdt_exchange_rate, gas_fee=gas_fee, service_fee=service_fee, total_price=total_price, service_fee_rate=chosen_rate, created_at=datetime.now(timezone.utc), )