From f04c43abdccf721de52c2946b03f5af66db5364e Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Thu, 25 Jun 2015 15:49:14 +0200 Subject: [PATCH] Refs #33 -- Added UI and Stripe support for retrying failed payments --- doc/development/api/payment.rst | 14 ++- src/pretix/base/payment.py | 48 +++++-- src/pretix/plugins/banktransfer/payment.py | 6 +- src/pretix/plugins/paypal/payment.py | 8 +- src/pretix/plugins/stripe/payment.py | 42 +++++-- src/pretix/plugins/stripe/signals.py | 4 +- .../pretixplugins/stripe/pretix-stripe.js | 3 +- .../pretixplugins/stripe/pending.html | 11 +- .../templates/pretixpresale/event/order.html | 5 + .../pretixpresale/event/order_pay.html | 32 +++++ .../event/order_pay_confirm.html | 43 +++++++ src/pretix/presale/urls.py | 4 + src/pretix/presale/views/checkout.py | 6 +- src/pretix/presale/views/order.py | 118 +++++++++++++++--- 14 files changed, 287 insertions(+), 57 deletions(-) create mode 100644 src/pretix/presale/templates/pretixpresale/event/order_pay.html create mode 100644 src/pretix/presale/templates/pretixpresale/event/order_pay_confirm.html diff --git a/doc/development/api/payment.rst b/doc/development/api/payment.rst index 681688ddf..49deb850a 100644 --- a/doc/development/api/payment.rst +++ b/doc/development/api/payment.rst @@ -62,23 +62,23 @@ The provider class .. automethod:: settings_content_render - .. automethod:: checkout_form_render + .. automethod:: payment_form_render - .. automethod:: checkout_form + .. automethod:: payment_form .. automethod:: is_allowed - .. autoattribute:: checkout_form_fields + .. autoattribute:: payment_form_fields .. automethod:: checkout_prepare - .. automethod:: checkout_is_valid_session + .. automethod:: payment_is_valid_session .. automethod:: checkout_confirm_render This is an abstract method, you **must** override this! - .. automethod:: checkout_perform + .. automethod:: payment_perform .. automethod:: order_pending_mail_render @@ -86,6 +86,10 @@ The provider class This is an abstract method, you **must** override this! + .. automethod:: order_can_retry + + .. automethod:: retry_prepare + .. automethod:: order_paid_render .. automethod:: order_control_render diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py index 259dec1aa..ca7818c29 100644 --- a/src/pretix/base/payment.py +++ b/src/pretix/base/payment.py @@ -129,7 +129,7 @@ class BasePaymentProvider: pass @property - def checkout_form_fields(self) -> dict: + def payment_form_fields(self) -> dict: """ This is used by the default implementation of :py:meth:`checkout_form`. It should return an object similar to :py:attr:`settings_form_fields`. @@ -138,7 +138,7 @@ class BasePaymentProvider: """ return {} - def checkout_form(self, request: HttpRequest) -> Form: + def payment_form(self, request: HttpRequest) -> Form: """ This is called by the default implementation of :py:meth:`checkout_form_render` to obtain the form that is displayed to the user during the checkout @@ -155,7 +155,7 @@ class BasePaymentProvider: if k.startswith('payment_%s_' % self.identifier) } ) - form.fields = self.checkout_form_fields + form.fields = self.payment_form_fields return form def is_allowed(self, request: HttpRequest) -> bool: @@ -168,7 +168,7 @@ class BasePaymentProvider: """ return True - def checkout_form_render(self, request: HttpRequest) -> str: + def payment_form_render(self, request: HttpRequest) -> str: """ When the user selects this provider as his prefered payment method, he will be shown the HTML you return from this method. @@ -178,7 +178,7 @@ class BasePaymentProvider: the user to fill out form fields, you should just return a paragraph of explainatory text. """ - form = self.checkout_form(request) + form = self.payment_form(request) template = get_template('pretixpresale/event/checkout_payment_form_default.html') ctx = {'request': request, 'form': form} return template.render(ctx) @@ -209,7 +209,7 @@ class BasePaymentProvider: to the user (or the normal form validation error messages). The default implementation stores the input into the form returned by - :py:meth:`checkout_form` in the user's session. + :py:meth:`payment_form` in the user's session. If your payment method requires you to redirect the user to an external provider, this might be the place to do so. @@ -233,7 +233,7 @@ class BasePaymentProvider: payment_fee: The fee for the payment method. """ - form = self.checkout_form(request) + form = self.payment_form(request) if form.is_valid(): for k, v in form.cleaned_data.items(): request.session['payment_%s_%s' % (self.identifier, k)] = v @@ -241,7 +241,7 @@ class BasePaymentProvider: else: return False - def checkout_is_valid_session(self, request: HttpRequest) -> bool: + def payment_is_valid_session(self, request: HttpRequest) -> bool: """ This is called at the time the user tries to place the order. It should return ``True``, if the user's session is valid and all data your payment provider requires @@ -249,7 +249,7 @@ class BasePaymentProvider: """ raise NotImplementedError() # NOQA - def checkout_perform(self, request: HttpRequest, order: Order) -> str: + def payment_perform(self, request: HttpRequest, order: Order) -> str: """ After the user confirmed his purchase, this method will be called to complete the payment process. This is the place to actually move the money, if applicable. @@ -299,6 +299,32 @@ class BasePaymentProvider: """ raise NotImplementedError() # NOQA + 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. + + :param order: The order object + """ + return False + + def retry_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`). + + It should return and report errors the same way as :py:meth:`checkout_prepare`, but + receives an ``Order`` object instead of a cart object. + """ + form = self.payment_form(request) + if form.is_valid(): + for k, v in form.cleaned_data.items(): + request.session['payment_%s_%s' % (self.identifier, k)] = v + return True + else: + return False + def order_paid_render(self, request: HttpRequest, order: Order) -> str: """ Will be called if the user views the detail page of an paid order which is @@ -377,14 +403,14 @@ class FreeOrderProvider(BasePaymentProvider): def order_pending_render(self, request: HttpRequest, order: Order) -> str: pass - def checkout_is_valid_session(self, request: HttpRequest) -> bool: + def payment_is_valid_session(self, request: HttpRequest) -> bool: return True @property def verbose_name(self) -> str: return _("Free of charge") - def checkout_perform(self, request: HttpRequest, order: Order): + def payment_perform(self, request: HttpRequest, order: Order): mark_order_paid(order, 'free') @property diff --git a/src/pretix/plugins/banktransfer/payment.py b/src/pretix/plugins/banktransfer/payment.py index 02ba1485e..6dec27403 100644 --- a/src/pretix/plugins/banktransfer/payment.py +++ b/src/pretix/plugins/banktransfer/payment.py @@ -23,7 +23,7 @@ class BankTransfer(BasePaymentProvider): ] ) - def checkout_form_render(self, request) -> str: + def payment_form_render(self, request) -> str: template = get_template('pretixplugins/banktransfer/checkout_payment_form.html') ctx = {'request': request, 'event': self.event, 'settings': self.settings} return template.render(ctx) @@ -31,11 +31,11 @@ class BankTransfer(BasePaymentProvider): def checkout_prepare(self, request, total): return True - def checkout_is_valid_session(self, request): + def payment_is_valid_session(self, request): return True def checkout_confirm_render(self, request): - form = self.checkout_form(request) + form = self.payment_form(request) template = get_template('pretixplugins/banktransfer/checkout_payment_confirm.html') ctx = {'request': request, 'form': form, 'settings': self.settings} return template.render(ctx) diff --git a/src/pretix/plugins/paypal/payment.py b/src/pretix/plugins/paypal/payment.py index 95a0509b1..5c48944d2 100644 --- a/src/pretix/plugins/paypal/payment.py +++ b/src/pretix/plugins/paypal/payment.py @@ -22,7 +22,7 @@ class Paypal(BasePaymentProvider): identifier = 'paypal' verbose_name = _('PayPal') - checkout_form_fields = OrderedDict([ + payment_form_fields = OrderedDict([ ]) @property @@ -55,11 +55,11 @@ class Paypal(BasePaymentProvider): client_id=self.settings.get('client_id'), client_secret=self.settings.get('secret')) - def checkout_is_valid_session(self, request): + def payment_is_valid_session(self, request): return (request.session.get('payment_paypal_id', '') != '' and request.session.get('payment_paypal_payer', '') != '') - def checkout_form_render(self, request) -> str: + def payment_form_render(self, request) -> str: template = get_template('pretixplugins/paypal/checkout_payment_form.html') ctx = {'request': request, 'event': self.event, 'settings': self.settings} return template.render(ctx) @@ -135,7 +135,7 @@ class Paypal(BasePaymentProvider): ctx = {'request': request, 'event': self.event, 'settings': self.settings} return template.render(ctx) - def checkout_perform(self, request, order) -> str: + def payment_perform(self, request, order) -> str: """ Will be called if the user submitted his order successfully to initiate the payment process. diff --git a/src/pretix/plugins/stripe/payment.py b/src/pretix/plugins/stripe/payment.py index 73de287de..695546ce5 100644 --- a/src/pretix/plugins/stripe/payment.py +++ b/src/pretix/plugins/stripe/payment.py @@ -3,7 +3,6 @@ import json import logging from django.contrib import messages -from django.core.urlresolvers import reverse from django.template.loader import get_template from django.utils.translation import ugettext_lazy as _ from django import forms @@ -43,9 +42,12 @@ class Stripe(BasePaymentProvider): build_absolute_uri('plugins:stripe:webhook') ) - def checkout_is_valid_session(self, request): + def payment_is_valid_session(self, request): return request.session.get('payment_stripe_token') != '' + def retry_prepare(self, request, order): + return self.checkout_prepare(request, None) + def checkout_prepare(self, request, cart): token = request.POST.get('stripe_token', '') request.session['payment_stripe_token'] = token @@ -56,7 +58,7 @@ class Stripe(BasePaymentProvider): return False return True - def checkout_form_render(self, request) -> str: + def payment_form_render(self, request) -> str: template = get_template('pretixplugins/stripe/checkout_payment_form.html') ctx = {'request': request, 'event': self.event, 'settings': self.settings} return template.render(ctx) @@ -70,7 +72,10 @@ class Stripe(BasePaymentProvider): ctx = {'request': request, 'event': self.event, 'settings': self.settings} return template.render(ctx) - def checkout_perform(self, request, order) -> str: + def order_can_retry(self, order): + return True + + def payment_perform(self, request, order) -> str: self._init_api() try: charge = stripe.Charge.create( @@ -82,23 +87,31 @@ class Stripe(BasePaymentProvider): 'event': self.event.identity, 'code': order.code }, - idempotency_key=self.event.identity + order.code # TODO: Use something better + # TODO: Is this sufficient? + idempotency_key=self.event.identity + order.code + request.session['payment_stripe_token'] ) except stripe.error.CardError as e: err = e.json_body['error'] messages.error(request, _('Stripe reported an error with your card: %s' % err['message'])) logger.info('Stripe card error: %s' % str(err)) + order = order.clone() + order.payment_info = json.dumps({ + 'error': True, + 'message': err['message'], + }) + order.save() except stripe.error.InvalidRequestError or stripe.error.AuthenticationError or stripe.error.APIConnectionError \ - as e: + or stripe.error.StripeError as e: err = e.json_body['error'] messages.error(request, _('We had trouble communicating with Stripe. Please try again and get in touch ' 'with us if this problem persists.')) logger.error('Stripe error: %s' % str(err)) - except stripe.error.StripeError as e: - err = e.json_body['error'] - messages.error(request, _('We had trouble processing the payment. Please try again and get ' - 'in touch with us if this problem persists.')) - logger.error('Stripe error: %s' % str(err)) + order = order.clone() + order.payment_info = json.dumps({ + 'error': True, + 'message': err['message'], + }) + order.save() else: if charge.status == 'succeeded' and charge.paid: try: @@ -112,11 +125,16 @@ class Stripe(BasePaymentProvider): order = order.clone() order.payment_info = str(charge) order.save() + del request.session['payment_stripe_token'] def order_pending_render(self, request, order) -> str: + if order.payment_info: + payment_info = json.loads(order.payment_info) + else: + payment_info = None template = get_template('pretixplugins/stripe/pending.html') ctx = {'request': request, 'event': self.event, 'settings': self.settings, - 'order': order} + 'order': order, 'payment_info': payment_info} return template.render(ctx) def order_control_render(self, request, order) -> str: diff --git a/src/pretix/plugins/stripe/signals.py b/src/pretix/plugins/stripe/signals.py index 442398f13..eeb0d403f 100644 --- a/src/pretix/plugins/stripe/signals.py +++ b/src/pretix/plugins/stripe/signals.py @@ -11,15 +11,17 @@ from pretix.presale.signals import html_head @receiver(register_payment_providers) def register_payment_provider(sender, **kwargs): from .payment import Stripe + return Stripe @receiver(html_head) def html_head_presale(sender, request=None, **kwargs): from .payment import Stripe + provider = Stripe(sender) url = resolve(request.path_info) - if provider.is_enabled and "checkout.payment" in url.url_name: + if provider.is_enabled and ("checkout.payment" in url.url_name or "order.pay" in url.url_name): template = get_template('pretixplugins/stripe/presale_head.html') ctx = Context({'event': sender, 'settings': provider.settings}) return template.render(ctx) diff --git a/src/pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js b/src/pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js index 093684a0a..c029c6127 100644 --- a/src/pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js +++ b/src/pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js @@ -85,7 +85,8 @@ $(function() { .keyup(pretixstripe.validate_cvc) $("#stripe_number").parents("form").submit( function () { - if ($("input[name=payment][value=stripe]").prop('checked') && $("#stripe_token").val() == "") { + if (($("input[name=payment][value=stripe]").prop('checked') || $("input[name=payment]").length === 0) + && $("#stripe_token").val() == "") { pretixstripe.request(); return false; } diff --git a/src/pretix/plugins/stripe/templates/pretixplugins/stripe/pending.html b/src/pretix/plugins/stripe/templates/pretixplugins/stripe/pending.html index 8e9e2c12a..d9919cf37 100644 --- a/src/pretix/plugins/stripe/templates/pretixplugins/stripe/pending.html +++ b/src/pretix/plugins/stripe/templates/pretixplugins/stripe/pending.html @@ -1,5 +1,12 @@ {% load i18n %}

{% blocktrans trimmed %} - The credit card transaction could not be completed. Please contact us. -{% endblocktrans %}

+ The credit card transaction could not be completed for the following reason: +{% endblocktrans %} +
+ {% if payment_info and payment_info.error %} + {{ payment_info.message }} + {% else %} + {% trans "Unknown reason" %} + {% endif %} +

diff --git a/src/pretix/presale/templates/pretixpresale/event/order.html b/src/pretix/presale/templates/pretixpresale/event/order.html index 8d2bf4063..1c30c0fee 100644 --- a/src/pretix/presale/templates/pretixpresale/event/order.html +++ b/src/pretix/presale/templates/pretixpresale/event/order.html @@ -17,10 +17,15 @@
+ {% if can_retry %} + {% trans "Complete payment" %} + {% endif %} {{ payment }} {% blocktrans trimmed with date=order.expires|date:"SHORT_DATE_FORMAT" %} Please complete your payment before {{ date }} {% endblocktrans %} +
{% endif %} diff --git a/src/pretix/presale/templates/pretixpresale/event/order_pay.html b/src/pretix/presale/templates/pretixpresale/event/order_pay.html new file mode 100644 index 000000000..c5648270a --- /dev/null +++ b/src/pretix/presale/templates/pretixpresale/event/order_pay.html @@ -0,0 +1,32 @@ +{% extends "pretixpresale/event/base.html" %} +{% load i18n %} +{% block title %}{% trans "Cancel order" %}{% endblock %} +{% block content %} +

+ {% blocktrans trimmed with code=order.code %} + Pay order: {{ code }} + {% endblocktrans %} +

+ +
+ {% csrf_token %} +
+ {{ form }} +
+
+ +
+ +
+
+
+
+ +{% endblock %} diff --git a/src/pretix/presale/templates/pretixpresale/event/order_pay_confirm.html b/src/pretix/presale/templates/pretixpresale/event/order_pay_confirm.html new file mode 100644 index 000000000..61ec667c1 --- /dev/null +++ b/src/pretix/presale/templates/pretixpresale/event/order_pay_confirm.html @@ -0,0 +1,43 @@ +{% extends "pretixpresale/event/base.html" %} +{% load i18n %} +{% block title %}{% trans "Cancel order" %}{% endblock %} +{% block content %} +

+ {% blocktrans trimmed with code=order.code %} + Pay order: {{ code }} + {% endblocktrans %} +

+ +
+ {% csrf_token %} + +

{% trans "Please confirm the following payment details." %}

+
+
+
+

+ {{ payment_provider.verbose_name }} +

+
+
+ {{ payment }} +
+
+
+
+ +
+ +
+
+
+
+ +{% endblock %} diff --git a/src/pretix/presale/urls.py b/src/pretix/presale/urls.py index 0d4b9c504..24ed45276 100644 --- a/src/pretix/presale/urls.py +++ b/src/pretix/presale/urls.py @@ -23,6 +23,10 @@ urlpatterns = [ name='event.order.cancel'), url(r'^order/(?P[^/]+)/modify$', pretix.presale.views.order.OrderModify.as_view(), name='event.order.modify'), + url(r'^order/(?P[^/]+)/pay$', pretix.presale.views.order.OrderPay.as_view(), + name='event.order.pay'), + url(r'^order/(?P[^/]+)/pay/confirm$', pretix.presale.views.order.OrderPayDo.as_view(), + name='event.order.pay.confirm'), url(r'^order/(?P[^/]+)/download/(?P[^/]+)$', pretix.presale.views.order.OrderDownload.as_view(), name='event.order.download'), url(r'^login$', pretix.presale.views.event.EventLogin.as_view(), name='event.checkout.login'), diff --git a/src/pretix/presale/views/checkout.py b/src/pretix/presale/views/checkout.py index 0a5c9a758..ed200746f 100644 --- a/src/pretix/presale/views/checkout.py +++ b/src/pretix/presale/views/checkout.py @@ -156,7 +156,7 @@ class PaymentDetails(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, providers.append({ 'provider': provider, 'fee': fee, - 'form': provider.checkout_form_render(self.request), + 'form': provider.payment_form_render(self.request), }) return providers @@ -220,7 +220,7 @@ class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, Ch if 'payment' not in request.session or not self.payment_provider: messages.error(request, _('The payment information you entered was incomplete.')) return redirect(self.get_payment_url()) - if not self.payment_provider.checkout_is_valid_session(request) or \ + if not self.payment_provider.payment_is_valid_session(request) or \ not self.payment_provider.is_enabled or \ not self.payment_provider.is_allowed(request): messages.error(request, _('The payment information you entered was incomplete.')) @@ -259,7 +259,7 @@ class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, Ch return redirect(self.get_confirm_url()) else: messages.success(request, _('Your order has been placed.')) - resp = self.payment_provider.checkout_perform(request, order) + resp = self.payment_provider.payment_perform(request, order) return redirect(resp or self.get_order_url(order)) def get_previous_url(self): diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py index 65267b0aa..2f189a90f 100644 --- a/src/pretix/presale/views/order.py +++ b/src/pretix/presale/views/order.py @@ -13,6 +13,7 @@ from pretix.presale.views.checkout import QuestionsViewMixin class OrderDetailMixin: + @cached_property def order(self): try: @@ -24,6 +25,21 @@ class OrderDetailMixin: except Order.DoesNotExist: return None + @cached_property + def payment_provider(self): + responses = register_payment_providers.send(self.request.event) + for receiver, response in responses: + provider = response(self.request.event) + if provider.identifier == self.order.payment_provider: + return provider + + def get_order_url(self): + return reverse('presale:event.order', kwargs={ + 'event': self.request.event.slug, + 'organizer': self.request.event.organizer.slug, + 'order': self.order.code, + }) + class OrderDetails(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, CartDisplayMixin, TemplateView): @@ -35,14 +51,6 @@ class OrderDetails(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, return HttpResponseNotFound(_('Unknown order code or order does belong to another user.')) return super().get(request, *args, **kwargs) - @cached_property - def payment_provider(self): - responses = register_payment_providers.send(self.request.event) - for receiver, response in responses: - provider = response(self.request.event) - if provider.identifier == self.order.payment_provider: - return provider - @cached_property def download_buttons(self): buttons = [] @@ -75,11 +83,94 @@ class OrderDetails(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, ) if self.order.status == Order.STATUS_PENDING: ctx['payment'] = self.payment_provider.order_pending_render(self.request, self.order) + ctx['can_retry'] = self.payment_provider.order_can_retry(self.order) and self.payment_provider.is_enabled elif self.order.status == Order.STATUS_PAID: ctx['payment'] = self.payment_provider.order_paid_render(self.request, self.order) + ctx['can_retry'] = False return ctx +class OrderPay(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, TemplateView): + template_name = "pretixpresale/event/order_pay.html" + + def dispatch(self, request, *args, **kwargs): + self.request = request + if not self.order: + return HttpResponseNotFound(_('Unknown order code or order does belong to another user.')) + if not self.payment_provider.order_can_retry(self.order) or not self.payment_provider.is_enabled: + messages.error(request, _('The payment for this order cannot be continued.')) + return redirect(self.get_order_url()) + return super().dispatch(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + resp = self.payment_provider.retry_prepare( + request, self.order + ) + if isinstance(resp, str): + return redirect(resp) + elif resp is True: + return redirect(self.get_confirm_url()) + else: + return self.get(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + ctx['order'] = self.order + ctx['form'] = self.form + return ctx + + @cached_property + def form(self): + return self.payment_provider.payment_form_render(self.request) + + def get_confirm_url(self): + return reverse('presale:event.order.pay.confirm', kwargs={ + 'event': self.request.event.slug, + 'organizer': self.request.event.organizer.slug, + 'order': self.order.code, + }) + + +class OrderPayDo(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, TemplateView): + template_name = "pretixpresale/event/order_pay_confirm.html" + + def dispatch(self, request, *args, **kwargs): + self.request = request + if not self.order: + return HttpResponseNotFound(_('Unknown order code or order does belong to another user.')) + if not self.payment_provider.order_can_retry(self.order) or not self.payment_provider.is_enabled: + messages.error(request, _('The payment for this order cannot be continued.')) + return redirect(self.get_order_url()) + if not self.payment_provider.payment_is_valid_session(request) or \ + not self.payment_provider.is_enabled or \ + not self.payment_provider.is_allowed(request): + messages.error(request, _('The payment information you entered was incomplete.')) + return redirect(self.get_payment_url()) + return super().dispatch(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + resp = self.payment_provider.payment_perform(request, self.order) + return redirect(resp or self.get_order_url()) + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + ctx['order'] = self.order + ctx['payment'] = self.payment_provider.checkout_confirm_render(self.request) + ctx['payment_provider'] = self.payment_provider + return ctx + + @cached_property + def form(self): + return self.payment_provider.payment_form_render(self.request) + + def get_payment_url(self): + return reverse('presale:event.order.pay', kwargs={ + 'event': self.request.event.slug, + 'organizer': self.request.event.organizer.slug, + 'order': self.order.code, + }) + + class OrderModify(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, QuestionsViewMixin, TemplateView): template_name = "pretixpresale/event/order_modify.html" @@ -96,12 +187,6 @@ class OrderModify(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, )) def post(self, request, *args, **kwargs): - self.request = request - self.kwargs = kwargs - if not self.order: - return HttpResponseNotFound(_('Unknown order code or order does belong to another user.')) - if not self.order.can_modify_answers: - return HttpResponseForbidden(_('You cannot modify this order')) failed = not self.save() if failed: messages.error(self.request, @@ -113,13 +198,16 @@ class OrderModify(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, order=self.order.code) def get(self, request, *args, **kwargs): + return super().get(request, *args, **kwargs) + + def dispatch(self, request, *args, **kwargs): self.request = request self.kwargs = kwargs if not self.order: return HttpResponseNotFound(_('Unknown order code or order does belong to another user.')) if not self.order.can_modify_answers: return HttpResponseForbidden(_('You cannot modify this order')) - return super().get(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs)