diff --git a/doc/development/api/payment.rst b/doc/development/api/payment.rst
index 59c109e8d6..e9b95de938 100644
--- a/doc/development/api/payment.rst
+++ b/doc/development/api/payment.rst
@@ -68,6 +68,8 @@ The provider class
.. automethod:: is_allowed
+ .. automethod:: is_allowed_for_order
+
.. autoattribute:: payment_form_fields
.. automethod:: checkout_prepare
@@ -86,9 +88,11 @@ The provider class
This is an abstract method, you **must** override this!
+ .. automethod:: order_change_allowed
+
.. automethod:: order_can_retry
- .. automethod:: retry_prepare
+ .. automethod:: order_prepare
.. automethod:: order_paid_render
diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py
index 9d6ebb8c42..3b8f636969 100644
--- a/src/pretix/base/payment.py
+++ b/src/pretix/base/payment.py
@@ -314,20 +314,42 @@ class BasePaymentProvider:
"""
raise NotImplementedError() # NOQA
+ def order_change_allowed(self, order: Order) -> bool:
+ """
+ Will be called to check whether it is allowed to change the payment method of
+ an order to this one.
+
+ The default implementation always returns ``True``.
+
+ :param order: The order object
+ """
+ return True
+
def order_can_retry(self, order: Order) -> bool:
"""
Will be called if the user views the detail page of an unpaid order to determine
whether the user should be presented with an option to retry the payment. The default
implementation always returns False.
+ The retry workflow is also used if a user switches to this payment method for an existing
+ order! Therefore, they can only switch to your p
+
:param order: The order object
"""
return False
def retry_prepare(self, request: HttpRequest, order: Order) -> "bool|str":
+ """
+ Deprecated, use order_prepare instead
+ """
+ raise DeprecationWarning('retry_prepare is deprecated, use order_prepare instead')
+ return self.order_prepare(request, order)
+
+ def order_prepare(self, request: HttpRequest, order: Order) -> "bool|str":
"""
Will be called if the user retries to pay an unpaid order (after the user filled in
- e.g. the form returned by :py:meth:`payment_form`).
+ e.g. the form returned by :py:meth:`payment_form`) or if the user changes the payment
+ method.
It should return and report errors the same way as :py:meth:`checkout_prepare`, but
receives an ``Order`` object instead of a cart object.
@@ -469,6 +491,9 @@ class FreeOrderProvider(BasePaymentProvider):
cart_id=request.session.session_key, event=request.event
).aggregate(sum=Sum('price'))['sum'] == 0
+ def order_change_allowed(self, order: Order) -> bool:
+ return False
+
@receiver(register_payment_providers, dispatch_uid="payment_free")
def register_payment_provider(sender, **kwargs):
diff --git a/src/pretix/plugins/paypal/payment.py b/src/pretix/plugins/paypal/payment.py
index 4c157aac20..e0ceca7531 100644
--- a/src/pretix/plugins/paypal/payment.py
+++ b/src/pretix/plugins/paypal/payment.py
@@ -246,7 +246,7 @@ class Paypal(BasePaymentProvider):
def order_can_retry(self, order):
return True
- def retry_prepare(self, request, order):
+ def order_prepare(self, request, order):
self.init_api()
payment = paypalrestsdk.Payment({
'intent': 'sale',
diff --git a/src/pretix/plugins/stripe/payment.py b/src/pretix/plugins/stripe/payment.py
index 4b98b59f0c..bea5a3a16f 100644
--- a/src/pretix/plugins/stripe/payment.py
+++ b/src/pretix/plugins/stripe/payment.py
@@ -46,7 +46,7 @@ class Stripe(BasePaymentProvider):
def payment_is_valid_session(self, request):
return request.session.get('payment_stripe_token') != ''
- def retry_prepare(self, request, order):
+ def order_prepare(self, request, order):
return self.checkout_prepare(request, None)
def checkout_prepare(self, request, cart):
diff --git a/src/pretix/presale/templates/pretixpresale/event/order.html b/src/pretix/presale/templates/pretixpresale/event/order.html
index 3aae677b60..4646ab5276 100644
--- a/src/pretix/presale/templates/pretixpresale/event/order.html
+++ b/src/pretix/presale/templates/pretixpresale/event/order.html
@@ -33,6 +33,12 @@
{% if order.status == "n" %}
+
{% trans "Payment" %}
diff --git a/src/pretix/presale/templates/pretixpresale/event/order_pay_change.html b/src/pretix/presale/templates/pretixpresale/event/order_pay_change.html
new file mode 100644
index 0000000000..85d8d4be61
--- /dev/null
+++ b/src/pretix/presale/templates/pretixpresale/event/order_pay_change.html
@@ -0,0 +1,65 @@
+{% extends "pretixpresale/event/base.html" %}
+{% load i18n %}
+{% load eventurl %}
+{% block title %}{% trans "Change payment method" %}{% endblock %}
+{% block content %}
+
+ {% blocktrans trimmed with code=order.code %}
+ Change payment method: {{ code }}
+ {% endblocktrans %}
+
+
+ {% blocktrans trimmed %}
+ Please note: If you change your payment method, your order total will change by the
+ amount displayed to the right of each method.
+ {% endblocktrans %}
+
+
+
+{% endblock %}
diff --git a/src/pretix/presale/urls.py b/src/pretix/presale/urls.py
index 8f7615878c..fc94cb1438 100644
--- a/src/pretix/presale/urls.py
+++ b/src/pretix/presale/urls.py
@@ -40,6 +40,9 @@ event_patterns = [
url(r'^order/(?P
[^/]+)/(?P[A-Za-z0-9]+)/pay/complete$',
pretix.presale.views.order.OrderPayComplete.as_view(),
name='event.order.pay.complete'),
+ url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/pay/change',
+ pretix.presale.views.order.OrderPayChangeMethod.as_view(),
+ name='event.order.pay.change'),
url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/download/(?P