forked from CGM_Public/pretix_original
Fixed #132 -- Reverse payment fee calculation
This commit is contained in:
@@ -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. <a href="/control/help/payment/fee_reverse" target="_blank">Click here '
|
||||
'for detailled information on what this does.</a>'),
|
||||
required=False
|
||||
)),
|
||||
])
|
||||
|
||||
def settings_content_render(self, request: HttpRequest) -> str:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{% extends "pretixcontrol/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Help center" %}</h1>
|
||||
{% block inner %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,68 @@
|
||||
{% extends "pretixcontrol/help/base.html" %}
|
||||
{% block title %}Payment fee calculation{% endblock %}
|
||||
{% block inner %}
|
||||
<h2>Payment fee calculation</h2>
|
||||
<p>
|
||||
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 €.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Method A: Calculate the fee from the subtotal and add it to the bill.</strong> For a ticket price of
|
||||
100 €, this will lead to the following calculation:
|
||||
<table class="table" style="width: auto;">
|
||||
<tr>
|
||||
<td>Ticket price</td>
|
||||
<td class="text-right">100.00 €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>pretix calculates the fee as 5% of 100 €</td>
|
||||
<td class="text-right">+ 5.00 €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subtotal that will be paid by the customer</td>
|
||||
<td class="text-right">105.00 €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PayPal calculates its fee as 5% of 105 €</td>
|
||||
<td class="text-right">- 5.25 €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>End total that is on your bank account</td>
|
||||
<td class="text-right"><strong>99.75 €</strong></td>
|
||||
</tr>
|
||||
</table>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Method B (default): Calculate the fee from the total value including the fee.</strong> For a ticket
|
||||
price of 100 €, this will lead to the following calculation:
|
||||
<table class="table" style="width: auto;">
|
||||
<tr>
|
||||
<td>Ticket price</td>
|
||||
<td class="text-right">100.00 €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>pretix calculates the fee as 100/(100 - 5)% of 100 €</td>
|
||||
<td class="text-right">+ 5.26 €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subtotal that will be paid by the customer</td>
|
||||
<td class="text-right">105.26 €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PayPal calculates its fee as 5% of 105.26 €</td>
|
||||
<td class="text-right">- 5.26 €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>End total that is on your bank account</td>
|
||||
<td class="text-right"><strong>100.00 €</strong></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="alert-warning alert">
|
||||
Due to the various rounding steps performed by us and by the payment provider, the end total on
|
||||
your bank account might stil vary by one cent.
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
@@ -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<topic>[^.]+)$', help.HelpView.as_view(), name='help'),
|
||||
]
|
||||
|
||||
23
src/pretix/control/views/help.py
Normal file
23
src/pretix/control/views/help.py
Normal file
@@ -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('')
|
||||
54
src/tests/base/test_payment.py
Normal file
54
src/tests/base/test_payment.py
Normal file
@@ -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')
|
||||
19
src/tests/testdummy/payment.py
Normal file
19
src/tests/testdummy/payment.py
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user