mirror of
https://github.com/pretix/pretix.git
synced 2026-05-03 14:54:04 +00:00
Allow to store strucutred SEPA bank transfer details
This commit is contained in:
@@ -64,6 +64,8 @@ The provider class
|
||||
|
||||
.. autoattribute:: settings_form_fields
|
||||
|
||||
.. automethod:: settings_form_clean
|
||||
|
||||
.. automethod:: settings_content_render
|
||||
|
||||
.. automethod:: is_allowed
|
||||
|
||||
@@ -267,6 +267,15 @@ class BasePaymentProvider:
|
||||
d['_restricted_countries']._as_type = list
|
||||
return d
|
||||
|
||||
def settings_form_clean(self, cleaned_data):
|
||||
"""
|
||||
Overriding this method allows you to inject custom validation into the settings form.
|
||||
|
||||
:param cleaned_data: Form data as per previous validations.
|
||||
:return: Please return the modified cleaned_data
|
||||
"""
|
||||
return cleaned_data
|
||||
|
||||
def settings_content_render(self, request: HttpRequest) -> str:
|
||||
"""
|
||||
When the event's administrator visits the event configuration
|
||||
|
||||
@@ -489,6 +489,7 @@ class ProviderForm(SettingsForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.settingspref = kwargs.pop('settingspref')
|
||||
self.provider = kwargs.pop('provider', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def prepare_fields(self):
|
||||
@@ -515,6 +516,9 @@ class ProviderForm(SettingsForm):
|
||||
val = cleaned_data.get(k)
|
||||
if v._required and not val:
|
||||
self.add_error(k, _('This field is required.'))
|
||||
if self.provider:
|
||||
cleaned_data = self.provider.settings_form_clean(cleaned_data)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class InvoiceSettingsForm(SettingsForm):
|
||||
@@ -1189,7 +1193,13 @@ class QuickSetupForm(I18nForm):
|
||||
"bank statements to process the payments within pretix, or mark them as paid manually."),
|
||||
required=False
|
||||
)
|
||||
payment_banktransfer_bank_details = BankTransfer.form_field(required=False)
|
||||
btf = BankTransfer.form_fields()
|
||||
payment_banktransfer_bank_details_type = btf['bank_details_type']
|
||||
payment_banktransfer_bank_details_sepa_name = btf['bank_details_sepa_name']
|
||||
payment_banktransfer_bank_details_sepa_iban = btf['bank_details_sepa_iban']
|
||||
payment_banktransfer_bank_details_sepa_bic = btf['bank_details_sepa_bic']
|
||||
payment_banktransfer_bank_details_sepa_bank = btf['bank_details_sepa_bank']
|
||||
payment_banktransfer_bank_details = btf['bank_details']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.obj = kwargs.pop('event', None)
|
||||
@@ -1199,6 +1209,16 @@ class QuickSetupForm(I18nForm):
|
||||
if not self.obj.settings.payment_stripe_connect_client_id:
|
||||
del self.fields['payment_stripe__enabled']
|
||||
self.fields['payment_banktransfer_bank_details'].required = False
|
||||
for f in self.fields.values():
|
||||
if 'data-required-if' in f.widget.attrs:
|
||||
del f.widget.attrs['data-required-if']
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
if cleaned_data.get('payment_banktransfer__enabled'):
|
||||
provider = BankTransfer(self.obj)
|
||||
cleaned_data = provider.settings_form_clean(cleaned_data)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class QuickSetupProductForm(I18nForm):
|
||||
|
||||
@@ -4,25 +4,27 @@
|
||||
{% load formset_tags %}
|
||||
{% block title %}{{ request.event.name }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="quick-setup-step">
|
||||
<div class="quick-icon">
|
||||
<span class="fa fa-fw fa-check-circle text-success"></span>
|
||||
</div>
|
||||
<div class="quick-content">
|
||||
{% if request.method == "GET" %}
|
||||
<div class="quick-setup-step">
|
||||
<div class="quick-icon">
|
||||
<span class="fa fa-fw fa-check-circle text-success"></span>
|
||||
</div>
|
||||
<div class="quick-content">
|
||||
|
||||
<h2>{% trans "Congratulations!" %}</h2>
|
||||
<p>
|
||||
<strong>{% trans "You just created an event!" %}</strong>
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
You can scroll down and create your first ticket products quickly, or you can use the navigation
|
||||
on the left to modify the settings of your event in much more detail.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<div class="clearfix"></div>
|
||||
<h2>{% trans "Congratulations!" %}</h2>
|
||||
<p>
|
||||
<strong>{% trans "You just created an event!" %}</strong>
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
You can scroll down and create your first ticket products quickly, or you can use the navigation
|
||||
on the left to modify the settings of your event in much more detail.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
@@ -157,6 +159,11 @@
|
||||
</p>
|
||||
{% bootstrap_field form.payment_banktransfer__enabled layout="control" label_class="sr-only" field_class="col-md-12" %}
|
||||
<div data-display-dependency="#id_payment_banktransfer__enabled">
|
||||
{% bootstrap_field form.payment_banktransfer_bank_details_type layout="control" %}
|
||||
{% bootstrap_field form.payment_banktransfer_bank_details_sepa_name layout="control" %}
|
||||
{% bootstrap_field form.payment_banktransfer_bank_details_sepa_iban layout="control" %}
|
||||
{% bootstrap_field form.payment_banktransfer_bank_details_sepa_bic layout="control" %}
|
||||
{% bootstrap_field form.payment_banktransfer_bank_details_sepa_bank layout="control" %}
|
||||
{% bootstrap_field form.payment_banktransfer_bank_details layout="control" %}
|
||||
</div>
|
||||
{% if form.payment_stripe__enabled %}
|
||||
|
||||
@@ -270,7 +270,8 @@ class PaymentProviderSettings(EventSettingsViewMixin, EventPermissionRequiredMix
|
||||
form = ProviderForm(
|
||||
obj=self.request.event,
|
||||
settingspref=self.provider.settings.get_prefix(),
|
||||
data=(self.request.POST if self.request.method == 'POST' else None)
|
||||
data=(self.request.POST if self.request.method == 'POST' else None),
|
||||
provider=self.provider
|
||||
)
|
||||
form.fields = OrderedDict(
|
||||
[
|
||||
@@ -1193,6 +1194,7 @@ class QuickSetupView(FormView):
|
||||
if form.is_valid() and self.formset.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||
return self.form_invalid(form)
|
||||
|
||||
@transaction.atomic
|
||||
@@ -1224,8 +1226,12 @@ class QuickSetupView(FormView):
|
||||
data={'plugin': 'pretix.plugins.banktransfer'})
|
||||
plugins_active.append('pretix.plugins.banktransfer')
|
||||
self.request.event.settings.payment_banktransfer__enabled = True
|
||||
self.request.event.settings.payment_banktransfer_bank_details = form.cleaned_data[
|
||||
'payment_banktransfer_bank_details']
|
||||
for f in ('bank_details', 'bank_details_type', 'bank_details_sepa_name', 'bank_details_sepa_iban',
|
||||
'bank_details_sepa_bic', 'bank_details_sepa_bank'):
|
||||
self.request.event.settings.set(
|
||||
'payment_banktransfer_%s' % f,
|
||||
form.cleaned_data['payment_banktransfer_%s' % f]
|
||||
)
|
||||
|
||||
if form.cleaned_data.get('payment_stripe__enabled', None):
|
||||
if 'pretix.plugins.stripe' not in plugins_active:
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 2.1 on 2018-10-23 22:09
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def set_type(app, schema_editor):
|
||||
EventSettingsStore = app.get_model('pretixbase', 'Event_SettingsStore')
|
||||
|
||||
for setting in EventSettingsStore.objects.filter(key='payment_banktransfer_bank_details').select_related('object'):
|
||||
EventSettingsStore.objects.create(
|
||||
object=setting.object,
|
||||
key='payment_banktransfer_bank_details_type',
|
||||
value='other'
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('banktransfer', '0004_auto_20170619_1125'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(set_type, migrations.RunPython.noop)
|
||||
]
|
||||
@@ -3,11 +3,13 @@ import textwrap
|
||||
from collections import OrderedDict
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.http import HttpRequest
|
||||
from django.template.loader import get_template
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from i18nfield.fields import I18nFormField, I18nTextarea
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from localflavor.generic.forms import BICFormField, IBANFormField
|
||||
|
||||
from pretix.base.models import OrderPayment
|
||||
from pretix.base.payment import BasePaymentProvider
|
||||
@@ -19,30 +21,80 @@ class BankTransfer(BasePaymentProvider):
|
||||
abort_pending_allowed = True
|
||||
|
||||
@staticmethod
|
||||
def form_field(**kwargs):
|
||||
return I18nFormField(
|
||||
label=_('Bank account details'),
|
||||
widget=I18nTextarea,
|
||||
help_text=_('Include everything that your customers need to send you a bank transfer payment. Within SEPA '
|
||||
'countries, IBAN, BIC and account owner should suffice. If you have lots of international '
|
||||
'customers, they might also need your full address and your bank\'s full address.'),
|
||||
widget_kwargs={'attrs': {
|
||||
'rows': '4',
|
||||
'placeholder': _(
|
||||
'e.g. IBAN: DE12 1234 5678 8765 4321\n'
|
||||
'BIC: GENEXAMPLE1\n'
|
||||
'Account owner: John Doe\n'
|
||||
'Name of Bank: Professional Banking Institute Ltd., London'
|
||||
)
|
||||
}},
|
||||
**kwargs
|
||||
)
|
||||
def form_fields():
|
||||
return OrderedDict([
|
||||
('bank_details_type', forms.ChoiceField(
|
||||
label=_('Bank account type'),
|
||||
widget=forms.RadioSelect,
|
||||
choices=(
|
||||
('sepa', _('SEPA bank account')),
|
||||
('other', _('Other bank account')),
|
||||
),
|
||||
initial='sepa'
|
||||
)),
|
||||
('bank_details_sepa_name', forms.CharField(
|
||||
label=_('Name of account holder'),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
'data-display-dependency': '#id_payment_banktransfer_bank_details_type_0',
|
||||
'data-required-if': '#id_payment_banktransfer_bank_details_type_0'
|
||||
}
|
||||
),
|
||||
required=False
|
||||
)),
|
||||
('bank_details_sepa_iban', IBANFormField(
|
||||
label=_('IBAN'),
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
'data-display-dependency': '#id_payment_banktransfer_bank_details_type_0',
|
||||
'data-required-if': '#id_payment_banktransfer_bank_details_type_0'
|
||||
}
|
||||
),
|
||||
)),
|
||||
('bank_details_sepa_bic', BICFormField(
|
||||
label=_('BIC'),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
'data-display-dependency': '#id_payment_banktransfer_bank_details_type_0',
|
||||
'data-required-if': '#id_payment_banktransfer_bank_details_type_0'
|
||||
}
|
||||
),
|
||||
required=False
|
||||
)),
|
||||
('bank_details_sepa_bank', forms.CharField(
|
||||
label=_('Name of bank'),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
'data-display-dependency': '#id_payment_banktransfer_bank_details_type_0',
|
||||
'data-required-if': '#id_payment_banktransfer_bank_details_type_0'
|
||||
}
|
||||
),
|
||||
required=False
|
||||
)),
|
||||
('bank_details', I18nFormField(
|
||||
label=_('Bank account details'),
|
||||
widget=I18nTextarea,
|
||||
help_text=_(
|
||||
'Include everything else that your customers might need to send you a bank transfer payment. '
|
||||
'If you have lots of international customers, they might need your full address and your '
|
||||
'bank\'s full address.'),
|
||||
widget_kwargs={'attrs': {
|
||||
'rows': '4',
|
||||
'placeholder': _(
|
||||
'For SEPA accounts, you can leave this empty. Otherwise, please add everything that '
|
||||
'your customers need to transfer the money, e.g. account numbers, routing numbers, '
|
||||
'addresses, etc.'
|
||||
),
|
||||
}},
|
||||
required=False
|
||||
))
|
||||
])
|
||||
|
||||
@property
|
||||
def settings_form_fields(self):
|
||||
d = OrderedDict(
|
||||
list(super().settings_form_fields.items()) + [
|
||||
('bank_details', self.form_field()),
|
||||
list(super().settings_form_fields.items()) + list(BankTransfer.form_fields().items()) + [
|
||||
('omit_hyphen', forms.BooleanField(
|
||||
label=_('Do not include a hypen in the payment reference.'),
|
||||
help_text=_('This is required in some countries.'),
|
||||
@@ -52,14 +104,34 @@ class BankTransfer(BasePaymentProvider):
|
||||
]
|
||||
)
|
||||
d.move_to_end('bank_details', last=False)
|
||||
d.move_to_end('bank_details_sepa_bank', last=False)
|
||||
d.move_to_end('bank_details_sepa_bic', last=False)
|
||||
d.move_to_end('bank_details_sepa_iban', last=False)
|
||||
d.move_to_end('bank_details_sepa_name', last=False)
|
||||
d.move_to_end('bank_details_type', last=False)
|
||||
d.move_to_end('_enabled', last=False)
|
||||
return d
|
||||
|
||||
def settings_form_clean(self, cleaned_data):
|
||||
if cleaned_data.get('payment_banktransfer_bank_details_type') == 'sepa':
|
||||
for f in (
|
||||
'bank_details_sepa_name', 'bank_details_sepa_bank', 'bank_details_sepa_bic',
|
||||
'bank_details_sepa_iban'):
|
||||
if not cleaned_data.get('payment_banktransfer_%s' % f):
|
||||
raise ValidationError(
|
||||
{'payment_banktransfer_%s' % f: _('Please fill out your bank account details.')})
|
||||
else:
|
||||
if not cleaned_data.get('payment_banktransfer_bank_details'):
|
||||
raise ValidationError(
|
||||
{'payment_banktransfer_bank_details': _('Please enter your bank account details.')})
|
||||
return cleaned_data
|
||||
|
||||
def payment_form_render(self, request) -> str:
|
||||
template = get_template('pretixplugins/banktransfer/checkout_payment_form.html')
|
||||
ctx = {
|
||||
'request': request,
|
||||
'event': self.event,
|
||||
'settings': self.settings,
|
||||
'details': self.settings.get('bank_details', as_type=LazyI18nString),
|
||||
}
|
||||
return template.render(ctx)
|
||||
@@ -78,11 +150,22 @@ class BankTransfer(BasePaymentProvider):
|
||||
|
||||
def order_pending_mail_render(self, order) -> str:
|
||||
template = get_template('pretixplugins/banktransfer/email/order_pending.txt')
|
||||
bankdetails = []
|
||||
if self.settings.get('bank_details_type') == 'sepa':
|
||||
bankdetails += [
|
||||
_("Account holder"), ": ", self.settings.get('bank_details_sepa_name'), "\n",
|
||||
_("IBAN"), ": ", self.settings.get('bank_details_sepa_iban'), "\n",
|
||||
_("BIC"), ": ", self.settings.get('bank_details_sepa_bic'), "\n",
|
||||
_("Bank"), ": ", self.settings.get('bank_details_sepa_bank'),
|
||||
]
|
||||
if bankdetails and self.settings.get('bank_details', as_type=LazyI18nString):
|
||||
bankdetails.append("\n")
|
||||
bankdetails.append(self.settings.get('bank_details', as_type=LazyI18nString))
|
||||
ctx = {
|
||||
'event': self.event,
|
||||
'order': order,
|
||||
'code': self._code(order),
|
||||
'details': textwrap.indent(str(self.settings.get('bank_details', as_type=LazyI18nString)), ' '),
|
||||
'details': textwrap.indent(''.join(str(i) for i in bankdetails), ' '),
|
||||
}
|
||||
return template.render(ctx)
|
||||
|
||||
@@ -92,6 +175,7 @@ class BankTransfer(BasePaymentProvider):
|
||||
'event': self.event,
|
||||
'code': self._code(payment.order),
|
||||
'order': payment.order,
|
||||
'settings': self.settings,
|
||||
'details': self.settings.get('bank_details', as_type=LazyI18nString),
|
||||
}
|
||||
return template.render(ctx)
|
||||
|
||||
@@ -6,7 +6,15 @@
|
||||
{% endblocktrans %}</p>
|
||||
|
||||
<address>
|
||||
{{ details|linebreaksbr }}<br>
|
||||
{% if settings.bank_details_type == "sepa" %}
|
||||
{% trans "Account holder" %}: {{ settings.bank_details_sepa_name }}<br>
|
||||
{% trans "IBAN" %}: {{ settings.bank_details_sepa_iban }}<br>
|
||||
{% trans "BIC" %}: {{ settings.bank_details_sepa_bic }}<br>
|
||||
{% trans "Bank" %}: {{ settings.bank_details_sepa_bank }}<br>
|
||||
{% endif %}
|
||||
{% if details %}
|
||||
{{ details|linebreaksbr }}<br>
|
||||
{% endif %}
|
||||
<strong>
|
||||
{% trans "We will assign you a personal reference code to use after you completed the order." %}
|
||||
</strong>
|
||||
|
||||
@@ -7,7 +7,15 @@
|
||||
{% endblocktrans %}</p>
|
||||
|
||||
<address>
|
||||
{{ details|linebreaksbr }}<br />
|
||||
{% trans "Amount:" %} {{ order.total|money:event.currency }}<br />
|
||||
{% if settings.bank_details_type == "sepa" %}
|
||||
<strong>{% trans "Account holder" %}:</strong> {{ settings.bank_details_sepa_name }}<br>
|
||||
<strong>{% trans "IBAN" %}:</strong> {{ settings.bank_details_sepa_iban }}<br>
|
||||
<strong>{% trans "BIC" %}:</strong> {{ settings.bank_details_sepa_bic }}<br>
|
||||
<strong>{% trans "Bank" %}:</strong> {{ settings.bank_details_sepa_bank }}<br>
|
||||
{% endif %}
|
||||
{% if details %}
|
||||
{{ details|linebreaksbr }}<br>
|
||||
{% endif %}
|
||||
<strong>{% trans "Amount:" %}</strong> {{ order.total|money:event.currency }}<br />
|
||||
<strong>{% trans "Reference code (important):" %} {{ code }}</strong>
|
||||
</address>
|
||||
|
||||
@@ -212,7 +212,7 @@ var form_handlers = function (el) {
|
||||
update = function (ev) {
|
||||
var enabled = (dependency.attr("type") === 'checkbox' || dependency.attr("type") === 'radio') ? dependency.prop('checked') : !!dependency.val();
|
||||
var $toggling = dependent;
|
||||
if (dependent.tagName === "input") {
|
||||
if (dependent.get(0).tagName.toLowerCase() === "input") {
|
||||
$toggling = dependent.closest('.form-group');
|
||||
}
|
||||
if (ev) {
|
||||
@@ -235,7 +235,9 @@ var form_handlers = function (el) {
|
||||
dependency = $($(this).attr("data-required-if")),
|
||||
update = function (ev) {
|
||||
var enabled = (dependency.attr("type") === 'checkbox' || dependency.attr("type") === 'radio') ? dependency.prop('checked') : !!dependency.val();
|
||||
dependent.prop('required', enabled).closest('.form-group').toggleClass('required', enabled);
|
||||
dependent.prop('required', enabled).closest('.form-group').toggleClass('required', enabled).find('.optional').stop().animate({
|
||||
'opacity': enabled ? 0 : 1
|
||||
}, ev ? 500 : 1);
|
||||
};
|
||||
update();
|
||||
dependency.closest('.form-group').find('input[name=' + dependency.attr("name") + ']').on("change", update);
|
||||
|
||||
@@ -48,6 +48,7 @@ django-countries
|
||||
pyuca # for better sorting of country names in django-countries
|
||||
defusedcsv>=1.0.1
|
||||
vat_moss==0.11.0
|
||||
django-localflavor
|
||||
idna==2.6 # required by current requests
|
||||
django-redis==4.8.*
|
||||
redis==2.10.5
|
||||
|
||||
@@ -133,6 +133,7 @@ setup(
|
||||
'pyuca',
|
||||
'defusedcsv',
|
||||
'vat_moss==0.11.0',
|
||||
'django-localflavor',
|
||||
'django-hijack>=2.1.10,<2.2.0',
|
||||
'django-oauth-toolkit==1.2.*',
|
||||
'idna==2.6', # required by current requests
|
||||
|
||||
Reference in New Issue
Block a user