feat: add rub quote

This commit is contained in:
2026-05-14 21:53:20 +03:00
parent 6130188a4f
commit 366bdc9515
7 changed files with 136 additions and 17 deletions

View File

@@ -1,13 +1,15 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
from decimal import Decimal, ROUND_UP
from decimal import Decimal, ROUND_DOWN, ROUND_UP
from src.application.contracts import ICache, ILogger
from src.application.domain.exceptions import OrderTotalOutOfRangeException, ServiceUnavailableException
_MIN_USDT_AMOUNT: Decimal = Decimal('1')
_MAX_TOTAL_RUB: Decimal = Decimal('600000')
_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),
@@ -51,12 +53,7 @@ class PaymentQuoteService:
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',
)
async def _load_prices(self) -> tuple[Decimal, Decimal]:
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}')
@@ -71,8 +68,16 @@ class PaymentQuoteService:
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
return usdt_exchange_rate, gas_fee
def _compose_quote(
self,
usdt_amount: Decimal,
usdt_exchange_rate: Decimal,
gas_fee: Decimal,
) -> PaymentQuote | None:
base_rub = usdt_amount * usdt_exchange_rate
chosen_rate: Decimal | None = None
service_fee: Decimal | None = None
total_price: Decimal | None = None
@@ -87,9 +92,7 @@ class PaymentQuoteService:
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 None
return PaymentQuote(
usdt_amount=usdt_amount,
@@ -100,3 +103,86 @@ class PaymentQuoteService:
service_fee_rate=chosen_rate,
created_at=datetime.now(timezone.utc),
)
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',
)
usdt_exchange_rate, gas_fee = await self._load_prices()
quote = self._compose_quote(usdt_amount, usdt_exchange_rate, gas_fee)
if quote is None:
raise OrderTotalOutOfRangeException(
message='Order total exceeds maximum allowed amount',
)
return quote
async def get_quote_from_total_rub(self, total_rub: Decimal) -> PaymentQuote:
if total_rub <= Decimal('0'):
raise OrderTotalOutOfRangeException(
message='Order total is below minimum allowed amount',
)
if total_rub > _MAX_TOTAL_RUB:
raise OrderTotalOutOfRangeException(
message='Order total exceeds maximum allowed amount',
)
usdt_exchange_rate, gas_fee = await self._load_prices()
if total_rub <= gas_fee:
raise OrderTotalOutOfRangeException(
message='Order total is below minimum allowed amount',
)
min_quote = self._compose_quote(_MIN_USDT_AMOUNT, usdt_exchange_rate, gas_fee)
if min_quote is None or min_quote.total_price > total_rub:
raise OrderTotalOutOfRangeException(
message='Order total is below minimum allowed amount',
)
u_budget = ((total_rub - gas_fee) / usdt_exchange_rate).quantize(Decimal('0.01'), rounding=ROUND_DOWN)
u_cap = ((_MAX_TOTAL_RUB - gas_fee) / (usdt_exchange_rate * Decimal('1.04'))).quantize(
Decimal('0.01'),
rounding=ROUND_DOWN,
)
u_upper = min(u_budget, u_cap)
n_hi = int((u_upper * 100).to_integral_value(rounding=ROUND_DOWN))
if n_hi < 100:
raise OrderTotalOutOfRangeException(
message='Order total is below minimum allowed amount',
)
n_lo = 100
best_cent: int | None = None
while n_lo <= n_hi:
mid = (n_lo + n_hi) // 2
u = Decimal(mid) / Decimal(100)
quote = self._compose_quote(u, usdt_exchange_rate, gas_fee)
if quote is None:
n_hi = mid - 1
continue
if quote.total_price <= total_rub:
best_cent = mid
n_lo = mid + 1
else:
n_hi = mid - 1
if best_cent is None:
raise OrderTotalOutOfRangeException(
message='Order total is below minimum allowed amount',
)
final = self._compose_quote(
Decimal(best_cent) / Decimal(100),
usdt_exchange_rate,
gas_fee,
)
if final is None:
raise OrderTotalOutOfRangeException(
message='Order total exceeds maximum allowed amount',
)
return final