diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index ba2c44d8aa..af1d51e435 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -1667,12 +1667,13 @@ class OrderPayment(models.Model): if status_change: self.order.create_transactions() - def fail(self, info=None, user=None, auth=None, log_data=None): + def fail(self, info=None, user=None, auth=None, log_data=None, send_mail=True): """ 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 been marked as paid. + :param send_mail: Whether an email should be sent to the user about this event (default: ``True``). """ with transaction.atomic(): locked_instance = OrderPayment.objects.select_for_update(of=OF_SELF).get(pk=self.pk) @@ -1697,6 +1698,17 @@ class OrderPayment(models.Model): 'info': info, 'data': log_data, }, user=user, auth=auth) + + if send_mail: + with language(self.order.locale, self.order.event.settings.region): + email_subject = self.order.event.settings.mail_subject_order_payment_failed + email_template = self.order.event.settings.mail_text_order_payment_failed + email_context = get_email_context(event=self.order.event, order=self.order) + self.order.send_mail( + email_subject, email_template, email_context, + 'pretix.event.order.email.payment_failed', user=user, auth=auth, + ) + return True def confirm(self, count_waitinglist=True, send_mail=True, force=False, user=None, auth=None, mail_text='', diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index d4efb2fcd7..bd8d2f28b5 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -2341,6 +2341,24 @@ missing additional payment of **{pending_sum}**. You can view the payment information and the status of your order at {url} +Best regards, +Your {event} team""")) # noqa: W291 + }, + 'mail_subject_order_payment_failed': { + 'type': LazyI18nString, + 'default': LazyI18nString.from_gettext(gettext_noop("Payment failed for your order: {code}")), + }, + 'mail_text_order_payment_failed': { + 'type': LazyI18nString, + 'default': LazyI18nString.from_gettext(gettext_noop("""Hello, + +your payment attempt for your order for {event} has failed. + +Your order is still valid and you can try to pay again using the same or a different payment method. Please complete your payment before {expire_date}. + +You can retry the payment and view the status of your order at +{url} + Best regards, Your {event} team""")) # noqa: W291 }, diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 7740a5144e..1b82f3d634 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -1116,6 +1116,16 @@ class MailSettingsForm(SettingsForm): help_text=_("This email only applies to payment methods that can receive incomplete payments, " "such as bank transfer."), ) + mail_subject_order_payment_failed = I18nFormField( + label=_("Subject"), + required=False, + widget=I18nTextInput, + ) + mail_text_order_payment_failed = I18nFormField( + label=_("Text"), + required=False, + widget=I18nTextarea, + ) mail_subject_waiting_list = I18nFormField( label=_("Subject"), required=False, @@ -1289,6 +1299,8 @@ class MailSettingsForm(SettingsForm): 'mail_subject_order_pending_warning': ['event', 'order'], 'mail_text_order_incomplete_payment': ['event', 'order', 'pending_sum'], 'mail_subject_order_incomplete_payment': ['event', 'order'], + 'mail_text_order_payment_failed': ['event', 'order'], + 'mail_subject_order_payment_failed': ['event', 'order'], 'mail_text_order_custom_mail': ['event', 'order'], 'mail_text_download_reminder': ['event', 'order'], 'mail_subject_download_reminder': ['event', 'order'], diff --git a/src/pretix/control/logdisplay.py b/src/pretix/control/logdisplay.py index ec6a4600e2..c2d96b65cd 100644 --- a/src/pretix/control/logdisplay.py +++ b/src/pretix/control/logdisplay.py @@ -433,6 +433,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs): 'the order has been received and requires ' 'approval.'), 'pretix.event.order.email.resend': _('An email with a link to the order detail page has been resent to the user.'), + 'pretix.event.order.email.payment_failed': _('An email has been sent to notify the user that the payment failed.'), 'pretix.event.order.payment.confirmed': _('Payment {local_id} has been confirmed.'), 'pretix.event.order.payment.canceled': _('Payment {local_id} has been canceled.'), 'pretix.event.order.payment.canceled.failed': _('Canceling payment {local_id} has failed.'), diff --git a/src/pretix/control/templates/pretixcontrol/event/mail.html b/src/pretix/control/templates/pretixcontrol/event/mail.html index 763e4f8588..5006c7b058 100644 --- a/src/pretix/control/templates/pretixcontrol/event/mail.html +++ b/src/pretix/control/templates/pretixcontrol/event/mail.html @@ -105,6 +105,9 @@ {% blocktrans asvar title_payment_reminder %}Payment reminder{% endblocktrans %} {% include "pretixcontrol/event/mail_settings_fragment.html" with pid="order_expirew" title=title_payment_reminder items="mail_days_order_expire_warning,mail_subject_order_expire_warning,mail_text_order_expire_warning,mail_subject_order_pending_warning,mail_text_order_pending_warning,mail_subject_order_incomplete_payment,mail_text_order_incomplete_payment" exclude="mail_days_order_expire_warning" %} + {% blocktrans asvar title_payment_failed %}Payment failed{% endblocktrans %} + {% include "pretixcontrol/event/mail_settings_fragment.html" with pid="payment_failed" title=title_payment_failed items="mail_subject_order_payment_failed,mail_text_order_payment_failed" %} + {% blocktrans asvar title_waiting_list_notification %}Waiting list notification{% endblocktrans %} {% include "pretixcontrol/event/mail_settings_fragment.html" with pid="waiting_list" title=title_waiting_list_notification items="mail_subject_waiting_list,mail_text_waiting_list" %} diff --git a/src/tests/base/test_orders.py b/src/tests/base/test_orders.py index 0689352155..8745697a27 100644 --- a/src/tests/base/test_orders.py +++ b/src/tests/base/test_orders.py @@ -704,6 +704,47 @@ class PaymentReminderTests(TestCase): assert len(djmail.outbox) == 0 +class PaymentFailedTests(TestCase): + def setUp(self): + super().setUp() + self.o = Organizer.objects.create(name='Dummy', slug='dummy') + with scope(organizer=self.o): + self.event = Event.objects.create( + organizer=self.o, name='Dummy', slug='dummy', + date_from=now() + timedelta(days=2), + plugins='pretix.plugins.banktransfer' + ) + self.order = Order.objects.create( + code='FOO', event=self.event, email='dummy@dummy.test', + status=Order.STATUS_PENDING, locale='en', + datetime=now() - timedelta(hours=4), + expires=now() - timedelta(hours=4) + timedelta(days=10), + total=Decimal('46.00'), + ) + self.ticket = Item.objects.create(event=self.event, name='Early-bird ticket', + default_price=Decimal('23.00'), admission=True) + self.op1 = OrderPosition.objects.create( + order=self.order, item=self.ticket, variation=None, + price=Decimal("23.00"), attendee_name_parts={'full_name': "Peter"}, positionid=1 + ) + djmail.outbox = [] + + @classscope(attr='o') + def test_send_payment_fail_mail(self): + payment = self.order.payments.create(state=OrderPayment.PAYMENT_STATE_PENDING, amount=self.order.total) + payment.save() + payment.fail() + assert len(djmail.outbox) == 1 + assert "fail" in djmail.outbox[0].subject + + @classscope(attr='o') + def test_no_payment_fail_mail_setting(self): + payment = self.order.payments.create(state=OrderPayment.PAYMENT_STATE_PENDING, amount=self.order.total) + payment.save() + payment.fail(send_mail=False) + assert len(djmail.outbox) == 0 + + class DownloadReminderTests(TestCase): def setUp(self): super().setUp()