Add ability for users to change their bank accounts `

parent ae180f9f
......@@ -33,6 +33,14 @@ payments.apps module
:undoc-members:
:show-inheritance:
payments.forms module
---------------------
.. automodule:: payments.forms
:members:
:undoc-members:
:show-inheritance:
payments.models module
----------------------
......@@ -49,6 +57,22 @@ payments.services module
:undoc-members:
:show-inheritance:
payments.urls module
--------------------
.. automodule:: payments.urls
:members:
:undoc-members:
:show-inheritance:
payments.views module
---------------------
.. automodule:: payments.views
:members:
:undoc-members:
:show-inheritance:
payments.widgets module
-----------------------
......
from django.db import migrations
def forwards_func(apps, schema_editor):
Membership = apps.get_model('members', 'membership')
db_alias = schema_editor.connection.alias
Membership.objects.using(db_alias).filter(
type='supporter').update(type='benefactor')
def reverse_func(apps, schema_editor):
Membership = apps.get_model('members', 'membership')
db_alias = schema_editor.connection.alias
Membership.objects.using(db_alias).filter(
type='benefactor').update(type='supporter')
class Migration(migrations.Migration):
dependencies = [
('members', '0029_profile_address_country'),
]
operations = [
migrations.RunPython(forwards_func, reverse_func),
]
......@@ -3,14 +3,17 @@ import csv
from django.contrib import admin, messages
from django.contrib.admin.utils import model_ngettext
from django.db.models.query_utils import Q
from django.http import HttpResponse
from django.urls import path
from django.urls import path, reverse
from django.utils import timezone
from django.utils.html import format_html
from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy as _
from payments import services, admin_views
from .models import Payment
from payments.forms import BankAccountAdminForm
from .models import Payment, BankAccount
def _show_message(admin, request, n, message, error):
......@@ -169,3 +172,55 @@ class PaymentAdmin(admin.ModelAdmin):
])
return response
export_csv.short_description = _('Export')
class ValidAccountFilter(admin.SimpleListFilter):
"""Filter the memberships by whether they are active or not"""
title = _('mandates')
parameter_name = 'active'
def lookups(self, request, model_name):
return (
('valid', _('Valid')),
('invalid', _('Invalid')),
('none', _('None')),
)
def queryset(self, request, queryset):
now = timezone.now()
if self.value() == 'valid':
return queryset.filter(Q(valid_from__gte=now) &
Q(valid_until=None) |
Q(valid_until__lt=now))
if self.value() == 'invalid':
return queryset.filter(valid_until__gte=now)
if self.value() == 'none':
return queryset.filter(valid_from=None)
@admin.register(BankAccount)
class BankAccountAdmin(admin.ModelAdmin):
""" Manage bank accounts """
list_display = ('iban', 'initials', 'last_name',
'owner_link', 'valid_from', 'valid_until')
list_filter = (ValidAccountFilter,)
fields = ('created_at', 'owner', 'iban', 'bic', 'initials', 'last_name',
'mandate_no', 'valid_from', 'valid_until', 'signature')
readonly_fields = ('created_at',)
search_fields = ('owner__username', 'owner__first_name',
'owner__last_name', 'iban')
form = BankAccountAdminForm
def owner_link(self, obj):
if obj.owner:
return format_html("<a href='{}'>{}</a>",
reverse('admin:auth_user_change',
args=[obj.owner.pk]),
obj.owner.get_full_name())
return ''
owner_link.admin_order_field = 'owner'
owner_link.short_description = _('owner')
from django import forms
from payments.models import BankAccount
from payments.widgets import SignatureWidget
from django.utils.translation import gettext as _
class BankAccountForm(forms.ModelForm):
"""
Custom admin form for BankAccount model
to add the widget for the signature
"""
direct_debit = forms.BooleanField(
required=False,
label=_('I want to use this account for direct debits')
)
class Meta:
fields = ('initials', 'last_name', 'iban', 'bic',
'signature', 'valid_from', 'mandate_no', 'owner')
model = BankAccount
widgets = {
'signature': SignatureWidget(),
}
class BankAccountAdminForm(forms.ModelForm):
"""
Custom admin form for BankAccount model
to add the widget for the signature
"""
class Meta:
fields = '__all__'
model = BankAccount
widgets = {
'signature': SignatureWidget(),
}
# Generated by Django 2.2 on 2019-04-27 19:41
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import localflavor.generic.models
import uuid
class Migration(migrations.Migration):
dependencies = [
('members', '0031_benefactor_model_value'),
('payments', '0003_auto_20190204_2111'),
]
operations = [
migrations.CreateModel(
name='BankAccount',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created at')),
('initials', models.CharField(max_length=20, verbose_name='initials')),
('last_name', models.CharField(max_length=255, verbose_name='last name')),
('iban', localflavor.generic.models.IBANField(include_countries=('AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GI', 'GR', 'HR', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'SM'), max_length=34, use_nordea_extensions=False, verbose_name='IBAN')),
('bic', localflavor.generic.models.BICField(blank=True, help_text='This field is optional for Dutch bank accounts.', max_length=11, null=True, verbose_name='BIC')),
('valid_from', models.DateField(blank=True, null=True, verbose_name='valid from')),
('valid_until', models.DateField(blank=True, null=True, verbose_name='valid until')),
('signature', models.TextField(blank=True, null=True, verbose_name='signature')),
('mandate_no', models.CharField(blank=True, max_length=255, null=True, unique=True, verbose_name='mandate number')),
('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bank_accounts', to='members.Member', verbose_name='owner')),
],
options={
'ordering': ('created_at',),
},
),
]
......@@ -2,10 +2,13 @@
import uuid
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from localflavor.generic.countries.sepa import IBAN_SEPA_COUNTRIES
from localflavor.generic.models import IBANField, BICField
class Payment(models.Model):
......@@ -93,3 +96,135 @@ class Payment(models.Model):
def __str__(self):
return _("Payment of {amount}").format(amount=self.amount)
class BankAccount(models.Model):
"""
Describes a bank account
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_at = models.DateTimeField(_('created at'), default=timezone.now)
owner = models.ForeignKey(
to='members.Member',
verbose_name=_('owner'),
related_name='bank_accounts',
on_delete=models.SET_NULL,
blank=False,
null=True,
)
initials = models.CharField(
verbose_name=_('initials'),
max_length=20,
)
last_name = models.CharField(
verbose_name=_('last name'),
max_length=255,
)
iban = IBANField(
verbose_name=_('IBAN'),
include_countries=IBAN_SEPA_COUNTRIES,
)
bic = BICField(
verbose_name=_('BIC'),
blank=True,
null=True,
help_text=_('This field is optional for Dutch bank accounts.')
)
valid_from = models.DateField(
verbose_name=_('valid from'),
blank=True,
null=True,
)
valid_until = models.DateField(
verbose_name=_('valid until'),
blank=True,
null=True,
)
signature = models.TextField(
verbose_name=_('signature'),
blank=True,
null=True,
)
mandate_no = models.CharField(
verbose_name=_('mandate number'),
max_length=255,
blank=True,
null=True,
unique=True
)
def clean(self):
super().clean()
errors = {}
if self.bic is None and self.iban[0:2] != 'NL':
errors.update({
'bic': _('This field is required for foreign bank accounts.')
})
if not self.owner:
errors.update({
'owner': _('This field is required.')
})
mandate_fields = [
('valid_from', self.valid_from),
('signature', self.signature),
('mandate_no', self.mandate_no)
]
if (any(not field[1] for field in mandate_fields) and any(
field[1] for field in mandate_fields)):
for field in mandate_fields:
if not field[1]:
errors.update({
field[0]: _('This field is required '
'to complete the mandate.')
})
if (self.valid_from and self.valid_until and
self.valid_from > self.valid_until):
errors.update({
'valid_until': _('This date cannot be before the from date.')
})
if self.valid_until and not self.valid_from:
errors.update({
'valid_until': _('This field cannot have a value.')
})
if errors:
raise ValidationError(errors)
@property
def name(self):
return f'{self.initials} {self.last_name}'
@property
def valid(self):
if self.valid_from and self.valid_until:
return self.valid_from < timezone.now().date() < self.valid_until
return self.valid_from and self.valid_from < timezone.now().date()
def __str__(self):
return f'{self.iban} - {self.owner.get_full_name()}'
class Meta:
ordering = ('created_at',)
.signature-row .signature-container {
float: left;
border-radius: 4px;
border: 1px solid #ccc;
}
.signature-row:after {
content: "";
display: table;
clear: both;
}
#payments-account-form {
#id_initials {
flex: .3 1 auto;
}
#id_address_postal_code {
flex: .2 1 auto;
}
#id_signature_canvas {
height: 10rem;
}
#canvas-container {
position: relative;
.canvas-btn {
position: absolute;
height: 2rem;
width: 2rem;
top: 0.25rem;
color: $black;
display: inline-block;
font-weight: 400;
text-align: center;
white-space: nowrap;
vertical-align: middle;
font-size: 14px;
line-height: 2.5;
&#canvas-undo-btn {
right: 2.75rem;
}
&#canvas-clear-btn {
right: 0.5rem;
}
}
}
}
$(function () {
var signatureField = $('#id_signature');
var signatureCanvas = document.getElementById("id_signature_canvas");
var signaturePad = new SignaturePad(signatureCanvas);
function canvasToField() {
if (signaturePad.toData().length) {
signatureField.val(signaturePad.toDataURL());
}
}
signaturePad.fromDataURL(signatureField.val());
signaturePad.onEnd = canvasToField;
function resizeCanvas() {
var ratio = Math.max(window.devicePixelRatio || 1, 1);
signatureCanvas.width = signatureCanvas.offsetWidth * ratio;
signatureCanvas.height = signatureCanvas.offsetHeight * ratio;
signatureCanvas.getContext("2d").scale(ratio, ratio);
signaturePad.clear(); // otherwise isEmpty() might return incorrect value
}
window.addEventListener("resize", resizeCanvas);
resizeCanvas();
$('#canvas-undo-btn').click(function (e) {
e.preventDefault();
var data = signaturePad.toData();
if (data) {
data.pop();
signaturePad.fromData(data);
if (data.length) {
canvasToField();
} else {
signatureField.val('');
}
}
});
$('#canvas-clear-btn').click(function (e) {
e.preventDefault();
signaturePad.clear();
signatureField.val('');
});
var ddCheckbox = $('#id_direct_debit');
function showDirectDebitFields() {
if (ddCheckbox.is(':checked')) {
$('.direct-debit-fields').removeClass('d-none');
$('.normal-fields').addClass('col-lg-6');
} else {
$('.direct-debit-fields').addClass('d-none');
$('.normal-fields').removeClass('col-lg-6');
}
}
showDirectDebitFields();
ddCheckbox.change(showDirectDebitFields);
// // Returns signature image as data URL (see https://mdn.io/todataurl for the list of possible parameters)
// signaturePad.toDataURL(); // save image as PNG
// signaturePad.toDataURL("image/jpeg"); // save image as JPEG
// signaturePad.toDataURL("image/svg+xml"); // save image as SVG
//
// // Draws signature image from data URL.
// // NOTE: This method does not populate internal data structure that represents drawn signature. Thus, after using #fromDataURL, #toData won't work properly.
// signaturePad.fromDataURL("data:image/png;base64,iVBORw0K...");
//
// // Returns signature image as an array of point groups
// const data = signaturePad.toData();
//
// // Draws signature image from an array of point groups
// signaturePad.fromData(data);
//
// // Clears the canvas
// signaturePad.clear();
//
// // Returns true if canvas is empty, otherwise returns false
// signaturePad.isEmpty();
//
// // Unbinds all event handlers
// signaturePad.off();
//
// // Rebinds all event handlers
// signaturePad.on();
});
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.SignaturePad=e()}(this,function(){"use strict";function t(t,e,i){this.x=t,this.y=e,this.time=i||(new Date).getTime()}function e(t,e,i,o){this.startPoint=t,this.control1=e,this.control2=i,this.endPoint=o}function i(t,e,i){var o,n,s,r=null,h=0;i||(i={});var a=function(){h=!1===i.leading?0:Date.now(),r=null,s=t.apply(o,n),r||(o=n=null)};return function(){var c=Date.now();h||!1!==i.leading||(h=c);var d=e-(c-h);return o=this,n=arguments,d<=0||d>e?(r&&(clearTimeout(r),r=null),h=c,s=t.apply(o,n),r||(o=n=null)):r||!1===i.trailing||(r=setTimeout(a,d)),s}}function o(t,e){var n=this,s=e||{};this.velocityFilterWeight=s.velocityFilterWeight||.7,this.minWidth=s.minWidth||.5,this.maxWidth=s.maxWidth||2.5,this.throttle="throttle"in s?s.throttle:16,this.minDistance="minDistance"in s?s.minDistance:5,this.throttle?this._strokeMoveUpdate=i(o.prototype._strokeUpdate,this.throttle):this._strokeMoveUpdate=o.prototype._strokeUpdate,this.dotSize=s.dotSize||function(){return(this.minWidth+this.maxWidth)/2},this.penColor=s.penColor||"black",this.backgroundColor=s.backgroundColor||"rgba(0,0,0,0)",this.onBegin=s.onBegin,this.onEnd=s.onEnd,this._canvas=t,this._ctx=t.getContext("2d"),this.clear(),this._handleMouseDown=function(t){1===t.which&&(n._mouseButtonDown=!0,n._strokeBegin(t))},this._handleMouseMove=function(t){n._mouseButtonDown&&n._strokeMoveUpdate(t)},this._handleMouseUp=function(t){1===t.which&&n._mouseButtonDown&&(n._mouseButtonDown=!1,n._strokeEnd(t))},this._handleTouchStart=function(t){if(1===t.targetTouches.length){var e=t.changedTouches[0];n._strokeBegin(e)}},this._handleTouchMove=function(t){t.preventDefault();var e=t.targetTouches[0];n._strokeMoveUpdate(e)},this._handleTouchEnd=function(t){t.target===n._canvas&&(t.preventDefault(),n._strokeEnd(t))},this.on()}return t.prototype.velocityFrom=function(t){return this.time!==t.time?this.distanceTo(t)/(this.time-t.time):1},t.prototype.distanceTo=function(t){return Math.sqrt(Math.pow(this.x-t.x,2)+Math.pow(this.y-t.y,2))},t.prototype.equals=function(t){return this.x===t.x&&this.y===t.y&&this.time===t.time},e.prototype.length=function(){for(var t=0,e=void 0,i=void 0,o=0;o<=10;o+=1){var n=o/10,s=this._point(n,this.startPoint.x,this.control1.x,this.control2.x,this.endPoint.x),r=this._point(n,this.startPoint.y,this.control1.y,this.control2.y,this.endPoint.y);if(o>0){var h=s-e,a=r-i;t+=Math.sqrt(h*h+a*a)}e=s,i=r}return t},e.prototype._point=function(t,e,i,o,n){return e*(1-t)*(1-t)*(1-t)+3*i*(1-t)*(1-t)*t+3*o*(1-t)*t*t+n*t*t*t},o.prototype.clear=function(){var t=this._ctx,e=this._canvas;t.fillStyle=this.backgroundColor,t.clearRect(0,0,e.width,e.height),t.fillRect(0,0,e.width,e.height),this._data=[],this._reset(),this._isEmpty=!0},o.prototype.fromDataURL=function(t){var e=this,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=new Image,n=i.ratio||window.devicePixelRatio||1,s=i.width||this._canvas.width/n,r=i.height||this._canvas.height/n;this._reset(),o.src=t,o.onload=function(){e._ctx.drawImage(o,0,0,s,r)},this._isEmpty=!1},o.prototype.toDataURL=function(t){var e;switch(t){case"image/svg+xml":return this._toSVG();default:for(var i=arguments.length,o=Array(i>1?i-1:0),n=1;n<i;n++)o[n-1]=arguments[n];return(e=this._canvas).toDataURL.apply(e,[t].concat(o))}},o.prototype.on=function(){this._handleMouseEvents(),this._handleTouchEvents()},o.prototype.off=function(){this._canvas.removeEventListener("mousedown",this._handleMouseDown),this._canvas.removeEventListener("mousemove",this._handleMouseMove),document.removeEventListener("mouseup",this._handleMouseUp),this._canvas.removeEventListener("touchstart",this._handleTouchStart),this._canvas.removeEventListener("touchmove",this._handleTouchMove),this._canvas.removeEventListener("touchend",this._handleTouchEnd)},o.prototype.isEmpty=function(){return this._isEmpty},o.prototype._strokeBegin=function(t){this._data.push([]),this._reset(),this._strokeUpdate(t),"function"==typeof this.onBegin&&this.onBegin(t)},o.prototype._strokeUpdate=function(t){var e=t.clientX,i=t.clientY,o=this._createPoint(e,i),n=this._data[this._data.length-1],s=n&&n[n.length-1],r=s&&o.distanceTo(s)<this.minDistance;if(!s||!r){var h=this._addPoint(o),a=h.curve,c=h.widths;a&&c&&this._drawCurve(a,c.start,c.end),this._data[this._data.length-1].push({x:o.x,y:o.y,time:o.time,color:this.penColor})}},o.prototype._strokeEnd=function(t){var e=this.points.length>2,i=this.points[0];if(!e&&i&&this._drawDot(i),i){var o=this._data[this._data.length-1],n=o[o.length-1];i.equals(n)||o.push({x:i.x,y:i.y,time:i.time,color:this.penColor})}"function"==typeof this.onEnd&&this.onEnd(t)},o.prototype._handleMouseEvents=function(){this._mouseButtonDown=!1,this._canvas.addEventListener("mousedown",this._handleMouseDown),this._canvas.addEventListener("mousemove",this._handleMouseMove),document.addEventListener("mouseup",this._handleMouseUp)},o.prototype._handleTouchEvents=function(){this._canvas.style.msTouchAction="none",this._canvas.style.touchAction="none",this._canvas.addEventListener("touchstart",this._handleTouchStart),this._canvas.addEventListener("touchmove",this._handleTouchMove),this._canvas.addEventListener("touchend",this._handleTouchEnd)},o.prototype._reset=function(){this.points=[],this._lastVelocity=0,this._lastWidth=(this.minWidth+this.maxWidth)/2,this._ctx.fillStyle=this.penColor},o.prototype._createPoint=function(e,i,o){var n=this._canvas.getBoundingClientRect();return new t(e-n.left,i-n.top,o||(new Date).getTime())},o.prototype._addPoint=function(t){var i=this.points,o=void 0;if(i.push(t),i.length>2){3===i.length&&i.unshift(i[0]),o=this._calculateCurveControlPoints(i[0],i[1],i[2]);var n=o.c2;o=this._calculateCurveControlPoints(i[1],i[2],i[3]);var s=o.c1,r=new e(i[1],n,s,i[2]),h=this._calculateCurveWidths(r);return i.shift(),{curve:r,widths:h}}return{}},o.prototype._calculateCurveControlPoints=function(e,i,o){var n=e.x-i.x,s=e.y-i.y,r=i.x-o.x,h=i.y-o.y,a={x:(e.x+i.x)/2,y:(e.y+i.y)/2},c={x:(i.x+o.x)/2,y:(i.y+o.y)/2},d=Math.sqrt(n*n+s*s),l=Math.sqrt(r*r+h*h),u=a.x-c.x,v=a.y-c.y,p=l/(d+l),_={x:c.x+u*p,y:c.y+v*p},y=i.x-_.x,f=i.y-_.y;return{c1:new t(a.x+y,a.y+f),c2:new t(c.x+y,c.y+f)}},o.prototype._calculateCurveWidths=function(t){var e=t.startPoint,i=t.endPoint,o={start:null,end:null},n=this.velocityFilterWeight*i.velocityFrom(e)+(1-this.velocityFilterWeight)*this._lastVelocity,s=this._strokeWidth(n);return o.start=this._lastWidth,o.end=s,this._lastVelocity=n,this._lastWidth=s,o},o.prototype._strokeWidth=function(t){return Math.max(this.maxWidth/(t+1),this.minWidth)},o.prototype._drawPoint=function(t,e,i){var o=this._ctx;o.moveTo(t,e),o.arc(t,e,i,0,2*Math.PI,!1),this._isEmpty=!1},o.prototype._drawCurve=function(t,e,i){var o=this._ctx,n=i-e,s=Math.floor(t.length());o.beginPath();for(var r=0;r<s;r+=1){var h=r/s,a=h*h,c=a*h,d=1-h,l=d*d,u=l*d,v=u*t.startPoint.x;v+=3*l*h*t.control1.x,v+=3*d*a*t.control2.x,v+=c*t.endPoint.x;var p=u*t.startPoint.y;p+=3*l*h*t.control1.y,p+=3*d*a*t.control2.y,p+=c*t.endPoint.y;var _=e+c*n;this._drawPoint(v,p,_)}o.closePath(),o.fill()},o.prototype._drawDot=function(t){var e=this._ctx,i="function"==typeof this.dotSize?this.dotSize():this.dotSize;e.beginPath(),this._drawPoint(t.x,t.y,i),e.closePath(),e.fill()},o.prototype._fromData=function(e,i,o){for(var n=0;n<e.length;n+=1){var s=e[n];if(s.length>1)for(var r=0;r<s.length;r+=1){var h=s[r],a=new t(h.x,h.y,h.time),c=h.color;if(0===r)this.penColor=c,this._reset(),this._addPoint(a);else if(r!==s.length-1){var d=this._addPoint(a),l=d.curve,u=d.widths;l&&u&&i(l,u,c)}}else{this._reset();o(s[0])}}},o.prototype._toSVG=function(){var t=this,e=this._data,i=this._canvas,o=Math.max(window.devicePixelRatio||1,1),n=i.width/o,s=i.height/o,r=document.createElementNS("http://www.w3.org/2000/svg","svg");r.setAttributeNS(null,"width",i.width),r.setAttributeNS(null,"height",i.height),this._fromData(e,function(t,e,i){var o=document.createElement("path");if(!(isNaN(t.control1.x)||isNaN(t.control1.y)||isNaN(t.control2.x)||isNaN(t.control2.y))){var n="M "+t.startPoint.x.toFixed(3)+","+t.startPoint.y.toFixed(3)+" C "+t.control1.x.toFixed(3)+","+t.control1.y.toFixed(3)+" "+t.control2.x.toFixed(3)+","+t.control2.y.toFixed(3)+" "+t.endPoint.x.toFixed(3)+","+t.endPoint.y.toFixed(3);o.setAttribute("d",n),o.setAttribute("stroke-width",(2.25*e.end).toFixed(3)),o.setAttribute("stroke",i),o.setAttribute("fill","none"),o.setAttribute("stroke-linecap","round"),r.appendChild(o)}},function(e){var i=document.createElement("circle"),o="function"==typeof t.dotSize?t.dotSize():t.dotSize;i.setAttribute("r",o),i.setAttribute("cx",e.x),i.setAttribute("cy",e.y),i.setAttribute("fill",e.color),r.appendChild(i)});var h='<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 '+n+" "+s+'" width="'+n+'" height="'+s+'">',a=r.innerHTML;if(void 0===a){var c=document.createElement("dummy"),d=r.childNodes;c.innerHTML="";for(var l=0;l<d.length;l+=1)c.appendChild(d[l].cloneNode(!0));a=c.innerHTML}var u=h+a+"</svg>";return"data:image/svg+xml;base64,"+btoa(u)},o.prototype.fromData=function(t){var e=this;this.clear(),this._fromData(t,function(t,i){return e._drawCurve(t,i.start,i.end)},function(t){return e._drawDot(t)}),this._data=t},o.prototype.toData=function(){return this._data},o});
This diff is collapsed.
{% extends "base.html" %}
{% load i18n static bootstrap4 compress alert %}
{% block title %}{% trans "bank accounts"|capfirst %} —
{% trans "finances"|capfirst %} — {{ block.super }}{% endblock %}
{% block opengraph_title %}{% trans "add bank account"|capfirst %} —
{% trans "finances"|capfirst %} — {{ block.super }}{% endblock %}
{% block body %}
<section class="page-section" id="payments-account-overview">
<div class="container">
<h1 class="text-center section-title">{% trans "bank accounts" %}</h1>
{% if messages %}
{% for message in messages %}
{% alert message.tags message %}
{% endfor %}
{% endif %}
<p class="text-center">
{% blocktrans trimmed %}
Thalia keeps your bank account saved to your profile to
ease the processing of digital financial transactions like
reimbursements or invoices. You can change the bank account
saved to your profile and manage your authorisation for
direct debit transactions.
{% endblocktrans %}
</p>
<p class="text-center">
{% blocktrans trimmed %}
We only keep a history of bank accounts with a direct debit
authorisation. Bank accounts that do not have such an
authorisation are not kept when you change your account.
Changing your bank account automatically revokes any
previously given direct debit authorisations.
{% endblocktrans %}
</p>
{# TODO: Add a mention to payments functionality when we build it #}
{# https://gitlab.science.ru.nl/thalia/concrexit/issues/632 #}
{# <p class="text-center">#}
{# {% blocktrans trimmed %}#}
{# If you have a bank account with an authorisation for#}
{# direct debit you can use it to pay digitally.#}
{# {% endblocktrans %}#}
{# </p>#}
{% if object_list %}
<p class="text-center mb-4">
<a href="{% url 'payments:bankaccount-add' %}"
class="btn btn-primary">Change bank account</a>