forked from CGM_Public/pretix_original
Fix #177 - Allow to change the payment method
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -33,6 +33,12 @@
|
||||
{% if order.status == "n" %}
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading">
|
||||
<div class="pull-right">
|
||||
<a href="{% eventurl event "presale:event.order.pay.change" secret=order.secret order=order.code %}">
|
||||
<span class="fa fa-edit"></span>
|
||||
{% trans "Use different payment method" %}
|
||||
</a>
|
||||
</div>
|
||||
<h3 class="panel-title">
|
||||
{% trans "Payment" %}
|
||||
</h3>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
{% extends "pretixpresale/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load eventurl %}
|
||||
{% block title %}{% trans "Change payment method" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h2>
|
||||
{% blocktrans trimmed with code=order.code %}
|
||||
Change payment method: {{ code }}
|
||||
{% endblocktrans %}
|
||||
</h2>
|
||||
<p>
|
||||
{% 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 %}
|
||||
</p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="panel-group" id="payment_accordion">
|
||||
{% for p in providers %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<label class="radio">
|
||||
<strong class="pull-right">{% if p.fee_diff >= 0 %}+{% else %}-{% endif %} {{ p.fee_diff_abs|floatformat:2 }} {{ event.currency }}</strong>
|
||||
<input type="radio" name="payment" value="{{ p.provider.identifier }}"
|
||||
data-parent="#payment_accordion"
|
||||
{% if selected == p.provider.identifier %}checked="checked"{% endif %}
|
||||
data-toggle="radiocollapse" data-target="#payment_{{ p.provider.identifier }}" />
|
||||
<strong>{{ p.provider.verbose_name }}</strong>
|
||||
</label>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="payment_{{ p.provider.identifier }}"
|
||||
class="panel-collapse collapsed {% if selected == p.provider.identifier %}in{% endif %}">
|
||||
<div class="panel-body form-horizontal">
|
||||
{{ p.form }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="alert alert-info">
|
||||
{% trans "There are no alternative payment providers available for this order." %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="row checkout-button-row">
|
||||
<div class="col-md-4">
|
||||
<a class="btn btn-block btn-default btn-lg"
|
||||
href="{% eventurl request.event "presale:event.order" secret=order.secret order=order.code %}">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
{% if providers %}
|
||||
<button class="btn btn-block btn-primary btn-lg" type="submit">
|
||||
{% trans "Continue" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
@@ -40,6 +40,9 @@ event_patterns = [
|
||||
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/pay/complete$',
|
||||
pretix.presale.views.order.OrderPayComplete.as_view(),
|
||||
name='event.order.pay.complete'),
|
||||
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/pay/change',
|
||||
pretix.presale.views.order.OrderPayChangeMethod.as_view(),
|
||||
name='event.order.pay.change'),
|
||||
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/download/(?P<output>[^/]+)$',
|
||||
pretix.presale.views.order.OrderDownload.as_view(),
|
||||
name='event.order.download'),
|
||||
|
||||
@@ -2,6 +2,8 @@ from datetime import timedelta
|
||||
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.db.models import Sum
|
||||
from django.http import Http404
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.functional import cached_property
|
||||
@@ -14,7 +16,7 @@ from pretix.base.models import (
|
||||
)
|
||||
from pretix.base.models.orders import InvoiceAddress
|
||||
from pretix.base.services.invoices import (
|
||||
generate_invoice, invoice_pdf, invoice_qualified,
|
||||
generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified,
|
||||
)
|
||||
from pretix.base.services.orders import OrderError, cancel_order
|
||||
from pretix.base.services.tickets import generate
|
||||
@@ -125,9 +127,9 @@ class OrderPay(EventViewMixin, OrderDetailMixin, TemplateView):
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
resp = self.payment_provider.retry_prepare(
|
||||
request, self.order
|
||||
)
|
||||
resp = self.payment_provider.order_prepare(request, self.order)
|
||||
if 'payment_change_{}'.format(self.order.pk) in request.session:
|
||||
del request.session['payment_change_{}'.format(self.order.pk)]
|
||||
if isinstance(resp, str):
|
||||
return redirect(resp)
|
||||
elif resp is True:
|
||||
@@ -181,7 +183,8 @@ class OrderPayDo(EventViewMixin, OrderDetailMixin, TemplateView):
|
||||
self.request = request
|
||||
if not self.order:
|
||||
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
||||
if not self.payment_provider.order_can_retry(self.order) or not self.payment_provider.is_enabled:
|
||||
can_do = self.payment_provider.order_can_retry(self.order) or 'payment_change_{}'.format(self.order.pk) in request.session
|
||||
if not can_do 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)
|
||||
@@ -192,6 +195,8 @@ class OrderPayDo(EventViewMixin, OrderDetailMixin, TemplateView):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
resp = self.payment_provider.payment_perform(request, self.order)
|
||||
if 'payment_change_{}'.format(self.order.pk) in request.session:
|
||||
del request.session['payment_change_{}'.format(self.order.pk)]
|
||||
return redirect(resp or self.get_order_url())
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@@ -213,8 +218,8 @@ class OrderPayComplete(EventViewMixin, OrderDetailMixin, View):
|
||||
self.request = request
|
||||
if not self.order:
|
||||
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
||||
if (not self.payment_provider.payment_is_valid_session(request)
|
||||
or not self.payment_provider.is_enabled):
|
||||
if (not self.payment_provider.payment_is_valid_session(request) or
|
||||
not self.payment_provider.is_enabled):
|
||||
messages.error(request, _('The payment information you entered was incomplete.'))
|
||||
return redirect(self.get_payment_url())
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
@@ -230,6 +235,96 @@ class OrderPayComplete(EventViewMixin, OrderDetailMixin, View):
|
||||
})
|
||||
|
||||
|
||||
class OrderPayChangeMethod(EventViewMixin, OrderDetailMixin, TemplateView):
|
||||
template_name = 'pretixpresale/event/order_pay_change.html'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.request = request
|
||||
if not self.order:
|
||||
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
||||
if self.order.status not in (Order.STATUS_PENDING, Order.STATUS_EXPIRED):
|
||||
messages.error(request, _('The payment method for this order cannot be changed.'))
|
||||
return redirect(self.get_order_url() + '?paid=yes')
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_payment_url(self):
|
||||
return eventreverse(self.request.event, 'presale:event.order.pay', kwargs={
|
||||
'order': self.order.code,
|
||||
'secret': self.order.secret
|
||||
})
|
||||
|
||||
@cached_property
|
||||
def _total_order_value(self):
|
||||
return self.order.positions.aggregate(sum=Sum('price'))['sum']
|
||||
|
||||
@cached_property
|
||||
def provider_forms(self):
|
||||
providers = []
|
||||
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:
|
||||
continue
|
||||
if not provider.is_enabled or not provider.order_change_allowed(self.order):
|
||||
continue
|
||||
fee = provider.calculate_fee(self._total_order_value)
|
||||
providers.append({
|
||||
'provider': provider,
|
||||
'fee': fee,
|
||||
'fee_diff': fee - self.order.payment_fee,
|
||||
'fee_diff_abs': abs(fee - self.order.payment_fee),
|
||||
'form': provider.payment_form_render(self.request)
|
||||
})
|
||||
return providers
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.request = request
|
||||
for p in self.provider_forms:
|
||||
if p['provider'].identifier == request.POST.get('payment', ''):
|
||||
request.session['payment'] = p['provider'].identifier
|
||||
request.session['payment_change_{}'.format(self.order.pk)] = '1'
|
||||
|
||||
resp = p['provider'].order_prepare(request, self.order)
|
||||
if resp:
|
||||
with transaction.atomic():
|
||||
new_fee = p['provider'].calculate_fee(self._total_order_value)
|
||||
self.order.log_action('pretix.event.order.payment.changed', {
|
||||
'old_fee': self.order.payment_fee,
|
||||
'new_fee': new_fee,
|
||||
'old_provider': self.order.payment_provider,
|
||||
'new_provider': p['provider'].identifier
|
||||
})
|
||||
self.order.payment_provider = p['provider'].identifier
|
||||
self.order.payment_fee = new_fee
|
||||
self.order._calculate_tax()
|
||||
self.order.save()
|
||||
|
||||
i = self.order.invoices.filter(is_cancellation=False).last()
|
||||
if i:
|
||||
generate_cancellation(i)
|
||||
generate_invoice(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)
|
||||
messages.error(self.request, _("Please select a payment method."))
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['order'] = self.order
|
||||
ctx['providers'] = self.provider_forms
|
||||
return ctx
|
||||
|
||||
def get_confirm_url(self):
|
||||
return eventreverse(self.request.event, 'presale:event.order.pay.confirm', kwargs={
|
||||
'order': self.order.code,
|
||||
'secret': self.order.secret
|
||||
})
|
||||
|
||||
|
||||
class OrderModify(EventViewMixin, OrderDetailMixin, QuestionsViewMixin, TemplateView):
|
||||
template_name = "pretixpresale/event/order_modify.html"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user