feat: add full pay path
This commit is contained in:
2
src/application/commands/__init__.py
Normal file
2
src/application/commands/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from src.application.commands.create_order_command import CreateOrderCommand
|
||||
from src.application.commands.create_payment_command import CreatePaymentCommand
|
||||
81
src/application/commands/create_order_command.py
Normal file
81
src/application/commands/create_order_command.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from __future__ import annotations
|
||||
from datetime import datetime, timezone
|
||||
from decimal import Decimal, ROUND_UP
|
||||
from ulid import ULID
|
||||
from src.application.abstractions import IUnitOfWork
|
||||
from src.application.contracts import ICache, ILogger
|
||||
from src.application.contracts import IItPayService
|
||||
from src.application.domain.entities.order import OrderEntity
|
||||
from src.application.domain.enums import OrderStatus
|
||||
from src.application.domain.exceptions import ApplicationException
|
||||
from src.infrastructure.database.decorators import transactional
|
||||
from src.presentation.schemas.order import CreateOrder
|
||||
|
||||
|
||||
class CreateOrderCommand:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
unit_of_work: IUnitOfWork,
|
||||
logger: ILogger,
|
||||
cache_local: ICache,
|
||||
remote_cache: ICache,
|
||||
itpay_service: IItPayService,
|
||||
) -> None:
|
||||
self._unit_of_work = unit_of_work
|
||||
self._logger = logger
|
||||
self._cache_local = cache_local
|
||||
self._remote_cache = remote_cache
|
||||
self._itpay_service = itpay_service
|
||||
|
||||
|
||||
@transactional
|
||||
async def __call__(self, payment_data: CreateOrder, user_id: str) -> OrderEntity:
|
||||
client_payment_id = str(ULID())
|
||||
|
||||
rate_raw = await self._remote_cache.hget('tradex:rub:rate','value')
|
||||
gas_raw = await self._remote_cache.hget('gwei:eth:last','normal_rub')
|
||||
|
||||
if rate_raw is None:
|
||||
self._logger.error('Exchange rate unavailable')
|
||||
rate_raw = '2.00'
|
||||
#raise ApplicationException(status_code=503, message='Exchange rate unavailable')
|
||||
|
||||
if gas_raw is None:
|
||||
self._logger.error('Exchange gas unavailable')
|
||||
gas_raw = '1.00'
|
||||
#raise ApplicationException(status_code=503, message='Exchange gas unavailable')
|
||||
|
||||
actual_gas_fee = Decimal(gas_raw).quantize(Decimal('0.00'), rounding=ROUND_UP)
|
||||
actual_usdt_exchange_rate = Decimal(rate_raw).quantize(Decimal('0.00'), rounding=ROUND_UP)
|
||||
actual_service_fee = (payment_data.usdt_amount * actual_usdt_exchange_rate * Decimal('0.04')).quantize(Decimal('0.01'))
|
||||
actual_total_price = (payment_data.usdt_amount * actual_usdt_exchange_rate + actual_service_fee + actual_gas_fee).quantize(Decimal('0.01'))
|
||||
if actual_total_price > payment_data.total_price * Decimal('1.01'):
|
||||
self._logger.error('Price has changed, please refresh and try again')
|
||||
raise ApplicationException(status_code=409, message='Price has changed, please refresh and try again')
|
||||
|
||||
order = OrderEntity(
|
||||
user_id=user_id,
|
||||
usdt_amount=payment_data.usdt_amount,
|
||||
usdt_exchange_rate=actual_usdt_exchange_rate,
|
||||
gas_fee=actual_gas_fee,
|
||||
service_fee=actual_service_fee,
|
||||
total_price=actual_total_price,
|
||||
status=OrderStatus.PENDING,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
client_payment_id=client_payment_id,
|
||||
)
|
||||
|
||||
saved = await self._unit_of_work.order_repository.create(order)
|
||||
with_itpay = await self._itpay_service.create_payment(saved)
|
||||
if with_itpay.status in (
|
||||
OrderStatus.CANCELLED,
|
||||
OrderStatus.REJECTED,
|
||||
OrderStatus.ERROR,
|
||||
):
|
||||
await self._unit_of_work.order_repository.update_after_itpay_failure(with_itpay)
|
||||
else:
|
||||
await self._unit_of_work.order_repository.update_after_itpay_payment_created(with_itpay)
|
||||
return with_itpay
|
||||
|
||||
49
src/application/commands/create_payment_command.py
Normal file
49
src/application/commands/create_payment_command.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from __future__ import annotations
|
||||
from ulid import ULID
|
||||
from src.application.abstractions import IUnitOfWork
|
||||
from src.application.contracts import ILogger,IQueueMessanger
|
||||
from src.application.domain.exceptions import ApplicationException
|
||||
from src.infrastructure.config import settings
|
||||
from src.infrastructure.database.decorators import transactional
|
||||
from src.presentation.schemas.itpay_payment_models import ItpayPaymentData
|
||||
|
||||
|
||||
class CreatePaymentCommand:
|
||||
def __init__(self, *, unit_of_work: IUnitOfWork, logger: ILogger, queue_messanger: IQueueMessanger):
|
||||
self._unit_of_work = unit_of_work
|
||||
self._logger = logger
|
||||
self._queue_messanger = queue_messanger
|
||||
|
||||
@transactional
|
||||
async def __call__(self, payment: ItpayPaymentData) -> None:
|
||||
if str(payment.status).strip().lower() != 'completed':
|
||||
return
|
||||
metadata = payment.metadata or {}
|
||||
order_id = str(metadata.get('order_id') or '')
|
||||
user_id = str(metadata.get('user_id') or '')
|
||||
if not order_id:
|
||||
raise ApplicationException(status_code=400, message='Itpay webhook metadata missing order_id')
|
||||
if not user_id:
|
||||
raise ApplicationException(status_code=400, message='Itpay webhook metadata missing user_id')
|
||||
await self._unit_of_work.payment_repository.create_completed(
|
||||
user_id=user_id,
|
||||
order_id=order_id,
|
||||
itpay_payment_id=str(payment.id),
|
||||
itpay_paid_amount=str(payment.amount) if payment.amount is not None else None,
|
||||
transaction_id=str(payment.transaction_id) if payment.transaction_id is not None else None,
|
||||
paid_at=str(payment.paid) if payment.paid is not None else None,
|
||||
expired_date=str(payment.expired_date) if payment.expired_date is not None else None,
|
||||
)
|
||||
message_id = str(ULID())
|
||||
message: dict[str,str] = {
|
||||
'order_id': order_id,
|
||||
'user_id': user_id,
|
||||
'trace_id': self._logger.get_trace_id(),
|
||||
'message_id': message_id,
|
||||
}
|
||||
await self._queue_messanger.publish_to_queue(
|
||||
queue=settings.RABBIT_CRYPTO_TRANSFER_QUEUE,
|
||||
message=message,
|
||||
message_id=message_id,
|
||||
correlation_id=message['trace_id'],
|
||||
)
|
||||
Reference in New Issue
Block a user