mirror of
https://github.com/pretix/pretix.git
synced 2026-08-08 10:27:49 +00:00
Bank transfer: Allow customer to send latest invoice via email (Z#207218) (#3511)
Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
@@ -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``).
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 %}
|
||||
<link rel="stylesheet" href="{% static "pretixplugins/banktransfer/swisscross.css" %}">
|
||||
{% endif %}
|
||||
|
||||
{% if invoice_email_enabled and has_invoices %}
|
||||
<form method="post" action="{% eventurl event "plugins:banktransfer:mail_invoice" order=order.code secret=order.secret %}">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
To send the invoice directly to your accounting department, please enter their email address:
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-md-9 col-xs-12">
|
||||
<label for="mail_invoice_email" class="sr-only">{% trans "Invoice recipient email" %}:</label>
|
||||
<input type="email" name="email" id="mail_invoice_email" class="form-control" value="" required
|
||||
placeholder="{% trans "Email address" %}" />
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-12">
|
||||
<button class="btn btn-default btn-block">
|
||||
<span class="fa fa-envelope-o" aria-hidden="true"></span>
|
||||
{% trans "Send invoice via email" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
{% endif %}
|
||||
@@ -19,13 +19,19 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
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<order>[^/][^w]+)/(?P<secret>[A-Za-z0-9]+)/mail-invoice/$', views.SendInvoiceMailView.as_view(), name='mail_invoice'),
|
||||
])),
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/import/',
|
||||
views.OrganizerImportView.as_view(),
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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 <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# 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
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# 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 <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||
#
|
||||
# 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 <https://github.com/pretix/pretix>.
|
||||
#
|
||||
# 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
|
||||
Reference in New Issue
Block a user