diff --git a/payments/__init__.py b/payments/__init__.py index 3ca8e04d9..443705a6b 100644 --- a/payments/__init__.py +++ b/payments/__init__.py @@ -61,6 +61,18 @@ class FraudStatus: ] +class WalletStatus: + PENDING = "pending" + ACTIVE = "active" + ERASED = "erased" + + CHOICES = [ + (PENDING, pgettext_lazy("wallet status", "Pending")), + (ACTIVE, pgettext_lazy("wallet status", "Active")), + (ERASED, pgettext_lazy("wallet status", "Erased")), + ] + + class RedirectNeeded(Exception): pass diff --git a/payments/core.py b/payments/core.py index 16e371e26..703623886 100644 --- a/payments/core.py +++ b/payments/core.py @@ -9,6 +9,8 @@ from django.core.exceptions import ImproperlyConfigured from django.utils.module_loading import import_string +from . import WalletStatus + if TYPE_CHECKING: from django.http import HttpRequest @@ -143,6 +145,23 @@ def get_return_url( return url + "?" + qs return url + def autocomplete_with_wallet(self, payment): + """ + Complete the payment with wallet + + If the provider uses workflow such that the payments are initiated from + implementer's side. + The users of django-payments will create a payment and call + Payment.autocomplete_with_wallet(). + + Throws RedirectNeeded if there is problem with the payment + that needs to be solved by user. + """ + raise NotImplementedError + + def erase_wallet(self, wallet): + wallet.status = WalletStatus.ERASED + def capture(self, payment, amount=None): raise NotImplementedError diff --git a/payments/models.py b/payments/models.py index 52e7e76da..9ef439153 100644 --- a/payments/models.py +++ b/payments/models.py @@ -12,6 +12,7 @@ from . import FraudStatus from . import PaymentStatus from . import PurchasedItem +from . import WalletStatus from .core import provider_factory @@ -39,6 +40,34 @@ def __setattr__(self, key, value): return None +class BaseWallet(models.Model): + token = models.CharField( + _("wallet token/id"), + help_text=_("Token/id used to identify wallet by provider"), + max_length=255, + default=None, + null=True, + blank=True, + ) + status = models.CharField( + max_length=10, choices=WalletStatus.CHOICES, default=WalletStatus.PENDING + ) + extra_data = models.JSONField( + _("extra data"), + help_text=_("Provider-specific data associated with wallet"), + default=dict, + ) + + def payment_completed(self, payment): + """ + Concrete implementation specific logic called whenever a payment is completed + using this wallet. + """ + + class Meta: + abstract = True + + class BasePayment(models.Model): """ Represents a single transaction. Each instance has one or more PaymentItem. @@ -185,6 +214,27 @@ def get_success_url(self) -> str: def get_process_url(self) -> str: return reverse("process_payment", kwargs={"token": self.token}) + def get_payment_url(self) -> str: + """ + Get the url the view that handles the payment + (payment_details() in documentation) + For now used only by PayU provider to redirect users back to CVV2 form + """ + raise NotImplementedError + + def autocomplete_with_wallet(self): + """ + Complete the payment with wallet + + If the provider uses workflow such that the payments are initiated from + implementer's side. + + Throws RedirectNeeded if there is problem with the payment + that needs to be solved by user + """ + provider = provider_factory(self.variant) + provider.autocomplete_with_wallet(self) + def capture(self, amount=None): """Capture a pre-authorized payment.