diff --git a/src/application/abstractions/repositories/i_payment_repository.py b/src/application/abstractions/repositories/i_payment_repository.py index 56289de..689a34b 100644 --- a/src/application/abstractions/repositories/i_payment_repository.py +++ b/src/application/abstractions/repositories/i_payment_repository.py @@ -29,6 +29,11 @@ class IPaymentRepository(ABC): raise NotImplementedError + @abstractmethod + async def get_by_id_for_user(self,*,payment_id:str,user_id:str) -> PaymentEntity | None: + raise NotImplementedError + + @abstractmethod async def list_by_user_id(self,*,user_id:str,limit:int,offset:int) -> list[PaymentEntity]: raise NotImplementedError diff --git a/src/application/commands/__init__.py b/src/application/commands/__init__.py index b86bc81..1f6d0b7 100644 --- a/src/application/commands/__init__.py +++ b/src/application/commands/__init__.py @@ -1,4 +1,4 @@ from src.application.commands.create_order_command import CreateOrderCommand from src.application.commands.create_payment_command import CreatePaymentCommand from src.application.commands.create_crypto_transfer_completed_command import CreateCryptoTransferCompletedCommand -from src.application.commands.payment_read_commands import GetOrderCommand,GetOrderStatusCommand,GetPaymentConfigCommand,GetPaymentQuoteCommand,GetPaymentQuoteFromRubCommand,ListOrdersCommand,ListPaymentsCommand,OrderPaymentResult \ No newline at end of file +from src.application.commands.payment_read_commands import GetOrderCommand,GetOrderStatusCommand,GetPaymentCommand,GetPaymentConfigCommand,GetPaymentQuoteCommand,GetPaymentQuoteFromRubCommand,ListOrdersCommand,ListPaymentsCommand,OrderPaymentResult \ No newline at end of file diff --git a/src/application/commands/payment_read_commands.py b/src/application/commands/payment_read_commands.py index d88ef23..87528db 100644 --- a/src/application/commands/payment_read_commands.py +++ b/src/application/commands/payment_read_commands.py @@ -91,6 +91,24 @@ class ListPaymentsCommand: return payments +class GetPaymentCommand: + def __init__(self, *, unit_of_work: IUnitOfWork, logger: ILogger): + self._unit_of_work = unit_of_work + self._logger = logger + + + @transactional + async def __call__(self, *, payment_id: str, user_id: str) -> PaymentEntity: + payment = await self._unit_of_work.payment_repository.get_by_id_for_user( + payment_id=payment_id, + user_id=user_id, + ) + if payment is None: + raise NotFoundException(message='Payment not found') + self._logger.info({'event':'payment_detail_requested','payment_id':payment_id,'user_id':user_id}) + return payment + + class GetOrderCommand: def __init__(self, *, unit_of_work: IUnitOfWork, logger: ILogger): self._unit_of_work = unit_of_work diff --git a/src/infrastructure/database/repositories/payment_repository.py b/src/infrastructure/database/repositories/payment_repository.py index 4e7ba3f..8647fcb 100644 --- a/src/infrastructure/database/repositories/payment_repository.py +++ b/src/infrastructure/database/repositories/payment_repository.py @@ -103,6 +103,14 @@ class PaymentRepository(IPaymentRepository): return self._to_entity(model) + async def get_by_id_for_user(self,*,payment_id:str,user_id:str) -> PaymentEntity | None: + stmt=select(Payment).where(Payment.id==payment_id,Payment.user_id==user_id) + model=await self._session.scalar(stmt) + if model is None: + return None + return self._to_entity(model) + + async def list_by_user_id(self,*,user_id:str,limit:int,offset:int) -> list[PaymentEntity]: stmt=( select(Payment) diff --git a/src/presentation/dependencies/commands.py b/src/presentation/dependencies/commands.py index bc0c407..1997c3a 100644 --- a/src/presentation/dependencies/commands.py +++ b/src/presentation/dependencies/commands.py @@ -1,7 +1,7 @@ from __future__ import annotations from fastapi import Depends from src.application.abstractions import IUnitOfWork -from src.application.commands import CreateCryptoTransferCompletedCommand,CreateOrderCommand,CreatePaymentCommand,GetOrderCommand,GetOrderStatusCommand,GetPaymentConfigCommand,GetPaymentQuoteCommand,GetPaymentQuoteFromRubCommand,ListOrdersCommand,ListPaymentsCommand +from src.application.commands import CreateCryptoTransferCompletedCommand,CreateOrderCommand,CreatePaymentCommand,GetOrderCommand,GetOrderStatusCommand,GetPaymentCommand,GetPaymentConfigCommand,GetPaymentQuoteCommand,GetPaymentQuoteFromRubCommand,ListOrdersCommand,ListPaymentsCommand from src.application.contracts import ICache,ILogger,IQueueMessanger,IReceipt from src.application.contracts.i_itpay_service import IItPayService from src.application.services import PaymentQuoteService @@ -80,6 +80,13 @@ def get_list_payments_command( return ListPaymentsCommand(unit_of_work=unit_of_work,logger=logger) +def get_payment_command( + logger: ILogger = Depends(get_logger), + unit_of_work: IUnitOfWork = Depends(get_unit_of_work), +) -> GetPaymentCommand: + return GetPaymentCommand(unit_of_work=unit_of_work,logger=logger) + + def get_order_command( logger: ILogger = Depends(get_logger), unit_of_work: IUnitOfWork = Depends(get_unit_of_work), diff --git a/src/presentation/routing/order.py b/src/presentation/routing/order.py index 3cc34a2..3569bf3 100644 --- a/src/presentation/routing/order.py +++ b/src/presentation/routing/order.py @@ -6,7 +6,7 @@ from fastapi import APIRouter,Depends,Query,Request,WebSocket,WebSocketDisconnec from fastapi.responses import ORJSONResponse from fastapi.security.utils import get_authorization_scheme_param from ulid import ULID -from src.application.commands import CreateOrderCommand,CreatePaymentCommand,GetOrderCommand,GetOrderStatusCommand,GetPaymentConfigCommand,GetPaymentQuoteCommand,GetPaymentQuoteFromRubCommand,ListOrdersCommand,ListPaymentsCommand +from src.application.commands import CreateOrderCommand,CreatePaymentCommand,GetOrderCommand,GetOrderStatusCommand,GetPaymentCommand,GetPaymentConfigCommand,GetPaymentQuoteCommand,GetPaymentQuoteFromRubCommand,ListOrdersCommand,ListPaymentsCommand from src.application.contracts import IJwtService,ILogger from src.application.domain.dto import AccessTokenPayload,AuthContext from src.application.domain.entities import OrderEntity,PaymentEntity @@ -15,10 +15,10 @@ from src.application.domain.exceptions import ApplicationException,ConflictExcep from src.application.services import PaymentQuote from src.infrastructure.context_vars import trace_id_var from src.presentation.decorators import require_access_token, csrf_protect -from src.presentation.dependencies.commands import get_create_order_command,get_create_payment_command,get_list_orders_command,get_list_payments_command,get_order_command,get_order_status_command,get_payment_config_command,get_payment_quote_command,get_payment_quote_from_rub_command +from src.presentation.dependencies.commands import get_create_order_command,get_create_payment_command,get_list_orders_command,get_list_payments_command,get_order_command,get_order_status_command,get_payment_command,get_payment_config_command,get_payment_quote_command,get_payment_quote_from_rub_command from src.presentation.dependencies.logger import get_logger from src.presentation.dependencies.security import get_jwt_service -from src.presentation.schemas.order import CreateOrder,CreateOrderResponse,ErrorResponse,OrderDetailResponse,OrderPaymentResponse,OrdersResponse,OrderStatusResponse,OrderWithPaymentResponse,PaymentConfigResponse,PaymentQuoteResponse,PaymentResponse,PaymentsResponse +from src.presentation.schemas.order import CreateOrder,CreateOrderResponse,ErrorResponse,OrderDetailResponse,OrderPaymentResponse,OrdersResponse,OrderStatusResponse,OrderWithPaymentResponse,PaymentConfigResponse,PaymentDetailResponse,PaymentQuoteResponse,PaymentResponse,PaymentsResponse from src.presentation.schemas.itpay_payment_models import ItpayPaymentData order_router = APIRouter(prefix='/order', tags=['orders']) @@ -245,6 +245,65 @@ async def payment_quote_from_rub( return _payment_quote_response(quote) +@payment_router.get( + '/orders', + response_model=OrdersResponse, + status_code=200, + responses=ERROR_RESPONSES, +) +@csrf_protect() +async def payment_list_orders( + request: Request, + limit: int = Query(default=20, ge=1, le=100), + offset: int = Query(default=0, ge=0), + auth: AuthContext = Depends(require_access_token), + command: ListOrdersCommand = Depends(get_list_orders_command), +) -> OrdersResponse: + orders = await command(user_id=auth.user_id,limit=limit,offset=offset) + items = [_order_with_payment_response(item.order,item.payment) for item in orders] + return OrdersResponse(status_code=200,orders=items,limit=limit,offset=offset) + + +@payment_router.get( + '/payments', + response_model=PaymentsResponse, + status_code=200, + responses=ERROR_RESPONSES, +) +@csrf_protect() +async def payment_list_payments( + request: Request, + limit: int = Query(default=20, ge=1, le=100), + offset: int = Query(default=0, ge=0), + auth: AuthContext = Depends(require_access_token), + command: ListPaymentsCommand = Depends(get_list_payments_command), +) -> PaymentsResponse: + payments = await command(user_id=auth.user_id,limit=limit,offset=offset) + return PaymentsResponse( + status_code=200, + payments=[_payment_response(payment) for payment in payments], + limit=limit, + offset=offset, + ) + + +@payment_router.get( + '/payments/{payment_id}', + response_model=PaymentDetailResponse, + status_code=200, + responses=ERROR_RESPONSES, +) +@csrf_protect() +async def payment_detail( + request: Request, + payment_id: str, + auth: AuthContext = Depends(require_access_token), + command: GetPaymentCommand = Depends(get_payment_command), +) -> PaymentDetailResponse: + payment = await command(payment_id=payment_id,user_id=auth.user_id) + return PaymentDetailResponse(status_code=200,payment=_payment_response(payment)) + + @orders_router.get( '/orders', response_model=OrdersResponse, diff --git a/src/presentation/schemas/order.py b/src/presentation/schemas/order.py index 60e7433..12bbacd 100644 --- a/src/presentation/schemas/order.py +++ b/src/presentation/schemas/order.py @@ -100,6 +100,11 @@ class PaymentsResponse(BaseModel): offset: int +class PaymentDetailResponse(BaseModel): + status_code: int + payment: PaymentResponse + + class OrderStatusResponse(BaseModel): status_code: int order_id: str