Source code for shuup.core.models._service_payment

# -*- coding: utf-8 -*-
# This file is part of Shuup.
#
# Copyright (c) 2012-2021, Shuup Commerce Inc. All rights reserved.
#
# This source code is licensed under the OSL-3.0 license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import unicode_literals, with_statement

import decimal
from django.db import models
from django.http.response import HttpResponseRedirect
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from enumfields import Enum, EnumField
from parler.models import TranslatedFields

from shuup.utils.analog import define_log_model

from ._order_lines import OrderLineType
from ._orders import Order, PaymentStatus
from ._service_base import Service, ServiceChoice, ServiceProvider
from ._service_behavior import StaffOnlyBehaviorComponent


class PaymentMethod(Service):
    payment_processor = models.ForeignKey(
        "PaymentProcessor", null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_("payment processor")
    )

    translations = TranslatedFields(
        name=models.CharField(
            max_length=100,
            verbose_name=_("name"),
            help_text=_("The payment method name. This name is shown to the customers on checkout."),
        ),
        description=models.CharField(
            max_length=500,
            blank=True,
            verbose_name=_("description"),
            help_text=_(
                "The description of the payment method. This description is shown to the customers on checkout."
            ),
        ),
    )

    line_type = OrderLineType.PAYMENT
    provider_attr = "payment_processor"
    shop_product_m2m = "payment_methods"

    class Meta:
        verbose_name = _("payment method")
        verbose_name_plural = _("payment methods")

[docs] def can_delete(self): return not Order.objects.filter(payment_method=self).exists()
[docs] def get_payment_process_response(self, order, urls): self._make_sure_is_usable() return self.provider.get_payment_process_response(self, order, urls)
[docs] def process_payment_return_request(self, order, request): self._make_sure_is_usable() self.provider.process_payment_return_request(self, order, request)
class PaymentProcessor(ServiceProvider): """ Service provider interface for payment processing. Services provided by a payment processor are `payment methods <PaymentMethod>`. To create a new payment method for a payment processor, use the `create_service` method. Implementers of this interface will provide provide a list of payment service choices and each related payment method should have one of those service choices assigned to it. Payment processing is handled with `get_payment_process_response` and `process_payment_return_request` methods. Note: `PaymentProcessor` objects should never be created on their own but rather through a concrete subclass. """ service_model = PaymentMethod
[docs] def delete(self, *args, **kwargs): PaymentMethod.objects.filter(payment_processor=self).update(**{"enabled": False}) super(PaymentProcessor, self).delete(*args, **kwargs)
[docs] def get_payment_process_response(self, service, order, urls): """ Get payment process response for a given order. :type service: shuup.core.models.PaymentMethod :type order: shuup.core.models.Order :type urls: PaymentUrls :rtype: django.http.HttpResponse|None """ return HttpResponseRedirect(urls.return_url)
[docs] def process_payment_return_request(self, service, order, request): """ Process payment return request for a given order. Should set ``order.payment_status``. Default implementation just sets it to `~PaymentStatus.DEFERRED` if it is `~PaymentStatus.NOT_PAID`. :type service: shuup.core.models.PaymentMethod :type order: shuup.core.models.Order :type request: django.http.HttpRequest :rtype: None """ if order.payment_status == PaymentStatus.NOT_PAID: order.payment_status = PaymentStatus.DEFERRED order.add_log_entry("Info! Payment status set to `deferred` by %s." % self) order.save(update_fields=("payment_status",))
def _create_service(self, choice_identifier, **kwargs): labels = kwargs.pop("labels", None) service = PaymentMethod.objects.create(payment_processor=self, choice_identifier=choice_identifier, **kwargs) if labels: service.labels.set(labels) return service class PaymentUrls(object): """ Container for URLs used in payment processing. """ def __init__(self, payment_url, return_url, cancel_url): self.payment_url = payment_url self.return_url = return_url self.cancel_url = cancel_url class RoundingMode(Enum): ROUND_HALF_UP = decimal.ROUND_HALF_UP ROUND_HALF_DOWN = decimal.ROUND_HALF_DOWN ROUND_UP = decimal.ROUND_UP ROUND_DOWN = decimal.ROUND_DOWN class Labels: ROUND_HALF_UP = _("round up to the nearest number with ties going up, away from zero") ROUND_HALF_DOWN = _("round to the nearest number with ties going down, towards zero") ROUND_UP = _("round up, away from zero, towards the farther round number") ROUND_DOWN = _("round down, towards zero, towards the closest round number") class CustomPaymentProcessor(PaymentProcessor): """ Payment processor without any integration or special processing. Can be used for payment methods whose payments are processed manually or generally outside the Shuup. """ rounding_quantize = models.DecimalField( max_digits=36, decimal_places=9, default=decimal.Decimal("0.05"), verbose_name=_("rounding quantize"), help_text=_("Choose rounding quantize (precision) for cash payment."), ) rounding_mode = EnumField( RoundingMode, max_length=50, default=RoundingMode.ROUND_HALF_UP, verbose_name=_("rounding mode"), help_text=_("Choose rounding mode for cash payment."), ) class Meta: verbose_name = _("custom payment processor") verbose_name_plural = _("custom payment processors")
[docs] def get_service_choices(self): return [ServiceChoice("manual", _("Manually processed payment")), ServiceChoice("cash", _("Cash payment"))]
def _create_service(self, choice_identifier, **kwargs): service = super(CustomPaymentProcessor, self)._create_service(choice_identifier, **kwargs) if choice_identifier == "cash": service.behavior_components.add(StaffOnlyBehaviorComponent.objects.create()) return service
[docs] def process_payment_return_request(self, service, order, request): if service == "cash": if not order.is_paid(): order.create_payment( order.taxful_total_price, payment_identifier="Cash-%s" % now().isoformat(), description="Cash Payment", )
PaymentMethodLogEntry = define_log_model(PaymentMethod) PaymentProcessorLogEntry = define_log_model(PaymentProcessor)