Allow to store strucutred SEPA bank transfer details

This commit is contained in:
Raphael Michel
2018-10-24 00:13:49 +02:00
parent 3a0ef3760c
commit e60ff6b777
12 changed files with 220 additions and 47 deletions

View File

@@ -64,6 +64,8 @@ The provider class
.. autoattribute:: settings_form_fields
.. automethod:: settings_form_clean
.. automethod:: settings_content_render
.. automethod:: is_allowed

View File

@@ -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

View File

@@ -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):

View File

@@ -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 %}

View File

@@ -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:

View File

@@ -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)
]

View File

@@ -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)

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);

View File

@@ -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

View File

@@ -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