Source code for salesman.checkout.payment

from __future__ import annotations

from typing import TYPE_CHECKING, Any, List

from django.core.exceptions import ValidationError
from django.http import HttpRequest
from django.urls import URLPattern, URLResolver, include, path
from django.utils.translation import gettext_lazy as _

from salesman.conf import app_settings
from salesman.core.utils import get_salesman_model

if TYPE_CHECKING:  # pragma: no cover
    from salesman.basket.models import BaseBasket
    from salesman.orders.models import BaseOrder, BaseOrderPayment

Basket = get_salesman_model("Basket")
Order = get_salesman_model("Order")
OrderPayment = get_salesman_model("OrderPayment")


[docs]class PaymentError(Exception): """ Payment error for raising payment related exceptions. """
[docs]class PaymentMethod: """ Base payment method, all payment methods defined in ``SALESMAN_PAYMENT_METHODS`` must extend this class. """ identifier: str label: str
[docs] def get_urls(self) -> list[URLPattern | URLResolver]: """ Hook for adding extra url patterns for payment method. Urls will be included as child patterns under the defined identifier namespace => ``/payment/{identifier}/{urls}``. """ return []
[docs] def is_enabled(self, request: HttpRequest) -> bool: """ Method used to check that payment method is enabled for a given request. Args: request (HttpRequest): Django request Returns: bool: True if payment method is enabled """ return True
[docs] def validate_basket(self, basket: BaseBasket, request: HttpRequest) -> None: """ Method used to validate that payment method is valid for the given basket. Args: basket (Basket): Basket instance request (HttpRequest): Django request Raises: ValidationError: If payment is not valid for basket """ if not basket.count: raise ValidationError(_("Your basket is empty."))
[docs] def validate_order(self, order: BaseOrder, request: HttpRequest) -> None: """ Method used to validate that payment method is valid for the given order. Args: order (Order): Order instance request (HttpRequest): Django request Raises: ValidationError: If payment is not valid for order """ if order.is_paid: raise ValidationError(_("This order has already been paid for.")) if order.status not in order.Status.get_payable(): msg = _("Payment for order with status '{status}' is not allowed.") raise ValidationError(msg.format(status=order.status_display))
[docs] def basket_payment( self, basket: BaseBasket, request: HttpRequest, ) -> str | dict[str, Any]: """ This method gets called when new checkout is submitted and is responsible for creating a new order from given basket. Args: basket (Basket): Basket instance request (HttpRequest): Django request Raises: PaymentError: If error with payment occurs Returns: Union[str, dict]: Redirect URL string or JSON serializable data dictionary """ raise NotImplementedError("Method `basket_payment()` is not implemented.")
[docs] def order_payment( self, order: BaseOrder, request: HttpRequest, ) -> str | dict[str, Any]: """ This method gets called when payment for an existing order is requested. Args: order (Order): Order instance request (HttpRequest): Django request Raises: PaymentError: If error with payment occurs Returns: Union[str, dict]: Redirect URL string or JSON serializable data dictionary """ raise NotImplementedError("Method `order_payment()` is not implemented.")
[docs] def refund_payment(self, payment: BaseOrderPayment) -> bool: """ This method gets called when orders payment refund is requested. Should return True if refund was completed. Args: payment (OrderPayment): Order payment instance Returns: bool: True if refund was completed """ return False
[docs]class PaymentMethodsPool: """ Pool for storing payment method instances. """ def __init__(self) -> None: self._payments: list[PaymentMethod] | None = None
[docs] def get_payments( self, kind: str | None = None, request: HttpRequest = None, ) -> List[PaymentMethod]: """ Returns payment method instances. Args: kind (Optional[str], optional): Either basket or order. Defaults to None. Returns: List[PaymentMethod]: Payment method instances """ if not self._payments: self._payments = [P() for P in app_settings.SALESMAN_PAYMENT_METHODS] payments = self._payments if kind in ["basket", "order"]: method = f"{kind}_payment" payments = [p for p in payments if method in p.__class__.__dict__] if request: payments = [p for p in payments if p.is_enabled(request)] return payments
[docs] def get_urls(self) -> list[URLPattern | URLResolver]: """ Returns a list of url patterns for payments to be included. """ urlpatterns: list[URLPattern | URLResolver] = [] for payment in self.get_payments(): urls = payment.get_urls() if urls: base_url = f"payment/{payment.identifier}/" urlpatterns.append(path(base_url, include(urls))) return urlpatterns
[docs] def get_choices( self, kind: str | None = None, request: HttpRequest = None, ) -> list[tuple[str, str]]: """ Return payments formated as choices list of tuples. Args: kind (Optional[str], optional): Either basket or order. Defaults to None. Returns: list: List of choices """ return [(p.identifier, p.label) for p in self.get_payments(kind, request)]
[docs] def get_payment( self, identifier: str, kind: str | None = None, request: HttpRequest = None, ) -> PaymentMethod | None: """ Returns payment with given identifier. Args: identifier (str): Payment identifier kind (Optional[str], optional): Either basket or order. Defaults to None. Returns: PaymentMethod: Payment method instance """ for payment in self.get_payments(kind, request): if payment.identifier == identifier: return payment return None
payment_methods_pool = PaymentMethodsPool()