diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py index 57bf2150e..f1bc72c4b 100644 --- a/src/pretix/base/payment.py +++ b/src/pretix/base/payment.py @@ -25,6 +25,9 @@ class BasePaymentProvider: def __init__(self, event: Event): self.event = event self.settings = SettingsSandbox('payment', self.identifier, event) + # Default values + if self.settings.get('_fee_reverse_calc') is None: + self.settings.set('_fee_reverse_calc', True) def __str__(self): return self.identifier @@ -48,7 +51,11 @@ class BasePaymentProvider: """ fee_abs = self.settings.get('_fee_abs', as_type=Decimal, default=0) fee_percent = self.settings.get('_fee_percent', as_type=Decimal, default=0) - return round_decimal(price * fee_percent / 100) + fee_abs + fee_reverse_calc = self.settings.get('_fee_reverse_calc', as_type=bool, default=True) + if fee_reverse_calc: + return round_decimal((price + fee_abs) * (1 / (1 - fee_percent / 100)) - price) + else: + return round_decimal(price * fee_percent / 100) + fee_abs @property def verbose_name(self) -> str: @@ -118,6 +125,14 @@ class BasePaymentProvider: help_text=_('Percentage'), required=False )), + ('_fee_reverse_calc', + forms.BooleanField( + label=_('Calculate the fee from the total value including the fee.'), + help_text=_('We recommend you to enable this if you want your users to pay the payment fees of your ' + 'payment provider. Click here ' + 'for detailled information on what this does.'), + required=False + )), ]) def settings_content_render(self, request: HttpRequest) -> str: diff --git a/src/pretix/control/templates/pretixcontrol/help/base.html b/src/pretix/control/templates/pretixcontrol/help/base.html new file mode 100644 index 000000000..d328414da --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/help/base.html @@ -0,0 +1,7 @@ +{% extends "pretixcontrol/base.html" %} +{% load i18n %} +{% block content %} +

{% trans "Help center" %}

+ {% block inner %} + {% endblock %} +{% endblock %} \ No newline at end of file diff --git a/src/pretix/control/templates/pretixcontrol/help/payment/fee_reverse.html b/src/pretix/control/templates/pretixcontrol/help/payment/fee_reverse.html new file mode 100644 index 000000000..89937d6ea --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/help/payment/fee_reverse.html @@ -0,0 +1,68 @@ +{% extends "pretixcontrol/help/base.html" %} +{% block title %}Payment fee calculation{% endblock %} +{% block inner %} +

Payment fee calculation

+

+ If you configure a fee for a payment method, there are two possible ways for us to calculate this. Let's + assume that your payment provider, e.g. PayPal, charges you 5 % fees and you want to charge your users the + same 5 %, such that for a ticket with a list price of 100 € you will get your full 100 €. +

+ +{% endblock %} \ No newline at end of file diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index 5f422729f..460cb21d3 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -1,8 +1,8 @@ from django.conf.urls import include, url from pretix.control.views import ( - attendees, auth, dashboards, event, item, main, orders, organizer, user, - vouchers, + attendees, auth, dashboards, event, help, item, main, orders, organizer, + user, vouchers, ) urlpatterns = [ @@ -78,4 +78,5 @@ urlpatterns = [ url(r'^orders/$', orders.OrderList.as_view(), name='event.orders'), url(r'^attendees/$', attendees.AttendeeList.as_view(), name='event.attendees'), ])), + url(r'^help/(?P[^.]+)$', help.HelpView.as_view(), name='help'), ] diff --git a/src/pretix/control/views/help.py b/src/pretix/control/views/help.py new file mode 100644 index 000000000..da69f6203 --- /dev/null +++ b/src/pretix/control/views/help.py @@ -0,0 +1,23 @@ +from django import template +from django.http import Http404 +from django.shortcuts import render +from django.views.generic import View + +from pretix.base.models import Organizer + + +class HelpView(View): + model = Organizer + context_object_name = 'organizers' + template_name = 'pretixcontrol/organizers/index.html' + paginate_by = 30 + + def get(self, request, *args, **kwargs): + try: + locale = request.LANGUAGE_CODE + return render(request, 'pretixcontrol/help/%s.%s.html' % (kwargs.get('topic'), locale), {}) + except template.TemplateDoesNotExist: + try: + return render(request, 'pretixcontrol/help/%s.html' % kwargs.get('topic'), {}) + except template.TemplateDoesNotExist: + raise Http404('') diff --git a/src/tests/base/test_payment.py b/src/tests/base/test_payment.py new file mode 100644 index 000000000..106a695eb --- /dev/null +++ b/src/tests/base/test_payment.py @@ -0,0 +1,54 @@ +import time +from decimal import Decimal + +import pytest +from django.utils.timezone import now +from tests.testdummy.payment import DummyPaymentProvider + +from pretix.base.models import Event, EventLock, Organizer +from pretix.base.services import locking + + +@pytest.fixture +def event(): + o = Organizer.objects.create(name='Dummy', slug='dummy') + event = Event.objects.create( + organizer=o, name='Dummy', slug='dummy', + date_from=now() + ) + return event + + +@pytest.mark.django_db +def test_payment_fee_forward(event): + prov = DummyPaymentProvider(event) + prov.settings.set('_fee_abs', Decimal('0.30')) + prov.settings.set('_fee_percent', Decimal('5.00')) + prov.settings.set('_fee_reverse_calc', False) + assert prov.calculate_fee(Decimal('100.00')) == Decimal('5.30') + + +@pytest.mark.django_db +def test_payment_fee_reverse_percent(event): + prov = DummyPaymentProvider(event) + prov.settings.set('_fee_abs', Decimal('0.00')) + prov.settings.set('_fee_percent', Decimal('5.00')) + prov.settings.set('_fee_reverse_calc', True) + assert prov.calculate_fee(Decimal('100.00')) == Decimal('5.26') + + +@pytest.mark.django_db +def test_payment_fee_reverse_percent_and_abs(event): + prov = DummyPaymentProvider(event) + prov.settings.set('_fee_abs', Decimal('0.30')) + prov.settings.set('_fee_percent', Decimal('2.90')) + prov.settings.set('_fee_reverse_calc', True) + assert prov.calculate_fee(Decimal('100.00')) == Decimal('3.30') + + +@pytest.mark.django_db +def test_payment_fee_reverse_percent_and_abs_default(event): + prov = DummyPaymentProvider(event) + prov.settings.set('_fee_abs', Decimal('0.30')) + prov.settings.set('_fee_percent', Decimal('2.90')) + assert prov.calculate_fee(Decimal('100.00')) == Decimal('3.30') diff --git a/src/tests/testdummy/payment.py b/src/tests/testdummy/payment.py new file mode 100644 index 000000000..8be66dfbb --- /dev/null +++ b/src/tests/testdummy/payment.py @@ -0,0 +1,19 @@ +import logging + +from pretix.base.payment import BasePaymentProvider + +logger = logging.getLogger('tests.testdummy.ticketoutput') + + +class DummyPaymentProvider(BasePaymentProvider): + identifier = 'testdummy' + verbose_name = 'Test dummy' + + def order_pending_render(self, request, order) -> str: + pass + + def payment_is_valid_session(self, request) -> bool: + pass + + def checkout_confirm_render(self, request) -> str: + pass diff --git a/src/tests/testdummy/signals.py b/src/tests/testdummy/signals.py index 56955b6ea..500070f3d 100644 --- a/src/tests/testdummy/signals.py +++ b/src/tests/testdummy/signals.py @@ -1,9 +1,17 @@ from django.dispatch import receiver -from pretix.base.signals import register_ticket_outputs +from pretix.base.signals import ( + register_payment_providers, register_ticket_outputs, +) @receiver(register_ticket_outputs, dispatch_uid="output_dummy") def register_ticket_outputs(sender, **kwargs): from .ticketoutput import DummyTicketOutput return DummyTicketOutput + + +@receiver(register_payment_providers, dispatch_uid="payment_dummy") +def register_ticket_outputs(sender, **kwargs): + from .payment import DummyPaymentProvider + return DummyPaymentProvider