From 41fc74336002f6aa9e682ca9c1b110c94c35fd82 Mon Sep 17 00:00:00 2001 From: Lukas Bockstaller Date: Tue, 11 Aug 2026 13:48:58 +0200 Subject: [PATCH] extend BasePaymentProvider to gate aborting pending payments on a payment per payment basis --- src/pretix/base/payment.py | 19 +++++++++++++++---- src/pretix/presale/views/order.py | 11 ++++++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py index c8047bbd5c..6412833aa7 100644 --- a/src/pretix/base/payment.py +++ b/src/pretix/base/payment.py @@ -333,6 +333,15 @@ class BasePaymentProvider: """ return False + def abort_pending_payment_allowed(self, payment) -> bool: + """ + Whether this specific payment can be aborted in pending state or not. + If both ``abort_pending_allowed`` and ``abort_pending_payment_allowed`` are True, + then the frontend button will be shown to users. + This returns ``True`` by default, which makes implementing this method optional. + """ + return True + @property def requires_invoice_immediately(self): """ @@ -1019,10 +1028,12 @@ class BasePaymentProvider: On success, you should set ``payment.state = OrderPayment.PAYMENT_STATE_CANCELED`` (or call the super method). On failure, you should raise a PaymentException. """ - if payment.state == OrderPayment.PAYMENT_STATE_PENDING and not self.abort_pending_allowed: - raise PaymentException(_( - "This payment is already being processed and can not be canceled any more." - )) + + if payment.state == OrderPayment.PAYMENT_STATE_PENDING: + if self.abort_pending_allowed and self.abort_pending_payment_allowed(payment): + raise PaymentException(_( + "This payment is already being processed and can not be canceled any more." + )) payment.state = OrderPayment.PAYMENT_STATE_CANCELED payment.save(update_fields=['state']) diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py index c131644631..cc37f8a279 100644 --- a/src/pretix/presale/views/order.py +++ b/src/pretix/presale/views/order.py @@ -349,7 +349,8 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TicketPageMixin, pp = lp.payment_provider ctx['last_payment_info'] = pp.payment_pending_render(self.request, ctx['last_payment']) - if lp.state == OrderPayment.PAYMENT_STATE_PENDING and not pp.abort_pending_allowed: + abort_pending_payment_allowed = pp.abort_pending_allowed and pp.abort_pending_payment_allowed(lp) + if lp.state == OrderPayment.PAYMENT_STATE_PENDING and not abort_pending_payment_allowed: ctx['can_pay'] = False ctx['can_pay'] = ctx['can_pay'] and self.order._can_be_paid() is True @@ -611,7 +612,9 @@ class OrderPayChangeMethod(EventViewMixin, OrderDetailMixin, TemplateView): if self.open_payment: pp = self.open_payment.payment_provider - if self.open_payment.state == OrderPayment.PAYMENT_STATE_PENDING and not pp.abort_pending_allowed: + abort_pending_payment_allowed = pp.abort_pending_allowed and pp.abort_pending_payment_allowed( + self.open_payment) + if self.open_payment.state == OrderPayment.PAYMENT_STATE_PENDING and not abort_pending_payment_allowed: messages.error(request, _('A payment is currently pending for this order.')) return redirect(self.get_order_url()) @@ -1718,7 +1721,9 @@ class OrderChangeMixin: if totaldiff > Decimal('0.00') and self.order.status == Order.STATUS_PENDING: for p in self.order.payments.filter(state=OrderPayment.PAYMENT_STATE_PENDING): - if not p.payment_provider.abort_pending_allowed: + abort_pending_payment_allowed = p.payment_provider.abort_pending_allowed and p.payment_provider.abort_pending_payment_allowed( + p) + if not abort_pending_payment_allowed: raise OrderError(_('You may not change your order in a way that requires additional payment while ' 'we are processing your current payment. Please check back after your current ' 'payment has been accepted.'))