diff --git a/src/pretix/base/services/cart.py b/src/pretix/base/services/cart.py
index 03e5ad36f2..9e1c2e45fc 100644
--- a/src/pretix/base/services/cart.py
+++ b/src/pretix/base/services/cart.py
@@ -1605,6 +1605,7 @@ def add_payment_to_cart_session(cart_session, provider, min_value: Decimal=None,
'max_value': str(max_value) if max_value is not None else None,
'info_data': info_data or {},
})
+ cart_session['payments_postpone'] = False
def add_payment_to_cart(request, provider, min_value: Decimal=None, max_value: Decimal=None, info_data: dict=None):
diff --git a/src/pretix/presale/checkoutflow.py b/src/pretix/presale/checkoutflow.py
index 007d43d2bc..3d8df897d9 100644
--- a/src/pretix/presale/checkoutflow.py
+++ b/src/pretix/presale/checkoutflow.py
@@ -1440,10 +1440,12 @@ class PaymentStep(CartMixin, TemplateFlowStep):
ctx['providers'] = self.provider_forms
ctx['show_fees'] = any(p['fee'] for p in self.provider_forms)
- if len(self.provider_forms) == 1:
- ctx['selected'] = self.provider_forms[0]['provider'].identifier
- elif 'payment' in self.request.POST:
+ if 'payment' in self.request.POST:
ctx['selected'] = self.request.POST['payment']
+ elif self.cart_session.get('payments_postpone') and self._allow_postpone:
+ ctx['selected'] = ''
+ elif len(self.provider_forms) == 1:
+ ctx['selected'] = self.provider_forms[0]['provider'].identifier
elif self.single_use_payment:
ctx['selected'] = self.single_use_payment['provider']
else:
diff --git a/src/pretix/presale/templates/pretixpresale/event/checkout_payment.html b/src/pretix/presale/templates/pretixpresale/event/checkout_payment.html
index e8ed7620f1..fa75e636e4 100644
--- a/src/pretix/presale/templates/pretixpresale/event/checkout_payment.html
+++ b/src/pretix/presale/templates/pretixpresale/event/checkout_payment.html
@@ -135,7 +135,9 @@
{% trans "Not sure yet? You can complete your order first and then select a payment method later." %}
- {% if payment_deadline_minutes %}
+ {% if current_payments %}
+ {% trans "To do so, please first remove the payment methods you already selected above." %}
+ {% elif payment_deadline_minutes %}
{% blocktrans trimmed with minutes=payment_deadline_minutes %}
Your payment needs to be completed within {{ minutes }} minutes.
{% endblocktrans %}
@@ -147,7 +149,8 @@
-
diff --git a/src/tests/presale/test_checkout.py b/src/tests/presale/test_checkout.py
index 9411a4ad8c..3298bdc67c 100644
--- a/src/tests/presale/test_checkout.py
+++ b/src/tests/presale/test_checkout.py
@@ -2405,6 +2405,64 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
o = Order.objects.last()
assert not o.payments.exists()
+ def test_payment_postpone_cleared_on_selection(self):
+ self.event.settings.set('payment_banktransfer__enabled', True)
+ self.event.settings.payment_choice_postpone_allowed_channels = ['web']
+ with scopes_disabled():
+ CartPosition.objects.create(
+ event=self.event, cart_id=self.session_key, item=self.ticket,
+ price=23, expires=now() + timedelta(minutes=10)
+ )
+
+ response = self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
+ 'postpone': 'on',
+ }, follow=False)
+ self.assertRedirects(response, '/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug),
+ target_status_code=200)
+ assert self.client.session['carts'][self.session_key].get('payments_postpone')
+
+ # The only available provider must not be preselected while the choice is postponed
+ response = self.client.get('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), follow=True)
+ doc = BeautifulSoup(response.content.decode(), "lxml")
+ self.assertEqual(len(doc.select('input[name="payment"]')), 1)
+ self.assertEqual(len(doc.select('input[name="payment"][checked]')), 0)
+
+ # Selecting a payment method takes the order out of the postponed state again
+ response = self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
+ 'payment': 'banktransfer',
+ }, follow=False)
+ self.assertRedirects(response, '/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug),
+ target_status_code=200)
+ assert not self.client.session['carts'][self.session_key].get('payments_postpone')
+
+ def test_payment_postpone_disabled_with_partial_payment(self):
+ self.event.settings.set('payment_banktransfer__enabled', True)
+ self.event.settings.payment_choice_postpone_allowed_channels = ['web']
+ gc = self.orga.issued_gift_cards.create(currency="EUR")
+ gc.transactions.create(value=20, acceptor=self.orga)
+ with scopes_disabled():
+ CartPosition.objects.create(
+ event=self.event, cart_id=self.session_key, item=self.ticket,
+ price=23, expires=now() + timedelta(minutes=10)
+ )
+
+ response = self.client.get('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), follow=True)
+ doc = BeautifulSoup(response.content.decode(), "lxml")
+ self.assertEqual(len(doc.select('button[name="postpone"]')), 1)
+ self.assertEqual(len(doc.select('button[name="postpone"][disabled]')), 0)
+
+ # Apply a gift card that only covers part of the total
+ response = self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
+ 'payment': 'giftcard',
+ 'payment_giftcard-code': gc.secret,
+ }, follow=True)
+ self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
+ target_status_code=200)
+
+ # Postponing would silently drop the gift card, so it is no longer offered
+ doc = BeautifulSoup(response.content.decode(), "lxml")
+ self.assertEqual(len(doc.select('button[name="postpone"][disabled]')), 1)
+
def test_premature_confirm(self):
response = self.client.get('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug),