From 3fe3d571bb2bdbe206ebab1bf12547b6bde14692 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Wed, 22 Oct 2025 11:12:30 +0300 Subject: [PATCH 1/2] Features: 1) Add `GatewayManager` and `GatewayQuerySet` to handle advanced gateway filtering and usage checks; 2) Integrate `GatewayManager` with `Gateway` model for automatic query filtering; 3) Introduce `can_be_used` query annotation for gateway availability. Fixes: 1) Default to a usable `Gateway` when processing new transactions if none is specified. Extra: 1) Add missing import for `GatewayManager` in `payments.models`; 2) Refactor gateway availability logic into the manager and query set for cleaner code organization. --- payments/managers.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ payments/models.py | 5 +++++ payments/signals.py | 4 ++-- 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 payments/managers.py diff --git a/payments/managers.py b/payments/managers.py new file mode 100644 index 00000000..c4684f87 --- /dev/null +++ b/payments/managers.py @@ -0,0 +1,44 @@ +from django.db.models import BooleanField, Case, F, Manager, Q, QuerySet, Sum, Value, When +from django.db.models.functions import Coalesce +from django.utils.timezone import now + + +class GatewayQuerySet(QuerySet): + def with_usage_sums(self) -> QuerySet: + today = now().date() + current_month_start = today.replace(day=1) + return self.annotate( + daily_sum=Coalesce(Sum("transactions__amount", filter=Q(transactions__created__date=today)), Value(0.0)), + monthly_sum=Coalesce( + Sum("transactions__amount", filter=Q(transactions__created__gte=current_month_start)), Value(0.0) + ), + ) + + def can_be_used(self) -> QuerySet: + qs = self.with_usage_sums() + qs = qs.annotate( + daily_ok=Case( + When(daily_limit=0, then=Value(True)), + When(daily_sum__lt=F("daily_limit"), then=Value(True)), + default=Value(False), + output_field=BooleanField(), + ), + monthly_ok=Case( + When(monthly_limit=0, then=Value(True)), + When(monthly_sum__lt=F("monthly_limit"), then=Value(True)), + default=Value(False), + output_field=BooleanField(), + ), + ) + return qs.annotate( + can_be_used=Case( + When(daily_ok=True, monthly_ok=True, is_active=True, then=Value(True)), + default=Value(False), + output_field=BooleanField(), + ) + ).order_by("-priority") + + +class GatewayManager(Manager.from_queryset(GatewayQuerySet)): + def get_queryset(self) -> QuerySet: + return super().get_queryset().can_be_used() diff --git a/payments/models.py b/payments/models.py index 78fc9c68..398e3525 100644 --- a/payments/models.py +++ b/payments/models.py @@ -20,6 +20,7 @@ from django.utils.translation import gettext_lazy as _ from core.abstract import NiceModel from evibes.utils.misc import create_object from payments.gateways import AbstractGateway +from payments.managers import GatewayManager class Transaction(NiceModel): @@ -87,6 +88,7 @@ class Balance(NiceModel): class Gateway(NiceModel): + objects = GatewayManager() name = CharField(max_length=20, null=False, blank=False, verbose_name=_("name")) default_currency = CharField( max_length=4, @@ -138,6 +140,9 @@ class Gateway(NiceModel): @property def can_be_used(self) -> bool: + if not self.is_active: + return False + today = now().date() current_month_start = today.replace(day=1) diff --git a/payments/signals.py b/payments/signals.py index 32e3dd59..d108edac 100644 --- a/payments/signals.py +++ b/payments/signals.py @@ -5,7 +5,7 @@ from typing import Any from django.db.models.signals import post_save from django.dispatch import receiver -from payments.models import Balance, Transaction +from payments.models import Balance, Transaction, Gateway from payments.utils.emailing import balance_deposit_email from vibes_auth.models import User @@ -24,7 +24,7 @@ def create_balance_on_user_creation_signal(instance: User, created: bool, **kwar def process_transaction_changes(instance: Transaction, created: bool, **kwargs: dict[Any, Any]) -> None: if created: if not instance.gateway: - raise ValueError("gateway is required to process a transaction") + instance.gateway = Gateway.objects.can_be_used().first() try: gateway = instance.gateway.get_integration_class_object() gateway.process_transaction(instance) From ec8c1039f3623c2eb78af9a67a738ceeb4db0afd Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Wed, 22 Oct 2025 11:23:45 +0300 Subject: [PATCH 2/2] Features: 1) Add setter method for `can_be_used` property in payments model; Fixes: None; Extra: None; --- payments/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/payments/models.py b/payments/models.py index 398e3525..ca0c73d5 100644 --- a/payments/models.py +++ b/payments/models.py @@ -157,6 +157,10 @@ class Gateway(NiceModel): return daily_ok and monthly_ok + @can_be_used.setter + def can_be_used(self, value: bool): + self.__dict__["can_be_used"] = value + def get_integration_class_object(self, raise_exc: bool = True) -> Type[AbstractGateway] | None: if not self.integration_path: if raise_exc: