forked from CGM_Public/pretix_original
Display payment details
This commit is contained in:
@@ -714,7 +714,7 @@ class Item(Versionable):
|
||||
if self.event:
|
||||
self.event.get_cache().clear()
|
||||
|
||||
def delete(self):
|
||||
def delete(self, *args, **kwargs):
|
||||
self.deleted = True
|
||||
self.active = False
|
||||
super().save()
|
||||
@@ -1229,11 +1229,13 @@ class Order(Versionable):
|
||||
STATUS_PAID = "p"
|
||||
STATUS_EXPIRED = "e"
|
||||
STATUS_CANCELLED = "c"
|
||||
STATUS_REFUNDED = "r"
|
||||
STATUS_CHOICE = (
|
||||
(STATUS_PAID, _("pending")),
|
||||
(STATUS_PENDING, _("paid")),
|
||||
(STATUS_EXPIRED, _("expired")),
|
||||
(STATUS_CANCELLED, _("cancelled")),
|
||||
(STATUS_REFUNDED, _("refunded"))
|
||||
)
|
||||
|
||||
code = models.CharField(
|
||||
|
||||
@@ -150,3 +150,28 @@ class BasePaymentProvider:
|
||||
:param order: The order object
|
||||
"""
|
||||
return None
|
||||
|
||||
def order_pending_render(self, request, order) -> str:
|
||||
"""
|
||||
Will be called if the user views the detail page of an unpaid order which is
|
||||
associated with this payment provider.
|
||||
|
||||
It should return HTML code which should be displayed to the user. It should contian
|
||||
instructions on how to continue with the payment process, either in form of text
|
||||
or buttons/links/etc.
|
||||
|
||||
:param order: The order object
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def order_paid_render(self, request, order) -> str:
|
||||
"""
|
||||
Will be called if the user views the detail page of an paid order which is
|
||||
associated with this payment provider.
|
||||
|
||||
It should return HTML code which should be displayed to the user or None,
|
||||
if there is nothing to say.
|
||||
|
||||
:param order: The order object
|
||||
"""
|
||||
return None
|
||||
|
||||
@@ -35,3 +35,8 @@ class BankTransfer(BasePaymentProvider):
|
||||
template = get_template('pretixplugins/banktransfer/checkout_payment_confirm.html')
|
||||
ctx = Context({'request': request, 'form': form, 'settings': self.settings})
|
||||
return template.render(ctx)
|
||||
|
||||
def order_pending_render(self, request, order) -> str:
|
||||
template = get_template('pretixplugins/banktransfer/pending.html')
|
||||
ctx = Context({'request': request, 'order': order, 'settings': self.settings})
|
||||
return template.render(ctx)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{% load i18n %}
|
||||
|
||||
<p>{% blocktrans trimmed %}
|
||||
Please transfer the full amount to the following bank account:
|
||||
{% endblocktrans %}</p>
|
||||
|
||||
<address>
|
||||
{{ settings.bank_details|linebreaksbr }}<br />
|
||||
<strong>{% trans "Reference code (important):" %} {{ order.full_code }}</strong>
|
||||
</address>
|
||||
@@ -25,6 +25,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel-body address:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.cart-row, .product-row {
|
||||
padding: 10px 0;
|
||||
|
||||
@@ -43,6 +48,15 @@
|
||||
&.total {
|
||||
border-top: 1px solid @table-border-color;
|
||||
}
|
||||
|
||||
dl {
|
||||
padding-left: 20px;
|
||||
margin-bottom: 0;
|
||||
|
||||
dd {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.panel-primary .panel-heading a {
|
||||
color: white;
|
||||
|
||||
@@ -1,44 +1,57 @@
|
||||
{% load i18n %}
|
||||
{% for line in cart.positions %}
|
||||
<div class="row-fluid cart-row">
|
||||
<div class="col-md-4 col-xs-6">
|
||||
<div class="{% if line.has_questions %}col-md-9 col-xs-12{% else %}col-md-4 col-xs-6{% endif %}">
|
||||
<strong>{{ line.item }}</strong>
|
||||
{% if line.variation %}
|
||||
– {{ line.variation }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-6 price">
|
||||
{{ event.currency }} {{ line.price|floatformat:2 }}
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6 count">
|
||||
{% if editable %}
|
||||
<form action="{% url "presale:event.cart.remove" event=event.slug organizer=event.organizer.slug %}"
|
||||
{% if line.has_questions %}
|
||||
<dl>
|
||||
{% if line.item.admission and event.settings.attendee_names_asked == 'True' %}
|
||||
<dt>{% trans "Attendee name" %}</dt>
|
||||
<dd>{% if line.attendee_name %}{{ line.attendee_name }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}</dd>
|
||||
{% endif %}
|
||||
{% for q in line.questions %}
|
||||
<dt>{{ q.question }}</dt>
|
||||
<dd>{% if q.answer %}{{ q.answer }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
{% else %}
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-6 price">
|
||||
{{ event.currency }} {{ line.price|floatformat:2 }}
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6 count">
|
||||
{% if editable %}
|
||||
<form action="{% url "presale:event.cart.remove" event=event.slug organizer=event.organizer.slug %}"
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
{% if line.variation %}
|
||||
<input type="hidden" name="variation_{{ line.item.identity }}_{{ line.variation.identity }}"
|
||||
value="1" />
|
||||
{% else %}
|
||||
<input type="hidden" name="item_{{ line.item.identity }}"
|
||||
value="1" />
|
||||
{% endif %}
|
||||
<button class="btn btn-mini btn-link"><i class="fa fa-minus"></i></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{{ line.count }}
|
||||
{% if editable %}
|
||||
<form action="{% url "presale:event.cart.add" event=event.slug organizer=event.organizer.slug %}"
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
{% if line.variation %}
|
||||
<input type="hidden" name="variation_{{ line.item.identity }}_{{ line.variation.identity }}"
|
||||
value="1" />
|
||||
{% else %}
|
||||
<input type="hidden" name="item_{{ line.item.identity }}"
|
||||
{% csrf_token %}
|
||||
{% if line.variation %}
|
||||
<input type="hidden" name="variation_{{ line.item.identity }}_{{ line.variation.identity }}"
|
||||
value="1" />
|
||||
{% else %}
|
||||
<input type="hidden" name="item_{{ line.item.identity }}"
|
||||
value="1" />
|
||||
{% endif %}
|
||||
<button class="btn btn-mini btn-link"><i class="fa fa-plus"></i></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<button class="btn btn-mini btn-link"><i class="fa fa-minus"></i></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{{ line.count }}
|
||||
{% if editable %}
|
||||
<form action="{% url "presale:event.cart.add" event=event.slug organizer=event.organizer.slug %}"
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
{% if line.variation %}
|
||||
<input type="hidden" name="variation_{{ line.item.identity }}_{{ line.variation.identity }}"
|
||||
value="1" />
|
||||
{% else %}
|
||||
<input type="hidden" name="item_{{ line.item.identity }}"
|
||||
value="1" />
|
||||
{% endif %}
|
||||
<button class="btn btn-mini btn-link"><i class="fa fa-plus"></i></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-6 price">
|
||||
|
||||
69
src/pretix/presale/templates/pretixpresale/event/order.html
Normal file
69
src/pretix/presale/templates/pretixpresale/event/order.html
Normal file
@@ -0,0 +1,69 @@
|
||||
{% extends "pretixpresale/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Order details" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h2>
|
||||
{% blocktrans trimmed with code=order.code %}
|
||||
Your order: {{ code }}
|
||||
{% endblocktrans %}
|
||||
{% if order.status == "n" %}
|
||||
<span class="label label-warning">{% trans "Payment pending" %}</span>
|
||||
{% elif order.status == "p" %}
|
||||
<span class="label label-success">{% trans "Paid" %}</span>
|
||||
{% elif order.status == "e" %}
|
||||
<span class="label label-danger">{% trans "Payment pending" %}</span>
|
||||
{% elif order.status == "c" %}
|
||||
<span class="label label-danger">{% trans "Cancelled" %}</span>
|
||||
{% elif order.status == "r" %}
|
||||
<span class="label label-danger">{% trans "Refunded" %}</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
{% if order.status == "n" %}
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% trans "Payment" %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{{ payment }}
|
||||
<strong>{% blocktrans trimmed with date=order.expires|date %}
|
||||
Please complete your payment before {{ date }}
|
||||
{% endblocktrans %}</strong>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="panel panel-primary cart">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% trans "Ordered items" %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% include "pretixpresale/event/fragment_cart.html" with cart=cart event=request.event editable=False %}
|
||||
</div>
|
||||
</div>
|
||||
{% if order.status == "n" %}
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-danger">{% trans "Cancel order" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if order.status == "p" and payment %}
|
||||
<div class="panel panel-success">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% trans "Payment" %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{{ payment }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -3,6 +3,7 @@ from django.conf.urls import patterns, url, include
|
||||
import pretix.presale.views.event
|
||||
import pretix.presale.views.cart
|
||||
import pretix.presale.views.checkout
|
||||
import pretix.presale.views.order
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
@@ -18,7 +19,7 @@ urlpatterns = patterns(
|
||||
name='event.checkout.payment'),
|
||||
url(r'^checkout/confirm$', pretix.presale.views.checkout.OrderConfirm.as_view(),
|
||||
name='event.checkout.confirm'),
|
||||
url(r'^order/(?P<order>[^/]+)/$', pretix.presale.views.checkout.OrderConfirm.as_view(),
|
||||
url(r'^order/(?P<order>[^/]+)/$', pretix.presale.views.order.OrderDetails.as_view(),
|
||||
name='event.order'),
|
||||
url(r'^login$', pretix.presale.views.event.EventLogin.as_view(), name='event.checkout.login'),
|
||||
)
|
||||
|
||||
@@ -52,22 +52,33 @@ class CartDisplayMixin:
|
||||
'item__questions', 'answers'
|
||||
))
|
||||
|
||||
def get_cart(self):
|
||||
cartpos = CartPosition.objects.current.filter(
|
||||
Q(user=self.request.user) & Q(event=self.request.event)
|
||||
).order_by(
|
||||
def get_cart(self, answers=False, queryset=None, payment_fee=None):
|
||||
if queryset is None:
|
||||
queryset = CartPosition.objects.current.filter(
|
||||
Q(user=self.request.user) & Q(event=self.request.event)
|
||||
)
|
||||
|
||||
prefetch = ['variation__values', 'variation__values__prop']
|
||||
if answers:
|
||||
prefetch.append('item__questions')
|
||||
prefetch.append('answers')
|
||||
|
||||
cartpos = queryset.order_by(
|
||||
'item', 'variation'
|
||||
).select_related(
|
||||
'item', 'variation'
|
||||
).prefetch_related(
|
||||
'variation__values', 'variation__values__prop'
|
||||
*prefetch
|
||||
)
|
||||
|
||||
# Group items of the same variation
|
||||
# We do this by list manipulations instead of a GROUP BY query, as
|
||||
# Django is unable to join related models in a .values() query
|
||||
def keyfunc(pos):
|
||||
return pos.item_id, pos.variation_id, pos.price
|
||||
if answers and ((pos.item.admission and self.request.event.settings.attendee_names_asked == 'True')
|
||||
or pos.item.questions.all()):
|
||||
return pos.id, "", "", ""
|
||||
return "", pos.item_id, pos.variation_id, pos.price
|
||||
|
||||
positions = []
|
||||
for k, g in groupby(sorted(list(cartpos), key=keyfunc), key=keyfunc):
|
||||
@@ -75,27 +86,42 @@ class CartDisplayMixin:
|
||||
group = g[0]
|
||||
group.count = len(g)
|
||||
group.total = group.count * group.price
|
||||
group.has_questions = answers and k[0] != ""
|
||||
if answers:
|
||||
group.answ = {}
|
||||
for a in group.answers.all():
|
||||
group.answ[a.question_id] = a.answer
|
||||
group.questions = []
|
||||
for q in group.item.questions.all():
|
||||
if q.identity in group.answ:
|
||||
q.answer = group.answ[q.identity]
|
||||
else:
|
||||
q.answer = ""
|
||||
group.questions.append(q)
|
||||
positions.append(group)
|
||||
|
||||
total = sum(p.total for p in positions)
|
||||
|
||||
payment_fee = 0
|
||||
if 'payment' in self.request.session:
|
||||
responses = register_payment_providers.send(self.request.event)
|
||||
for receiver, response in responses:
|
||||
provider = response(self.request.event)
|
||||
if provider.identifier == self.request.session['payment']:
|
||||
payment_fee = provider.calculate_fee(total)
|
||||
if payment_fee is None:
|
||||
payment_fee = 0
|
||||
if 'payment' in self.request.session:
|
||||
responses = register_payment_providers.send(self.request.event)
|
||||
for receiver, response in responses:
|
||||
provider = response(self.request.event)
|
||||
if provider.identifier == self.request.session['payment']:
|
||||
payment_fee = provider.calculate_fee(total)
|
||||
|
||||
try:
|
||||
minutes_left = max(min(p.expires for p in positions) - now(), timedelta()).seconds // 60 if positions else 0
|
||||
except AttributeError:
|
||||
minutes_left = None
|
||||
return {
|
||||
'positions': positions,
|
||||
'raw': cartpos,
|
||||
'total': total + payment_fee,
|
||||
'payment_fee': payment_fee,
|
||||
'minutes_left': (
|
||||
max(min(p.expires for p in positions) - now(), timedelta()).seconds // 60
|
||||
if positions else 0
|
||||
),
|
||||
'answers': answers,
|
||||
'minutes_left': minutes_left,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, Ch
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['cart'] = self.get_cart()
|
||||
ctx['cart'] = self.get_cart(answers=True)
|
||||
ctx['payment'] = self.payment_provider.checkout_confirm_render(self.request)
|
||||
return ctx
|
||||
|
||||
|
||||
90
src/pretix/presale/views/order.py
Normal file
90
src/pretix/presale/views/order.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from itertools import groupby
|
||||
from django.db.models import Q
|
||||
from django.utils.functional import cached_property
|
||||
from django.views.generic import TemplateView
|
||||
from django.http import HttpResponseNotFound
|
||||
from pretix.base.models import Order, OrderPosition
|
||||
from pretix.base.signals import register_payment_providers
|
||||
from pretix.presale.views import EventViewMixin, EventLoginRequiredMixin, CartDisplayMixin
|
||||
|
||||
|
||||
class OrderDetails(EventViewMixin, EventLoginRequiredMixin, CartDisplayMixin, TemplateView):
|
||||
template_name = "pretixpresale/event/order.html"
|
||||
|
||||
@cached_property
|
||||
def order(self):
|
||||
try:
|
||||
return Order.objects.current.get(
|
||||
user=self.request.user,
|
||||
event=self.request.event,
|
||||
code=self.kwargs['order'],
|
||||
)
|
||||
except Order.DoesNotExist:
|
||||
return None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
if not self.order:
|
||||
return HttpResponseNotFound
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def itemlist_cartlike(self):
|
||||
"""
|
||||
Returns the list of ordered items a format compatible to the
|
||||
CardDisplayMixin, so we can reuse template code
|
||||
"""
|
||||
cartpos = OrderPosition.objects.current.filter(
|
||||
order=self.order,
|
||||
).order_by(
|
||||
'item', 'variation'
|
||||
).select_related(
|
||||
'item', 'variation'
|
||||
).prefetch_related(
|
||||
'variation__values', 'variation__values__prop',
|
||||
'item__questions'
|
||||
)
|
||||
|
||||
# Group items of the same variation
|
||||
# We do this by list manipulations instead of a GROUP BY query, as
|
||||
# Django is unable to join related models in a .values() query
|
||||
def keyfunc(pos):
|
||||
if (pos.item.admission and self.request.event.settings.attendee_names_asked == 'True') \
|
||||
or pos.item.questions.all():
|
||||
return pos.id, "", "", ""
|
||||
return "", pos.item_id, pos.variation_id, pos.price
|
||||
|
||||
positions = []
|
||||
for k, g in groupby(sorted(list(cartpos), key=keyfunc), key=keyfunc):
|
||||
g = list(g)
|
||||
group = g[0]
|
||||
group.count = len(g)
|
||||
group.total = group.count * group.price
|
||||
positions.append(group)
|
||||
|
||||
return {
|
||||
'positions': positions,
|
||||
'raw': cartpos,
|
||||
'total': self.order.total,
|
||||
'payment_fee': self.order.payment_fee,
|
||||
}
|
||||
|
||||
@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_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['order'] = self.order
|
||||
ctx['cart'] = self.get_cart(
|
||||
answers=True,
|
||||
queryset=OrderPosition.objects.current.filter(order=self.order)
|
||||
)
|
||||
if self.order.status == Order.STATUS_PENDING:
|
||||
ctx['payment'] = self.payment_provider.order_pending_render(self.request, self.order)
|
||||
elif self.order.status == Order.STATUS_PAID:
|
||||
ctx['payment'] = self.payment_provider.order_paid_render(self.request, self.order)
|
||||
return ctx
|
||||
Reference in New Issue
Block a user