From c8a78d18b8e9f2ef96034df83ba09beafe465618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Fri, 14 Feb 2020 19:42:09 +0100 Subject: [PATCH] add common subscription methods --- payments/core.py | 16 +++++++++ payments/models.py | 84 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/payments/core.py b/payments/core.py index 16e371e26..0e496cdac 100644 --- a/payments/core.py +++ b/payments/core.py @@ -143,6 +143,22 @@ def get_return_url( return url + "?" + qs return url + def autocomplete_with_subscription(self, payment): + """ + Complete the payment with subscription + Used by providers, that use server initiated subscription workflow + + Throws RedirectNeeded if there is problem with the payment that needs to be solved by user + """ + raise NotImplementedError() + + def cancel_subscription(self, subscription): + """ + Cancel subscription + Used by providers, that use provider initiated cancellation workflow + """ + raise NotImplementedError() + def capture(self, payment, amount=None): raise NotImplementedError diff --git a/payments/models.py b/payments/models.py index 52e7e76da..852da7c82 100644 --- a/payments/models.py +++ b/payments/models.py @@ -1,7 +1,10 @@ from __future__ import annotations import json +import enum from typing import Iterable +from typing import Optional +from typing import Union from uuid import uuid4 from django.db import models @@ -39,6 +42,60 @@ def __setattr__(self, key, value): return None +class BaseSubscription(models.Model): + token = models.CharField( + _("subscription token/id"), + help_text=_("Token/id used to identify subscription by provider"), + max_length=255, + default=None, + null=True, + blank=True, + ) + payment_provider = models.CharField( + _("payment provider"), + help_text=_("Provider variant, that will be used for payment renewal"), + max_length=255, + default=None, + null=True, + blank=True, + ) + + class TimeUnit(enum.Enum): + year = "year" + month = "month" + day = "day" + + def get_token(self) -> str: + return self.token + + def set_recurrence(self, token: str, **kwargs): + """ + Sets token and other values associated with subscription recurrence + Kwargs can contain provider-specific values + """ + self.token = token + + def get_period(self) -> int: + raise NotImplementedError() + + def get_unit(self) -> TimeUnit: + raise NotImplementedError() + + def cancel(self): + """ + Cancel the subscription by provider + Used by providers, that use provider initiated subscription workflow + Implementer is responsible for cancelling the subscription model + + Raises PaymentError if the cancellation didn't pass through + """ + provider = provider_factory(self.variant) + provider.cancel_subscription(self) + + class Meta: + abstract = True + + class BasePayment(models.Model): """ Represents a single transaction. Each instance has one or more PaymentItem. @@ -185,6 +242,33 @@ 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 get_subscription(self) -> Optional[BaseSubscription]: + """ + Returns subscription object associated with this payment + or None if the payment is not recurring + """ + return None + + def is_recurring(self) -> bool: + return self.get_subscription() is not None + + def autocomplete_with_subscription(self): + """ + Complete the payment with subscription + Used by providers, that use server initiated subscription workflow + + Throws RedirectNeeded if there is problem with the payment that needs to be solved by user + """ + provider = provider_factory(self.variant) + provider.autocomplete_with_subscription(self) + def capture(self, amount=None): """Capture a pre-authorized payment.