From e60ff6b777d8eccd24daf0e3b05e68e97e4018fb Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 24 Oct 2018 00:13:49 +0200 Subject: [PATCH] Allow to store strucutred SEPA bank transfer details --- doc/development/api/payment.rst | 2 + src/pretix/base/payment.py | 9 ++ src/pretix/control/forms/event.py | 22 ++- .../pretixcontrol/event/quick_setup.html | 41 +++--- src/pretix/control/views/event.py | 12 +- .../migrations/0005_auto_20181023_2209.py | 25 ++++ src/pretix/plugins/banktransfer/payment.py | 126 +++++++++++++++--- .../banktransfer/checkout_payment_form.html | 10 +- .../pretixplugins/banktransfer/pending.html | 12 +- src/pretix/static/pretixcontrol/js/ui/main.js | 6 +- src/requirements/production.txt | 1 + src/setup.py | 1 + 12 files changed, 220 insertions(+), 47 deletions(-) create mode 100644 src/pretix/plugins/banktransfer/migrations/0005_auto_20181023_2209.py diff --git a/doc/development/api/payment.rst b/doc/development/api/payment.rst index c49070b562..e0ac64ab66 100644 --- a/doc/development/api/payment.rst +++ b/doc/development/api/payment.rst @@ -64,6 +64,8 @@ The provider class .. autoattribute:: settings_form_fields + .. automethod:: settings_form_clean + .. automethod:: settings_content_render .. automethod:: is_allowed diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py index 3a653c6746..65ceee603b 100644 --- a/src/pretix/base/payment.py +++ b/src/pretix/base/payment.py @@ -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 diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 43f166d769..1aff70866e 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -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): diff --git a/src/pretix/control/templates/pretixcontrol/event/quick_setup.html b/src/pretix/control/templates/pretixcontrol/event/quick_setup.html index f6c23f8dd7..a64ea41aa9 100644 --- a/src/pretix/control/templates/pretixcontrol/event/quick_setup.html +++ b/src/pretix/control/templates/pretixcontrol/event/quick_setup.html @@ -4,25 +4,27 @@ {% load formset_tags %} {% block title %}{{ request.event.name }}{% endblock %} {% block content %} -
-
- -
-
+ {% if request.method == "GET" %} +
+
+ +
+
-

{% trans "Congratulations!" %}

-

- {% trans "You just created an event!" %} -

-

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

-
+

{% trans "Congratulations!" %}

+

+ {% trans "You just created an event!" %} +

+

+ {% 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 %} +

+
+
-
+ {% endif %}
{% csrf_token %} @@ -157,6 +159,11 @@

{% bootstrap_field form.payment_banktransfer__enabled layout="control" label_class="sr-only" field_class="col-md-12" %}
+ {% 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" %}
{% if form.payment_stripe__enabled %} diff --git a/src/pretix/control/views/event.py b/src/pretix/control/views/event.py index 7a4faf0a20..6c9dfc3ac1 100644 --- a/src/pretix/control/views/event.py +++ b/src/pretix/control/views/event.py @@ -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: diff --git a/src/pretix/plugins/banktransfer/migrations/0005_auto_20181023_2209.py b/src/pretix/plugins/banktransfer/migrations/0005_auto_20181023_2209.py new file mode 100644 index 0000000000..18c11db787 --- /dev/null +++ b/src/pretix/plugins/banktransfer/migrations/0005_auto_20181023_2209.py @@ -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) + ] diff --git a/src/pretix/plugins/banktransfer/payment.py b/src/pretix/plugins/banktransfer/payment.py index b2cd891b2d..3af10497c7 100644 --- a/src/pretix/plugins/banktransfer/payment.py +++ b/src/pretix/plugins/banktransfer/payment.py @@ -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) diff --git a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/checkout_payment_form.html b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/checkout_payment_form.html index 6e63381ef0..9e277695b5 100644 --- a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/checkout_payment_form.html +++ b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/checkout_payment_form.html @@ -6,7 +6,15 @@ {% endblocktrans %}

- {{ details|linebreaksbr }}
+ {% if settings.bank_details_type == "sepa" %} + {% trans "Account holder" %}: {{ settings.bank_details_sepa_name }}
+ {% trans "IBAN" %}: {{ settings.bank_details_sepa_iban }}
+ {% trans "BIC" %}: {{ settings.bank_details_sepa_bic }}
+ {% trans "Bank" %}: {{ settings.bank_details_sepa_bank }}
+ {% endif %} + {% if details %} + {{ details|linebreaksbr }}
+ {% endif %} {% trans "We will assign you a personal reference code to use after you completed the order." %} diff --git a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/pending.html b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/pending.html index 3a3f63c5f6..5c5409c9cc 100644 --- a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/pending.html +++ b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/pending.html @@ -7,7 +7,15 @@ {% endblocktrans %}

- {{ details|linebreaksbr }}
- {% trans "Amount:" %} {{ order.total|money:event.currency }}
+ {% if settings.bank_details_type == "sepa" %} + {% trans "Account holder" %}: {{ settings.bank_details_sepa_name }}
+ {% trans "IBAN" %}: {{ settings.bank_details_sepa_iban }}
+ {% trans "BIC" %}: {{ settings.bank_details_sepa_bic }}
+ {% trans "Bank" %}: {{ settings.bank_details_sepa_bank }}
+ {% endif %} + {% if details %} + {{ details|linebreaksbr }}
+ {% endif %} + {% trans "Amount:" %} {{ order.total|money:event.currency }}
{% trans "Reference code (important):" %} {{ code }}
diff --git a/src/pretix/static/pretixcontrol/js/ui/main.js b/src/pretix/static/pretixcontrol/js/ui/main.js index 7eb8780f2b..616eed5d0a 100644 --- a/src/pretix/static/pretixcontrol/js/ui/main.js +++ b/src/pretix/static/pretixcontrol/js/ui/main.js @@ -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); diff --git a/src/requirements/production.txt b/src/requirements/production.txt index d708d3ead4..8977744268 100644 --- a/src/requirements/production.txt +++ b/src/requirements/production.txt @@ -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 diff --git a/src/setup.py b/src/setup.py index ba12926cfc..52957619eb 100644 --- a/src/setup.py +++ b/src/setup.py @@ -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