feat: add endpoints desc

This commit is contained in:
2026-05-11 19:50:25 +03:00
parent 852ee9ec2e
commit 46b1e336d9
22 changed files with 236 additions and 95 deletions

View File

@@ -3,7 +3,7 @@ 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
from src.application.domain.exceptions import ApplicationException,NotFoundException,PaymentMetadataException,ReceiptDataException
from src.infrastructure.database.decorators import transactional
@@ -17,19 +17,19 @@ class CreateCryptoTransferCompletedCommand:
@transactional
async def __call__(self, *, order_id: str, user_id: str, web3_transaction_hash: str | None = None) -> None:
if not order_id:
raise ApplicationException(status_code=400, message='Crypto transfer completed message missing order_id')
raise PaymentMetadataException(message='Crypto transfer completed message missing order_id')
if not user_id:
raise ApplicationException(status_code=400, message='Crypto transfer completed message missing 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 ApplicationException(status_code=404, message='User not found')
raise NotFoundException(message='User not found')
email = str(user.email or '').strip()
if not email:
raise ApplicationException(status_code=400, message='User email missing')
raise ReceiptDataException(message='User email missing')
customer_info = ' '.join(
part
for part in (
@@ -40,28 +40,28 @@ class CreateCryptoTransferCompletedCommand:
if part
)
if not customer_info:
raise ApplicationException(status_code=400, message='User full name missing')
raise ReceiptDataException(message='User full name missing')
customer_inn = str(user.inn or '').strip()
if not customer_inn:
raise ApplicationException(status_code=400, message='User inn missing')
raise ReceiptDataException(message='User inn missing')
if user.birth_date is None:
raise ApplicationException(status_code=400, message='User birth date missing')
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 ApplicationException(status_code=404, message='Order not found')
raise NotFoundException(message='Order not found')
if order.total_price is None:
raise ApplicationException(status_code=400, message='Order total price missing for receipt')
raise ReceiptDataException(message='Order total price missing for receipt')
if order.service_fee is None:
raise ApplicationException(status_code=400, message='Order service fee missing for receipt')
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 ApplicationException(status_code=400, message='Invalid receipt amounts: principal negative')
raise ReceiptDataException(message='Invalid receipt amounts: principal negative')
try:
receipt_response = await self._receipt.create_receipt(

View File

@@ -7,7 +7,7 @@ from src.application.contracts import 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.application.domain.exceptions import PriceChangedException
from src.application.services import PaymentQuoteService
from src.infrastructure.database.decorators import transactional
from src.presentation.schemas.order import CreateOrder
@@ -40,7 +40,7 @@ class CreateOrderCommand:
actual_total_price = quote.total_price
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')
raise PriceChangedException()
order = OrderEntity(
user_id=user_id,

View File

@@ -3,7 +3,7 @@ from ulid import ULID
from src.application.abstractions import IUnitOfWork
from src.application.contracts import ILogger,IQueueMessanger
from src.application.domain.enums import PaymentStatus
from src.application.domain.exceptions import ApplicationException
from src.application.domain.exceptions import PaymentMetadataException
from src.infrastructure.config import settings
from src.infrastructure.database.decorators import transactional
from src.presentation.schemas.itpay_payment_models import ItpayPaymentData
@@ -25,9 +25,9 @@ class CreatePaymentCommand:
trace_id = str(metadata.get('trace_id') or self._logger.get_trace_id())
self._logger.set_trace_id(trace_id)
if not order_id:
raise ApplicationException(status_code=400, message='Itpay webhook metadata missing order_id')
raise PaymentMetadataException(message='Itpay webhook metadata missing order_id')
if not user_id:
raise ApplicationException(status_code=400, message='Itpay webhook metadata missing user_id')
raise PaymentMetadataException(message='Itpay webhook metadata missing user_id')
payment_created = await self._unit_of_work.payment_repository.create_completed(
user_id=user_id,
order_id=order_id,

View File

@@ -2,9 +2,17 @@ from src.application.domain.exceptions.application_exception import ApplicationE
from src.application.domain.exceptions.bad_gateway_exception import BadGatewayException
from src.application.domain.exceptions.bad_request_exception import BadRequestException
from src.application.domain.exceptions.conflict_exception import ConflictException
from src.application.domain.exceptions.csrf_exception import CsrfException
from src.application.domain.exceptions.forbidden_exception import ForbiddenException
from src.application.domain.exceptions.internal_server_exception import InternalServerException
from src.application.domain.exceptions.jwt_exception import JwtException
from src.application.domain.exceptions.not_found_exception import NotFoundException
from src.application.domain.exceptions.payment_metadata_exception import PaymentMetadataException
from src.application.domain.exceptions.payment_provider_exception import PaymentProviderException
from src.application.domain.exceptions.price_changed_exception import PriceChangedException
from src.application.domain.exceptions.receipt_data_exception import ReceiptDataException
from src.application.domain.exceptions.receipt_provider_exception import ReceiptProviderException
from src.application.domain.exceptions.rate_limit_exception import RateLimitException
from src.application.domain.exceptions.service_unavailable_exception import ServiceUnavailableException
from src.application.domain.exceptions.unauthorized_exception import UnauthorizedException
@@ -13,9 +21,17 @@ __all__ = [
'BadGatewayException',
'BadRequestException',
'ConflictException',
'CsrfException',
'ForbiddenException',
'InternalServerException',
'JwtException',
'NotFoundException',
'PaymentMetadataException',
'PaymentProviderException',
'PriceChangedException',
'ReceiptDataException',
'ReceiptProviderException',
'RateLimitException',
'ServiceUnavailableException',
'UnauthorizedException',
]

View File

@@ -0,0 +1,12 @@
from __future__ import annotations
from typing import Mapping
from src.application.domain.exceptions.application_exception import ApplicationException
class CsrfException(ApplicationException):
def __init__(
self,
message: str = 'CSRF token invalid',
headers: Mapping[str,str] | None = None,
):
super().__init__(status_code=403,message=message,headers=headers)

View File

@@ -0,0 +1,13 @@
from __future__ import annotations
from typing import Mapping
from src.application.domain.exceptions.application_exception import ApplicationException
class JwtException(ApplicationException):
def __init__(
self,
message: str = 'Invalid token',
status_code: int = 401,
headers: Mapping[str,str] | None = None,
):
super().__init__(status_code=status_code,message=message,headers=headers)

View File

@@ -0,0 +1,12 @@
from __future__ import annotations
from typing import Mapping
from src.application.domain.exceptions.application_exception import ApplicationException
class PaymentMetadataException(ApplicationException):
def __init__(
self,
message: str = 'Payment metadata invalid',
headers: Mapping[str,str] | None = None,
):
super().__init__(status_code=400,message=message,headers=headers)

View File

@@ -0,0 +1,12 @@
from __future__ import annotations
from typing import Mapping
from src.application.domain.exceptions.application_exception import ApplicationException
class PaymentProviderException(ApplicationException):
def __init__(
self,
message: str = 'Payment provider error',
headers: Mapping[str,str] | None = None,
):
super().__init__(status_code=502,message=message,headers=headers)

View File

@@ -0,0 +1,12 @@
from __future__ import annotations
from typing import Mapping
from src.application.domain.exceptions.application_exception import ApplicationException
class PriceChangedException(ApplicationException):
def __init__(
self,
message: str = 'Price has changed, please refresh and try again',
headers: Mapping[str,str] | None = None,
):
super().__init__(status_code=409,message=message,headers=headers)

View File

@@ -0,0 +1,13 @@
from __future__ import annotations
from typing import Mapping
from src.application.domain.exceptions.application_exception import ApplicationException
class RateLimitException(ApplicationException):
def __init__(
self,
message: str = 'Too Many Requests',
status_code: int = 429,
headers: Mapping[str,str] | None = None,
):
super().__init__(status_code=status_code,message=message,headers=headers)

View File

@@ -0,0 +1,12 @@
from __future__ import annotations
from typing import Mapping
from src.application.domain.exceptions.application_exception import ApplicationException
class ReceiptDataException(ApplicationException):
def __init__(
self,
message: str = 'Receipt data invalid',
headers: Mapping[str,str] | None = None,
):
super().__init__(status_code=400,message=message,headers=headers)

View File

@@ -0,0 +1,13 @@
from __future__ import annotations
from typing import Mapping
from src.application.domain.exceptions.application_exception import ApplicationException
class ReceiptProviderException(ApplicationException):
def __init__(
self,
message: str = 'Receipt provider error',
status_code: int = 502,
headers: Mapping[str,str] | None = None,
):
super().__init__(status_code=status_code,message=message,headers=headers)