From 3f07050d42c84601f15ec319e218c75443fc9bd3 Mon Sep 17 00:00:00 2001 From: Phin Wolkwitz Date: Thu, 7 Sep 2023 14:27:09 +0200 Subject: [PATCH] Payment: Add setting to prevent reminder mails (Z#23123914) (#3573) Adds a checkbox in each payment provider's settings controlling whether sending out expiry reminders should be prevented --- src/pretix/base/payment.py | 15 +++++++++ src/pretix/base/services/orders.py | 13 ++++++-- src/tests/base/test_orders.py | 50 ++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py index 9568b800fc..bab788b237 100644 --- a/src/pretix/base/payment.py +++ b/src/pretix/base/payment.py @@ -441,6 +441,13 @@ class BasePaymentProvider: 'Share this link with customers who should use this payment method.' ), )), + ('_prevent_reminder_mail', + forms.BooleanField( + label=_('Do not send a payment reminder mail'), + help_text=_('Users will not receive a reminder mail to pay for their order before it expires if ' + 'they have chosen this payment method.'), + required=False, + )), ]) d['_restricted_countries']._as_type = list d['_restrict_to_sales_channels']._as_type = list @@ -497,6 +504,14 @@ class BasePaymentProvider: if order.status == Order.STATUS_PAID: return _('paid') + def prevent_reminder_mail(self, order: Order, payment: OrderPayment) -> bool: + """ + This is called when a periodic task runs and sends out reminder mails to orders that have not been paid yet + and are soon expiring. + The default implementation returns the content of the _prevent_reminder_mail configuration variable (a boolean value). + """ + return self.settings.get('_prevent_reminder_mail', as_type=bool, default=False) + @property def payment_form_fields(self) -> dict: """ diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index a1829c63ee..c89c91665d 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -1289,9 +1289,18 @@ def send_expiry_warnings(sender, **kwargs): event_id = None for o in Order.objects.filter( - expires__gte=today, expiry_reminder_sent=False, status=Order.STATUS_PENDING, - datetime__lte=now() - timedelta(hours=2), require_approval=False + expires__gte=today, expiry_reminder_sent=False, status=Order.STATUS_PENDING, + datetime__lte=now() - timedelta(hours=2), require_approval=False ).only('pk', 'event_id', 'expires').order_by('event_id'): + + lp = o.payments.last() + if ( + lp and + lp.state in [OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING] and + lp.payment_provider.prevent_reminder_mail(o, lp) + ): + continue + if event_id != o.event_id: settings = o.event.settings days = cache.get_or_set('{}:{}:setting_mail_days_order_expire_warning'.format('event', o.event_id), diff --git a/src/tests/base/test_orders.py b/src/tests/base/test_orders.py index 4c2b5c9210..b993ce0762 100644 --- a/src/tests/base/test_orders.py +++ b/src/tests/base/test_orders.py @@ -683,6 +683,56 @@ class PaymentReminderTests(TestCase): send_expiry_warnings(sender=self.event) assert len(djmail.outbox) == 1 + @classscope(attr='o') + def test_prevent_reminder_mail(self): + self.event.settings.mail_days_order_expire_warning = 12 + pprov = list(self.event.get_payment_providers().keys())[0] + for state in [ + OrderPayment.PAYMENT_STATE_PENDING, + OrderPayment.PAYMENT_STATE_CREATED, + ]: + payment = self.order.payments.create( + state=state, + amount=self.order.total, + provider=pprov + ) + payment.payment_provider.settings.set('_prevent_reminder_mail', True) + payment.save() + send_expiry_warnings(sender=self.event) + assert len(djmail.outbox) == 0 + + @classscope(attr='o') + def test_prevent_reminder_mail_failed_state(self): + self.event.settings.mail_days_order_expire_warning = 12 + pprov = list(self.event.get_payment_providers().keys())[0] + payment = self.order.payments.create( + state=OrderPayment.PAYMENT_STATE_CREATED, + amount=self.order.total, + provider=pprov + ) + payment.payment_provider.settings.set('_prevent_reminder_mail', True) + payment.save() + payment.fail() + djmail.outbox = [] + send_expiry_warnings(sender=self.event) + assert len(djmail.outbox) == 1 + + @classscope(attr='o') + def test_prevent_reminder_mail_confirmed_but_not_all_paid(self): + self.event.settings.mail_days_order_expire_warning = 12 + pprov = list(self.event.get_payment_providers().keys())[0] + payment = self.order.payments.create( + state=OrderPayment.PAYMENT_STATE_CREATED, + amount=self.order.total / 2, + provider=pprov + ) + payment.payment_provider.settings.set('_prevent_reminder_mail', True) + payment.save() + payment.confirm() + djmail.outbox = [] + send_expiry_warnings(sender=self.event) + assert len(djmail.outbox) == 1 + @classscope(attr='o') def test_paid(self): self.order.status = Order.STATUS_PAID