diff --git a/src/pretix/api/views/order.py b/src/pretix/api/views/order.py index 43f5cd93d2..c90f5e8d07 100644 --- a/src/pretix/api/views/order.py +++ b/src/pretix/api/views/order.py @@ -764,7 +764,13 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet): ) and not order.invoices.last() invoice = None if gen_invoice: - invoice = generate_invoice(order, trigger_pdf=True) + try: + invoice = generate_invoice(order, trigger_pdf=True) + except Exception as e: + logger.exception("Could not generate invoice.") + order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) # Refresh serializer only after running signals prefetch_related_objects([order], self._positions_prefetch(request)) diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index 8b65d78981..5a64e9b1c6 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -1969,14 +1969,20 @@ class OrderPayment(models.Model): self.order.invoice_dirty ) if gen_invoice: - if invoices: - last_i = self.order.invoices.filter(is_cancellation=False).last() - if not last_i.canceled: - generate_cancellation(last_i) - invoice = generate_invoice( - self.order, - trigger_pdf=not send_mail or not self.order.event.settings.invoice_email_attachment - ) + try: + if invoices: + last_i = self.order.invoices.filter(is_cancellation=False).last() + if not last_i.canceled: + generate_cancellation(last_i) + invoice = generate_invoice( + self.order, + trigger_pdf=not send_mail or not self.order.event.settings.invoice_email_attachment + ) + except Exception as e: + logger.exception("Could not generate invoice.") + self.order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) transmit_invoice_task = invoice_transmission_separately(invoice) transmit_invoice_mail = not transmit_invoice_task and self.order.event.settings.invoice_email_attachment and self.order.email diff --git a/src/pretix/base/services/modelimport.py b/src/pretix/base/services/modelimport.py index 46504d933d..e8208a6e33 100644 --- a/src/pretix/base/services/modelimport.py +++ b/src/pretix/base/services/modelimport.py @@ -19,6 +19,7 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # +import logging from decimal import Decimal from typing import List @@ -43,6 +44,8 @@ from pretix.base.services.tasks import ProfiledEventTask from pretix.base.signals import order_paid, order_placed from pretix.celery_app import app +logger = logging.getLogger(__name__) + def _validate(cf: CachedFile, charset: str, cols: List[ImportColumn], settings: dict): try: @@ -233,7 +236,13 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user, (event.settings.get('invoice_generate') == 'paid' and o.status == Order.STATUS_PAID) ) and not o.invoices.last() if gen_invoice: - generate_invoice(o, trigger_pdf=True) + try: + generate_invoice(o, trigger_pdf=True) + except Exception as e: + logger.exception("Could not generate invoice.") + o.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) except DataImportError: raise ValidationError(_('We were not able to process your request completely as the server was too busy. ' 'Please try again.')) diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index 5d30600e04..b3e23a090d 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -264,7 +264,13 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None num_invoices = order.invoices.filter(is_cancellation=False).count() if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order): - generate_invoice(order) + try: + generate_invoice(order) + except Exception as e: + logger.exception("Could not generate invoice.") + order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) def extend_order(order: Order, new_date: datetime, force: bool=False, valid_if_pending: bool=None, user: User=None, auth=None): @@ -312,7 +318,13 @@ def extend_order(order: Order, new_date: datetime, force: bool=False, valid_if_p if was_expired: num_invoices = order.invoices.filter(is_cancellation=False).count() if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order): - generate_invoice(order) + try: + generate_invoice(order) + except Exception as e: + logger.exception("Could not generate invoice.") + order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) order.create_transactions() with transaction.atomic(): @@ -397,13 +409,19 @@ def approve_order(order, user=None, send_mail: bool=True, auth=None, force=False if order.event.settings.get('invoice_generate') == 'True' and invoice_qualified(order): if not invoice: - invoice = generate_invoice( - order, - # send_mail will trigger PDF generation later - trigger_pdf=not transmit_invoice_mail - ) - if transmit_invoice_task: - transmit_invoice.apply_async(args=(order.event_id, invoice.pk, False)) + try: + invoice = generate_invoice( + order, + # send_mail will trigger PDF generation later + trigger_pdf=not transmit_invoice_mail + ) + if transmit_invoice_task: + transmit_invoice.apply_async(args=(order.event_id, invoice.pk, False)) + except Exception as e: + logger.exception("Could not generate invoice.") + order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) if send_mail: with language(order.locale, order.event.settings.region): @@ -608,7 +626,13 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device order.save(update_fields=['status', 'cancellation_date', 'total']) if cancel_invoice and i: - invoices.append(generate_invoice(order)) + try: + invoices.append(generate_invoice(order)) + except Exception as e: + logger.exception("Could not generate invoice.") + order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) else: order.status = Order.STATUS_CANCELED order.cancellation_date = now() @@ -1306,13 +1330,19 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis ) ) if invoice_required: - invoice = generate_invoice( - order, - # send_mail will trigger PDF generation later - trigger_pdf=not transmit_invoice_mail - ) - if transmit_invoice_task: - transmit_invoice.apply_async(args=(event.pk, invoice.pk, False)) + try: + invoice = generate_invoice( + order, + # send_mail will trigger PDF generation later + trigger_pdf=not transmit_invoice_mail + ) + if transmit_invoice_task: + transmit_invoice.apply_async(args=(event.pk, invoice.pk, False)) + except Exception as e: + logger.exception("Could not generate invoice.") + order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) if order.email: if order.require_approval: @@ -2701,7 +2731,13 @@ class OrderChangeManager: ) if split_order.total != Decimal('0.00') and self.order.invoices.filter(is_cancellation=False).last(): - generate_invoice(split_order) + try: + generate_invoice(split_order) + except Exception as e: + logger.exception("Could not generate invoice.") + split_order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) order_split.send(sender=self.order.event, original=self.order, split_order=split_order) return split_order @@ -2812,15 +2848,27 @@ class OrderChangeManager: if order_now_qualified: if invoice_should_be_generated_now: - if i and not i.canceled: - self._invoices.append(generate_cancellation(i)) - self._invoices.append(generate_invoice(self.order)) + try: + if i and not i.canceled: + self._invoices.append(generate_cancellation(i)) + self._invoices.append(generate_invoice(self.order)) + except Exception as e: + logger.exception("Could not generate invoice.") + self.order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) elif invoice_should_be_generated_later: self.order.invoice_dirty = True self.order.save(update_fields=["invoice_dirty"]) else: - if i and not i.canceled: - self._invoices.append(generate_cancellation(i)) + try: + if i and not i.canceled: + self._invoices.append(generate_cancellation(i)) + except Exception as e: + logger.exception("Could not generate invoice.") + self.order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) def _check_complete_cancel(self): current = self.order.positions.count() @@ -3246,8 +3294,14 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay has_active_invoice = i and not i.canceled if has_active_invoice and order.total != oldtotal: - generate_cancellation(i) - generate_invoice(order) + try: + generate_cancellation(i) + generate_invoice(order) + except Exception as e: + logger.exception("Could not generate invoice.") + order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) new_invoice_created = True elif (not has_active_invoice or order.invoice_dirty) and invoice_qualified(order): @@ -3255,13 +3309,19 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay order.event.settings.get('invoice_generate') == 'paid' and new_payment.payment_provider.requires_invoice_immediately ): - if has_active_invoice: - generate_cancellation(i) - i = generate_invoice(order) - new_invoice_created = True - order.log_action('pretix.event.order.invoice.generated', data={ - 'invoice': i.pk - }) + try: + if has_active_invoice: + generate_cancellation(i) + i = generate_invoice(order) + new_invoice_created = True + order.log_action('pretix.event.order.invoice.generated', data={ + 'invoice': i.pk + }) + except Exception as e: + logger.exception("Could not generate invoice.") + order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) order.create_transactions() return old_fee, new_fee, fee, new_payment, new_invoice_created diff --git a/src/pretix/control/logdisplay.py b/src/pretix/control/logdisplay.py index 29de5a52c0..24e3bf05e1 100644 --- a/src/pretix/control/logdisplay.py +++ b/src/pretix/control/logdisplay.py @@ -522,6 +522,7 @@ def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, bl 'pretix.event.order.customer.changed': _('The customer account has been changed.'), 'pretix.event.order.locale.changed': _('The order locale has been changed.'), 'pretix.event.order.invoice.generated': _('The invoice has been generated.'), + 'pretix.event.order.invoice.failed': _('The invoice could not be generated.'), 'pretix.event.order.invoice.regenerated': _('The invoice has been regenerated.'), 'pretix.event.order.invoice.reissued': _('The invoice has been reissued.'), 'pretix.event.order.invoice.sent': _('The invoice {full_invoice_no} has been sent.'), diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py index b78e98ea02..efcfc3a648 100644 --- a/src/pretix/presale/views/order.py +++ b/src/pretix/presale/views/order.py @@ -36,6 +36,7 @@ import copy import hmac import inspect import json +import logging import mimetypes import os import re @@ -98,6 +99,8 @@ from pretix.presale.views import ( from pretix.presale.views.event import get_grouped_items from pretix.presale.views.robots import NoSearchIndexViewMixin +logger = logging.getLogger(__name__) + class OrderDetailMixin(NoSearchIndexViewMixin): @@ -734,11 +737,18 @@ class OrderInvoiceCreate(EventViewMixin, OrderDetailMixin, View): elif self.order.invoices.exists(): messages.error(self.request, _('An invoice for this order already exists.')) else: - i = generate_invoice(self.order) - self.order.log_action('pretix.event.order.invoice.generated', data={ - 'invoice': i.pk - }) - messages.success(self.request, _('The invoice has been generated.')) + try: + i = generate_invoice(self.order) + self.order.log_action('pretix.event.order.invoice.generated', data={ + 'invoice': i.pk + }) + messages.success(self.request, _('The invoice has been generated.')) + except Exception as e: + logger.exception("Could not generate invoice.") + self.order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) + messages.error(self.request, _('Invoice generation has failed, please reach out to the organizer.')) return redirect(self.get_order_url()) @@ -807,24 +817,37 @@ class OrderModify(EventViewMixin, OrderDetailMixin, OrderQuestionsViewMixin, Tem elif self.order.invoices.exists(): messages.error(self.request, _('An invoice for this order already exists.')) else: - i = generate_invoice(self.order) - self.order.log_action('pretix.event.order.invoice.generated', data={ - 'invoice': i.pk - }) - messages.success(self.request, _('The invoice has been generated.')) + try: + i = generate_invoice(self.order) + self.order.log_action('pretix.event.order.invoice.generated', data={ + 'invoice': i.pk + }) + messages.success(self.request, _('The invoice has been generated.')) + except Exception as e: + logger.exception("Could not generate invoice.") + self.order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) + }) + messages.error(self.request, _('Invoice generation has failed, please reach out to the organizer.')) elif self.request.event.settings.invoice_reissue_after_modify: if self.invoice_form.changed_data: - inv = self.order.invoices.last() - if inv and not inv.canceled and not inv.shredded: - c = generate_cancellation(inv) - if self.order.status != Order.STATUS_CANCELED: - inv = generate_invoice(self.order) - else: - inv = c - self.order.log_action('pretix.event.order.invoice.reissued', data={ - 'invoice': inv.pk + try: + inv = self.order.invoices.last() + if inv and not inv.canceled and not inv.shredded: + c = generate_cancellation(inv) + if self.order.status != Order.STATUS_CANCELED: + inv = generate_invoice(self.order) + else: + inv = c + self.order.log_action('pretix.event.order.invoice.reissued', data={ + 'invoice': inv.pk + }) + messages.success(self.request, _('The invoice has been reissued.')) + except Exception as e: + self.order.log_action("pretix.event.order.invoice.failed", data={ + "exception": str(e) }) - messages.success(self.request, _('The invoice has been reissued.')) + logger.exception("Could not generate invoice.") invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'order': self.order.pk}) CachedTicket.objects.filter(order_position__order=self.order).delete()