Display payment details

This commit is contained in:
Raphael Michel
2015-03-12 22:54:59 +01:00
parent 3e392f7ed4
commit 8c802e534e
11 changed files with 306 additions and 51 deletions

View File

@@ -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(

View File

@@ -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

View File

@@ -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)

View File

@@ -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>

View File

@@ -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;

View File

@@ -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">

View 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 %}

View File

@@ -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'),
)

View File

@@ -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,
}

View File

@@ -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

View 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