from __future__ import annotations from decimal import Decimal from src.application.abstractions import IUnitOfWork from src.application.contracts import ILogger,IReceipt from src.application.domain.enums import PaymentStatus from src.application.domain.exceptions import ApplicationException,NotFoundException,PaymentMetadataException,ReceiptDataException from src.infrastructure.database.decorators import transactional class CreateCryptoTransferCompletedCommand: def __init__(self, *, unit_of_work: IUnitOfWork, receipt: IReceipt, logger: ILogger): self._unit_of_work = unit_of_work self._receipt = receipt self._logger = logger @transactional async def __call__(self, *, order_id: str, user_id: str, web3_transaction_hash: str | None = None) -> None: if not order_id: raise PaymentMetadataException(message='Crypto transfer completed message missing order_id') if not user_id: raise PaymentMetadataException(message='Crypto transfer completed message missing user_id') await self._unit_of_work.payment_repository.update_crypto_transfer_completed( order_id=order_id, web3_transaction_hash=web3_transaction_hash, ) user = await self._unit_of_work.user_repository.get(user_id) if user is None: raise NotFoundException(message='User not found') email = str(user.email or '').strip() if not email: raise ReceiptDataException(message='User email missing') customer_info = ' '.join( part for part in ( str(user.last_name or '').strip(), str(user.first_name or '').strip(), str(user.middle_name or '').strip(), ) if part ) if not customer_info: raise ReceiptDataException(message='User full name missing') customer_inn = str(user.inn or '').strip() if not customer_inn: raise ReceiptDataException(message='User inn missing') if user.birth_date is None: raise ReceiptDataException(message='User birth date missing') customer_birthday = f'{user.birth_date.isoformat()}T12:00:00.000Z' order = await self._unit_of_work.order_repository.get_by_id(order_id) if order is None: raise NotFoundException(message='Order not found') if order.total_price is None: raise ReceiptDataException(message='Order total price missing for receipt') if order.service_fee is None: raise ReceiptDataException(message='Order service fee missing for receipt') total_amount = Decimal(str(order.total_price)).quantize(Decimal('0.01')) service_fee = Decimal(str(order.service_fee)).quantize(Decimal('0.01')) principal_amount = (total_amount - service_fee).quantize(Decimal('0.01')) if principal_amount < 0: raise ReceiptDataException(message='Invalid receipt amounts: principal negative') try: receipt_response = await self._receipt.create_receipt( order_id=order_id, user_id=user_id, email=email, total_amount=total_amount, principal_amount=principal_amount, service_fee=service_fee, customer_info=customer_info, customer_inn=customer_inn, customer_birthday=customer_birthday, request_id=self._logger.get_trace_id(), ) except ApplicationException as exception: self._logger.error({'event':'receipt_create_failed','order_id':order_id,'error':exception.message}) await self._unit_of_work.payment_repository.update_status( order_id=order_id, status=PaymentStatus.RECEIPT_ERROR, ) return receipt_model = receipt_response.get('Model') if not isinstance(receipt_model, dict): receipt_model = {} await self._unit_of_work.payment_repository.update_receipt( order_id=order_id, receipt_cloudekassir_id=str(receipt_model.get('Id') or '') or None, receipt_cloudekassir_link=str(receipt_model.get('ReceiptLocalUrl') or '') or None, )