diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py
index 0efc9f8d13..d2eaee4869 100644
--- a/src/pretix/base/models/orders.py
+++ b/src/pretix/base/models/orders.py
@@ -1676,7 +1676,7 @@ class OrderPayment(models.Model):
"""
Marks the order as failed and sets info to ``info``, but only if the order is in ``created`` or ``pending``
state. This is equivalent to setting ``state`` to ``OrderPayment.PAYMENT_STATE_FAILED`` and logging a failure,
- but it adds strong database logging since we do not want to report a failure for an order that has just
+ but it adds strong database locking since we do not want to report a failure for an order that has just
been marked as paid.
:param send_mail: Whether an email should be sent to the user about this event (default: ``True``).
"""
diff --git a/src/pretix/plugins/banktransfer/payment.py b/src/pretix/plugins/banktransfer/payment.py
index 0ad0e66a21..e20bb8eff5 100644
--- a/src/pretix/plugins/banktransfer/payment.py
+++ b/src/pretix/plugins/banktransfer/payment.py
@@ -535,9 +535,11 @@ class BankTransfer(BasePaymentProvider):
'eu_barcodes': self.event.currency == 'EUR',
'pending_description': self.settings.get('pending_description', as_type=LazyI18nString),
'details': self.settings.get('bank_details', as_type=LazyI18nString),
+ 'has_invoices': payment.order.invoices.exists(),
+ 'invoice_email_enabled': self.settings.get('invoice_email', as_type=bool),
}
ctx['any_barcodes'] = ctx['swiss_qrbill'] or ctx['eu_barcodes']
- return template.render(ctx)
+ return template.render(ctx, request=request)
def payment_control_render(self, request: HttpRequest, payment: OrderPayment) -> str:
warning = None
diff --git a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/pending.html b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/pending.html
index 9523482c34..e7ab5660c8 100644
--- a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/pending.html
+++ b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/pending.html
@@ -7,6 +7,7 @@
{% load money %}
{% load unidecode %}
{% load rich_text %}
+{% load eventurl %}
{% if pending_description %}
{{ pending_description|rich_text }}
@@ -103,3 +104,28 @@ SCT
{% if swiss_qrbill %}
{% endif %}
+
+{% if invoice_email_enabled and has_invoices %}
+
+
+{% endif %}
\ No newline at end of file
diff --git a/src/pretix/plugins/banktransfer/urls.py b/src/pretix/plugins/banktransfer/urls.py
index 968d0a9f1b..b1ad0ee019 100644
--- a/src/pretix/plugins/banktransfer/urls.py
+++ b/src/pretix/plugins/banktransfer/urls.py
@@ -19,13 +19,19 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# .
#
-from django.urls import re_path
+from django.urls import include, re_path
from pretix.api.urls import orga_router
from pretix.plugins.banktransfer.api import BankImportJobViewSet
from . import views
+event_patterns = [
+ re_path(r'^banktransfer/', include([
+ re_path(r'^(?P[^/][^w]+)/(?P[A-Za-z0-9]+)/mail-invoice/$', views.SendInvoiceMailView.as_view(), name='mail_invoice'),
+ ])),
+]
+
urlpatterns = [
re_path(r'^control/organizer/(?P[^/]+)/banktransfer/import/',
views.OrganizerImportView.as_view(),
diff --git a/src/pretix/plugins/banktransfer/views.py b/src/pretix/plugins/banktransfer/views.py
index 57c2457deb..e4598fa656 100644
--- a/src/pretix/plugins/banktransfer/views.py
+++ b/src/pretix/plugins/banktransfer/views.py
@@ -44,14 +44,18 @@ from typing import Set
from django import forms
from django.contrib import messages
+from django.core.exceptions import ValidationError
+from django.core.validators import validate_email
from django.db import transaction
from django.db.models import Count, Q, QuerySet
-from django.http import FileResponse, JsonResponse
+from django.http import FileResponse, Http404, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
+from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext as _
+from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import DetailView, FormView, ListView, View
from django.views.generic.detail import SingleObjectMixin
from localflavor.generic.forms import BICFormField, IBANFormField
@@ -75,6 +79,8 @@ from pretix.plugins.banktransfer.refund_export import (
build_sepa_xml, get_refund_export_csv,
)
from pretix.plugins.banktransfer.tasks import process_banktransfers
+from pretix.presale.views import EventViewMixin
+from pretix.presale.views.order import OrderDetailMixin
logger = logging.getLogger('pretix.plugins.banktransfer')
@@ -886,3 +892,36 @@ class OrganizerSepaXMLExportView(OrganizerPermissionRequiredMixin, OrganizerDeta
organizer=self.request.organizer,
pk=self.kwargs.get('id')
)
+
+
+@method_decorator(xframe_options_exempt, 'dispatch')
+class SendInvoiceMailView(EventViewMixin, OrderDetailMixin, View):
+ def post(self, request, *args, **kwargs):
+ if not self.order:
+ raise Http404(_('Unknown order code or not authorized to access this order.'))
+ try:
+ validate_email(request.POST['email'])
+ except ValidationError:
+ messages.error(request, _('Please enter a valid email address.'))
+ return redirect(self.get_order_url())
+
+ last_payment = self.order.payments.last()
+ if (not last_payment
+ or last_payment.provider != BankTransfer.identifier
+ or last_payment.state != OrderPayment.PAYMENT_STATE_CREATED):
+ messages.error(request, _('No pending bank transfer payment found. Maybe the order has been paid already?'))
+ return redirect(self.get_order_url())
+ if not last_payment.payment_provider.settings.get('invoice_email', as_type=bool):
+ messages.error(request, _('Sending invoices via email is disabled by the event organizer.'))
+ return redirect(self.get_order_url())
+
+ last_invoice = self.order.invoices.last()
+ if not last_invoice:
+ messages.error(request, _('No invoice found, please request an invoice first.'))
+ return redirect(self.get_order_url())
+
+ provider = last_payment.payment_provider
+ provider.send_invoice_to_alternate_email(self.order, last_invoice, request.POST['email'])
+
+ messages.success(request, _('Sending the latest invoice via e-mail to {email}.').format(email=request.POST['email']))
+ return redirect(self.get_order_url())
diff --git a/src/tests/plugins/banktransfer/test_presale.py b/src/tests/plugins/banktransfer/test_presale.py
new file mode 100644
index 0000000000..7cdfc36af0
--- /dev/null
+++ b/src/tests/plugins/banktransfer/test_presale.py
@@ -0,0 +1,91 @@
+#
+# This file is part of pretix (Community Edition).
+#
+# Copyright (C) 2014-2020 Raphael Michel and contributors
+# Copyright (C) 2020-2021 rami.io GmbH and contributors
+#
+# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
+# Public License as published by the Free Software Foundation in version 3 of the License.
+#
+# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
+# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
+# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
+# this file, see .
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
+# .
+#
+
+# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
+# the Apache License 2.0 can be obtained at .
+#
+# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A
+# full history of changes and contributors is available at .
+#
+# This file contains Apache-licensed contributions copyrighted by: Flavia Bastos
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
+# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under the License.
+
+from django.core import mail as djmail
+from django_countries.fields import Country
+from django_scopes import scopes_disabled
+from tests.presale.test_orders import BaseOrdersTest
+
+from pretix.base.models import InvoiceAddress, OrderPayment
+from pretix.base.services.invoices import generate_invoice
+
+
+class BanktransferOrdersTest(BaseOrdersTest):
+ def test_unknown_order(self):
+ response = self.client.post(
+ '/%s/%s/banktransfer/ABCDE/123/mail-invoice/' % (self.orga.slug, self.event.slug)
+ )
+ assert response.status_code == 404
+ response = self.client.post(
+ '/%s/%s/banktransfer/%s/123/mail-invoice/' % (self.orga.slug, self.event.slug, self.not_my_order.code)
+ )
+ assert response.status_code == 404
+
+ def test_order_with_no_invoice(self):
+ djmail.outbox = []
+ response = self.client.post(
+ '/%s/%s/banktransfer/%s/%s/mail-invoice/' % (
+ self.orga.slug, self.event.slug, self.order.code, self.order.secret),
+ {'email': 'test@example.org'}
+ )
+ assert response.status_code == 302
+
+ from django.contrib.messages import get_messages
+ messages = list(get_messages(response.wsgi_request))
+ assert len(messages) == 1
+ assert str(messages[0]) == 'No pending bank transfer payment found. Maybe the order has been paid already?'
+ assert len(djmail.outbox) == 0
+
+ def test_valid_order(self):
+ with scopes_disabled():
+ self.event.settings.set('payment_banktransfer_invoice_email', True)
+ self.order.payments.create(provider='banktransfer', state=OrderPayment.PAYMENT_STATE_CREATED,
+ amount=self.order.total)
+ InvoiceAddress.objects.create(order=self.order, company="Sample company", country=Country('NZ'))
+ generate_invoice(self.order)
+
+ djmail.outbox = []
+ response = self.client.post(
+ '/%s/%s/banktransfer/%s/%s/mail-invoice/' % (
+ self.orga.slug, self.event.slug, self.order.code, self.order.secret),
+ {'email': 'test@example.org'}
+ )
+ assert response.status_code == 302
+
+ from django.contrib.messages import get_messages
+ messages = list(get_messages(response.wsgi_request))
+ assert len(messages) == 1
+ assert str(messages[0]) == 'Sending the latest invoice via e-mail to test@example.org.'
+
+ assert len(djmail.outbox) == 1