From 87ba06ff0caf0e06479e8ce5dc33342c35ced4c7 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Mon, 8 Dec 2025 10:48:22 +0300 Subject: [PATCH] Features: 1) Add `LimitsType` GraphQL object and `payments_limits` query to expose deposit limits; 2) Introduce `LimitsSerializer` and `LimitsAPIView` for retrieving minimal and maximal deposit amounts; 3) Implement `get_limits` utility to calculate deposit boundaries dynamically; Fixes: 1) Add missing `LimitsSerializer` import in `drf.views` module; Extra: 1) Update `.gitignore` to exclude `queries`; 2) Refactor schema and views to integrate new limits functionality. --- .gitignore | 3 ++- engine/core/graphene/schema.py | 8 ++++++++ engine/payments/docs/drf/views.py | 16 +++++++++++++++- engine/payments/graphene/object_types.py | 5 +++++ engine/payments/serializers.py | 5 +++++ engine/payments/urls.py | 3 ++- engine/payments/utils/gateways.py | 15 +++++++++++++++ engine/payments/views.py | 17 +++++++++++++++-- 8 files changed, 67 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 77be66d4..44102a49 100644 --- a/.gitignore +++ b/.gitignore @@ -167,4 +167,5 @@ cypress/screenshots/ test.ipynb # Production stuff -.initialized \ No newline at end of file +.initialized +queries \ No newline at end of file diff --git a/engine/core/graphene/schema.py b/engine/core/graphene/schema.py index 3e6140fa..870024eb 100644 --- a/engine/core/graphene/schema.py +++ b/engine/core/graphene/schema.py @@ -83,6 +83,8 @@ from engine.core.utils import get_project_parameters from engine.core.utils.languages import get_flag_by_language from engine.core.utils.messages import permission_denied_message from engine.payments.graphene.mutations import Deposit +from engine.payments.graphene.object_types import LimitsType +from engine.payments.utils.gateways import get_limits from engine.vibes_auth.filters import UserFilter from engine.vibes_auth.graphene.mutations import ( ActivateUser, @@ -105,6 +107,7 @@ logger = logging.getLogger(__name__) class Query(ObjectType): parameters = Field(ConfigType) languages = List(LanguageType) + payments_limits = Field(LimitsType) products = DjangoFilterConnectionField(ProductType, filterset_class=ProductFilter) orders = DjangoFilterConnectionField(OrderType, filterset_class=OrderFilter) users = DjangoFilterConnectionField(UserType, filterset_class=UserFilter) @@ -140,6 +143,11 @@ class Query(ObjectType): return languages + @staticmethod + def resolve_payments_limits(_parent, _info): + min_amount, max_amount = get_limits() + return {"min_amount": min_amount, "max_amount": max_amount} + @staticmethod def resolve_products(_parent, info, **kwargs): if info.context.user.is_authenticated and kwargs.get("uuid"): diff --git a/engine/payments/docs/drf/views.py b/engine/payments/docs/drf/views.py index 7f8c7cea..4a8b3799 100644 --- a/engine/payments/docs/drf/views.py +++ b/engine/payments/docs/drf/views.py @@ -3,7 +3,7 @@ from drf_spectacular.utils import extend_schema from rest_framework import status from engine.core.docs.drf import error -from engine.payments.serializers import DepositSerializer, TransactionProcessSerializer +from engine.payments.serializers import DepositSerializer, TransactionProcessSerializer, LimitsSerializer DEPOSIT_SCHEMA = { "post": extend_schema( @@ -20,3 +20,17 @@ DEPOSIT_SCHEMA = { }, ), } + +LIMITS_SCHEMA = { + "get": extend_schema( + tags=[ + "payments", + ], + summary=_("payment limits"), + description=_("retrieve minimal and maximal allowed deposit amounts across available gateways"), + responses={ + status.HTTP_200_OK: LimitsSerializer, + status.HTTP_401_UNAUTHORIZED: error, + }, + ), +} diff --git a/engine/payments/graphene/object_types.py b/engine/payments/graphene/object_types.py index c33e7405..a710dc71 100644 --- a/engine/payments/graphene/object_types.py +++ b/engine/payments/graphene/object_types.py @@ -7,6 +7,11 @@ from graphene_django import DjangoObjectType from engine.payments.models import Balance, Transaction +class LimitsType(graphene.ObjectType): + min_amount = graphene.Float() + max_amount = graphene.Float() + + class TransactionType(DjangoObjectType): process = GenericScalar() diff --git a/engine/payments/serializers.py b/engine/payments/serializers.py index 9025e971..f2faac8c 100644 --- a/engine/payments/serializers.py +++ b/engine/payments/serializers.py @@ -29,3 +29,8 @@ class TransactionProcessSerializer(ModelSerializer): # type: ignore [type-arg] model = Transaction fields = ("process", "order_hr_id", "order_uuid") read_only_fields = ("process", "order_hr_id", "order_uuid") + + +class LimitsSerializer(Serializer): # type: ignore [type-arg] + min_amount = FloatField(read_only=True) + max_amount = FloatField(read_only=True) diff --git a/engine/payments/urls.py b/engine/payments/urls.py index b3837941..d4db799d 100644 --- a/engine/payments/urls.py +++ b/engine/payments/urls.py @@ -1,7 +1,7 @@ from django.urls import include, path from rest_framework.routers import DefaultRouter -from engine.payments.views import CallbackAPIView, DepositView +from engine.payments.views import CallbackAPIView, DepositView, LimitsAPIView from engine.payments.viewsets import TransactionViewSet app_name = "payments" @@ -12,5 +12,6 @@ payment_router.register(prefix=r"transactions", viewset=TransactionViewSet, base urlpatterns = [ path(r"", include(payment_router.urls)), path(r"deposit/", DepositView.as_view()), + path(r"limits/", LimitsAPIView.as_view()), path(r"/callback/", CallbackAPIView.as_view()), ] diff --git a/engine/payments/utils/gateways.py b/engine/payments/utils/gateways.py index d4109ec9..60278e74 100644 --- a/engine/payments/utils/gateways.py +++ b/engine/payments/utils/gateways.py @@ -14,3 +14,18 @@ def get_gateways_integrations(name: str | None = None) -> list[Type[AbstractGate class_name = gateway.integration_path.split(".")[-1] gateways_integrations.append(create_object(module_name, class_name)) return gateways_integrations + + +def get_limits() -> tuple[float, float]: + from django.db.models import Min, Max + + qs = Gateway.objects.can_be_used().filter(can_be_used=True) + + if not qs.exists(): + return 0.0, 0.0 + + agg = qs.aggregate(min_limit=Min("minimum_transaction_amount"), max_limit=Max("maximum_transaction_amount")) + + min_limit = float(agg.get("min_limit") or 0.0) + max_limit = float(agg.get("max_limit") or 0.0) + return min_limit, max_limit diff --git a/engine/payments/views.py b/engine/payments/views.py index 7db2c2bb..f1843b28 100644 --- a/engine/payments/views.py +++ b/engine/payments/views.py @@ -9,10 +9,11 @@ from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView -from engine.payments.docs.drf.views import DEPOSIT_SCHEMA +from engine.payments.docs.drf.views import DEPOSIT_SCHEMA, LIMITS_SCHEMA from engine.payments.gateways import UnknownGatewayError from engine.payments.models import Transaction -from engine.payments.serializers import DepositSerializer, TransactionProcessSerializer +from engine.payments.serializers import DepositSerializer, TransactionProcessSerializer, LimitsSerializer +from engine.payments.utils.gateways import get_limits logger = logging.getLogger(__name__) @@ -67,3 +68,15 @@ class CallbackAPIView(APIView): return Response( status=status.HTTP_500_INTERNAL_SERVER_ERROR, data={"error": str(e), "detail": traceback.format_exc()} ) + + +@extend_schema_view(**LIMITS_SCHEMA) +class LimitsAPIView(APIView): + __doc__ = _( # type: ignore [assignment] + "This endpoint returns minimal and maximal allowed deposit amounts across available gateways." + ) + + def get(self, request: Request, *args: list[Any], **kwargs: dict[Any, Any]) -> Response: + min_amount, max_amount = get_limits() + data = {"min_amount": min_amount, "max_amount": max_amount} + return Response(LimitsSerializer(data).data, status=status.HTTP_200_OK)