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.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-12-08 10:48:22 +03:00
parent 5b3a8aedbe
commit 87ba06ff0c
8 changed files with 67 additions and 5 deletions

3
.gitignore vendored
View file

@ -167,4 +167,5 @@ cypress/screenshots/
test.ipynb test.ipynb
# Production stuff # Production stuff
.initialized .initialized
queries

View file

@ -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.languages import get_flag_by_language
from engine.core.utils.messages import permission_denied_message from engine.core.utils.messages import permission_denied_message
from engine.payments.graphene.mutations import Deposit 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.filters import UserFilter
from engine.vibes_auth.graphene.mutations import ( from engine.vibes_auth.graphene.mutations import (
ActivateUser, ActivateUser,
@ -105,6 +107,7 @@ logger = logging.getLogger(__name__)
class Query(ObjectType): class Query(ObjectType):
parameters = Field(ConfigType) parameters = Field(ConfigType)
languages = List(LanguageType) languages = List(LanguageType)
payments_limits = Field(LimitsType)
products = DjangoFilterConnectionField(ProductType, filterset_class=ProductFilter) products = DjangoFilterConnectionField(ProductType, filterset_class=ProductFilter)
orders = DjangoFilterConnectionField(OrderType, filterset_class=OrderFilter) orders = DjangoFilterConnectionField(OrderType, filterset_class=OrderFilter)
users = DjangoFilterConnectionField(UserType, filterset_class=UserFilter) users = DjangoFilterConnectionField(UserType, filterset_class=UserFilter)
@ -140,6 +143,11 @@ class Query(ObjectType):
return languages 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 @staticmethod
def resolve_products(_parent, info, **kwargs): def resolve_products(_parent, info, **kwargs):
if info.context.user.is_authenticated and kwargs.get("uuid"): if info.context.user.is_authenticated and kwargs.get("uuid"):

View file

@ -3,7 +3,7 @@ from drf_spectacular.utils import extend_schema
from rest_framework import status from rest_framework import status
from engine.core.docs.drf import error from engine.core.docs.drf import error
from engine.payments.serializers import DepositSerializer, TransactionProcessSerializer from engine.payments.serializers import DepositSerializer, TransactionProcessSerializer, LimitsSerializer
DEPOSIT_SCHEMA = { DEPOSIT_SCHEMA = {
"post": extend_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,
},
),
}

View file

@ -7,6 +7,11 @@ from graphene_django import DjangoObjectType
from engine.payments.models import Balance, Transaction from engine.payments.models import Balance, Transaction
class LimitsType(graphene.ObjectType):
min_amount = graphene.Float()
max_amount = graphene.Float()
class TransactionType(DjangoObjectType): class TransactionType(DjangoObjectType):
process = GenericScalar() process = GenericScalar()

View file

@ -29,3 +29,8 @@ class TransactionProcessSerializer(ModelSerializer): # type: ignore [type-arg]
model = Transaction model = Transaction
fields = ("process", "order_hr_id", "order_uuid") fields = ("process", "order_hr_id", "order_uuid")
read_only_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)

View file

@ -1,7 +1,7 @@
from django.urls import include, path from django.urls import include, path
from rest_framework.routers import DefaultRouter 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 from engine.payments.viewsets import TransactionViewSet
app_name = "payments" app_name = "payments"
@ -12,5 +12,6 @@ payment_router.register(prefix=r"transactions", viewset=TransactionViewSet, base
urlpatterns = [ urlpatterns = [
path(r"", include(payment_router.urls)), path(r"", include(payment_router.urls)),
path(r"deposit/", DepositView.as_view()), path(r"deposit/", DepositView.as_view()),
path(r"limits/", LimitsAPIView.as_view()),
path(r"<str:uuid>/callback/", CallbackAPIView.as_view()), path(r"<str:uuid>/callback/", CallbackAPIView.as_view()),
] ]

View file

@ -14,3 +14,18 @@ def get_gateways_integrations(name: str | None = None) -> list[Type[AbstractGate
class_name = gateway.integration_path.split(".")[-1] class_name = gateway.integration_path.split(".")[-1]
gateways_integrations.append(create_object(module_name, class_name)) gateways_integrations.append(create_object(module_name, class_name))
return gateways_integrations 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

View file

@ -9,10 +9,11 @@ from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView 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.gateways import UnknownGatewayError
from engine.payments.models import Transaction 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__) logger = logging.getLogger(__name__)
@ -67,3 +68,15 @@ class CallbackAPIView(APIView):
return Response( return Response(
status=status.HTTP_500_INTERNAL_SERVER_ERROR, data={"error": str(e), "detail": traceback.format_exc()} 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)