forked from CGM_Public/pretix_original
Invoice email transmission: Handle permanent failures (Z#23205576) (#5420)
* Invoice email transmission: Handle permanent failures (Z#23205576) * Add missing raise branch * Fix missing file * Fix missing license header
This commit is contained in:
@@ -42,6 +42,7 @@ from django.db.models.functions import Cast
|
||||
from django.utils import timezone
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _, pgettext
|
||||
from django_scopes import ScopedManager
|
||||
|
||||
@@ -368,6 +369,22 @@ class Invoice(models.Model):
|
||||
from pretix.base.invoicing.transmission import transmission_types
|
||||
return transmission_types.get(identifier=self.transmission_type)[0]
|
||||
|
||||
def set_transmission_failed(self, provider, data):
|
||||
self.transmission_status = Invoice.TRANSMISSION_STATUS_FAILED
|
||||
self.transmission_date = now()
|
||||
if not self.transmission_provider and provider:
|
||||
self.transmission_provider = provider
|
||||
self.save(update_fields=["transmission_status", "transmission_date", "transmission_provider"])
|
||||
self.order.log_action(
|
||||
"pretix.event.order.invoice.sending_failed",
|
||||
data={
|
||||
"full_invoice_no": self.full_invoice_no,
|
||||
"transmission_provider": provider,
|
||||
"transmission_type": self.transmission_type,
|
||||
"data": data,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class InvoiceLine(models.Model):
|
||||
"""
|
||||
|
||||
@@ -664,20 +664,7 @@ def transmit_invoice(sender, invoice_id, allow_retransmission=True, **kwargs):
|
||||
break
|
||||
|
||||
if not provider:
|
||||
invoice.transmission_status = Invoice.TRANSMISSION_STATUS_FAILED
|
||||
invoice.transmission_date = now()
|
||||
invoice.save(update_fields=["transmission_status", "transmission_date"])
|
||||
invoice.order.log_action(
|
||||
"pretix.event.order.invoice.sending_failed",
|
||||
data={
|
||||
"full_invoice_no": invoice.full_invoice_no,
|
||||
"transmission_provider": None,
|
||||
"transmission_type": invoice.transmission_type,
|
||||
"data": {
|
||||
"reason": "no_provider",
|
||||
},
|
||||
}
|
||||
)
|
||||
invoice.set_transmission_failed(provider=None, data={"reason": "no_provider"})
|
||||
return
|
||||
|
||||
if invoice.order.testmode and not provider.testmode_supported:
|
||||
@@ -698,18 +685,7 @@ def transmit_invoice(sender, invoice_id, allow_retransmission=True, **kwargs):
|
||||
provider.transmit(invoice)
|
||||
except Exception as e:
|
||||
logger.exception(f"Transmission of invoice {invoice.pk} failed with exception.")
|
||||
invoice.transmission_status = Invoice.TRANSMISSION_STATUS_FAILED
|
||||
invoice.transmission_date = now()
|
||||
invoice.save(update_fields=["transmission_status", "transmission_date"])
|
||||
invoice.order.log_action(
|
||||
"pretix.event.order.invoice.sending_failed",
|
||||
data={
|
||||
"full_invoice_no": invoice.full_invoice_no,
|
||||
"transmission_provider": None,
|
||||
"transmission_type": invoice.transmission_type,
|
||||
"data": {
|
||||
"reason": "exception",
|
||||
"exception": str(e),
|
||||
},
|
||||
}
|
||||
)
|
||||
invoice.set_transmission_failed(provider=provider.identifier, data={
|
||||
"reason": "exception",
|
||||
"exception": str(e),
|
||||
})
|
||||
|
||||
@@ -495,7 +495,7 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
||||
|
||||
email = email_filter.send_chained(event, 'message', message=email, order=order, user=user)
|
||||
|
||||
invoices_sent = []
|
||||
invoices_to_mark_transmitted = []
|
||||
if invoices:
|
||||
invoices = Invoice.objects.filter(pk__in=invoices)
|
||||
for inv in invoices:
|
||||
@@ -516,7 +516,23 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
||||
inv.file.file.read(),
|
||||
'application/pdf'
|
||||
)
|
||||
invoices_sent.append(inv)
|
||||
|
||||
if inv.transmission_type == "email":
|
||||
# Mark invoice as sent when it was sent to the requested address *either* at the time of
|
||||
# invoice creation *or* as of right now.
|
||||
expected_recipients = [
|
||||
(inv.invoice_to_transmission_info or {}).get("transmission_email_address")
|
||||
or inv.order.email,
|
||||
]
|
||||
try:
|
||||
expected_recipients.append(
|
||||
(inv.order.invoice_address.transmission_info or {}).get("transmission_email_address")
|
||||
or inv.order.email
|
||||
)
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
pass
|
||||
if any(t in expected_recipients for t in to):
|
||||
invoices_to_mark_transmitted.append(inv)
|
||||
except:
|
||||
logger.exception('Could not attach invoice to email')
|
||||
pass
|
||||
@@ -589,6 +605,11 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
||||
'invoices': [],
|
||||
}
|
||||
)
|
||||
for i in invoices_to_mark_transmitted:
|
||||
i.set_transmission_failed(provider="email_pdf", data={
|
||||
"reason": "exception",
|
||||
"exception": "SMTP code {}, max retries exceeded".format(e.smtp_code),
|
||||
})
|
||||
raise e
|
||||
|
||||
logger.exception('Error sending email')
|
||||
@@ -602,6 +623,11 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
||||
'invoices': [],
|
||||
}
|
||||
)
|
||||
for i in invoices_to_mark_transmitted:
|
||||
i.set_transmission_failed(provider="email_pdf", data={
|
||||
"reason": "exception",
|
||||
"exception": "SMTP code {}".format(e.smtp_code),
|
||||
})
|
||||
|
||||
raise SendMailException('Failed to send an email to {}.'.format(to))
|
||||
except smtplib.SMTPRecipientsRefused as e:
|
||||
@@ -633,6 +659,11 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
||||
'invoices': [],
|
||||
}
|
||||
)
|
||||
for i in invoices_to_mark_transmitted:
|
||||
i.set_transmission_failed(provider="email_pdf", data={
|
||||
"reason": "exception",
|
||||
"exception": "SMTP error",
|
||||
})
|
||||
|
||||
raise SendMailException('Failed to send an email to {}.'.format(to))
|
||||
except Exception as e:
|
||||
@@ -650,6 +681,11 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
||||
'invoices': [],
|
||||
}
|
||||
)
|
||||
for i in invoices_to_mark_transmitted:
|
||||
i.set_transmission_failed(provider="email_pdf", data={
|
||||
"reason": "exception",
|
||||
"exception": "Internal error",
|
||||
})
|
||||
raise e
|
||||
if log_target:
|
||||
log_target.log_action(
|
||||
@@ -661,59 +697,52 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
||||
'invoices': [],
|
||||
}
|
||||
)
|
||||
for i in invoices_to_mark_transmitted:
|
||||
i.set_transmission_failed(provider="email_pdf", data={
|
||||
"reason": "exception",
|
||||
"exception": "Internal error",
|
||||
})
|
||||
logger.exception('Error sending email')
|
||||
raise SendMailException('Failed to send an email to {}.'.format(to))
|
||||
else:
|
||||
for i in invoices_sent:
|
||||
if i.transmission_type == "email":
|
||||
# Mark invoice as sent when it was sent to the requested address *either* at the time of invoice
|
||||
# creation *or* as of right now.
|
||||
expected_recipients = [
|
||||
(i.invoice_to_transmission_info or {}).get("transmission_email_address") or i.order.email,
|
||||
]
|
||||
try:
|
||||
expected_recipients.append((i.order.invoice_address.transmission_info or {}).get("transmission_email_address") or i.order.email)
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
pass
|
||||
if not any(t in expected_recipients for t in to):
|
||||
continue
|
||||
if i.transmission_status != Invoice.TRANSMISSION_STATUS_COMPLETED:
|
||||
i.transmission_date = now()
|
||||
i.transmission_status = Invoice.TRANSMISSION_STATUS_COMPLETED
|
||||
i.transmission_provider = "email_pdf"
|
||||
i.transmission_info = {
|
||||
"sent": [
|
||||
{
|
||||
"recipients": to,
|
||||
"datetime": now().isoformat(),
|
||||
}
|
||||
]
|
||||
}
|
||||
i.save(update_fields=[
|
||||
"transmission_date", "transmission_provider", "transmission_status",
|
||||
"transmission_info"
|
||||
])
|
||||
elif i.transmission_provider == "email_pdf":
|
||||
i.transmission_info["sent"].append(
|
||||
for i in invoices_to_mark_transmitted:
|
||||
if i.transmission_status != Invoice.TRANSMISSION_STATUS_COMPLETED:
|
||||
i.transmission_date = now()
|
||||
i.transmission_status = Invoice.TRANSMISSION_STATUS_COMPLETED
|
||||
i.transmission_provider = "email_pdf"
|
||||
i.transmission_info = {
|
||||
"sent": [
|
||||
{
|
||||
"recipients": to,
|
||||
"datetime": now().isoformat(),
|
||||
}
|
||||
)
|
||||
i.save(update_fields=[
|
||||
"transmission_info"
|
||||
])
|
||||
i.order.log_action(
|
||||
"pretix.event.order.invoice.sent",
|
||||
data={
|
||||
"full_invoice_no": i.full_invoice_no,
|
||||
"transmission_provider": "email_pdf",
|
||||
"transmission_type": "email",
|
||||
"data": {
|
||||
"recipients": [to],
|
||||
},
|
||||
]
|
||||
}
|
||||
i.save(update_fields=[
|
||||
"transmission_date", "transmission_provider", "transmission_status",
|
||||
"transmission_info"
|
||||
])
|
||||
elif i.transmission_provider == "email_pdf":
|
||||
i.transmission_info["sent"].append(
|
||||
{
|
||||
"recipients": to,
|
||||
"datetime": now().isoformat(),
|
||||
}
|
||||
)
|
||||
i.save(update_fields=[
|
||||
"transmission_info"
|
||||
])
|
||||
i.order.log_action(
|
||||
"pretix.event.order.invoice.sent",
|
||||
data={
|
||||
"full_invoice_no": i.full_invoice_no,
|
||||
"transmission_provider": "email_pdf",
|
||||
"transmission_type": "email",
|
||||
"data": {
|
||||
"recipients": [to],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def mail_send(*args, **kwargs):
|
||||
|
||||
31
src/pretix/testutils/mail.py
Normal file
31
src/pretix/testutils/mail.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
import smtplib
|
||||
|
||||
from django.core.mail.backends.locmem import EmailBackend
|
||||
|
||||
|
||||
class FailingEmailBackend(EmailBackend):
|
||||
def send_messages(self, email_messages):
|
||||
raise smtplib.SMTPRecipientsRefused({
|
||||
'recipient@example.org': (450, b'Recipient unknown')
|
||||
})
|
||||
Reference in New Issue
Block a user