mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Adds messages both pre- and post-update explaining to the user that the invoice itself will not be automatically regenerated and that support interaction is required for this step. Might reduce user frustration.
416 lines
17 KiB
Python
416 lines
17 KiB
Python
from datetime import timedelta
|
|
|
|
from django.contrib import messages
|
|
from django.core.urlresolvers import reverse
|
|
from django.http import Http404
|
|
from django.shortcuts import redirect
|
|
from django.utils.functional import cached_property
|
|
from django.utils.timezone import now
|
|
from django.utils.translation import gettext, ugettext_lazy as _
|
|
from django.views.generic import TemplateView, View
|
|
|
|
from pretix.base.models import (
|
|
CachedFile, CachedTicket, Invoice, Order, OrderPosition,
|
|
)
|
|
from pretix.base.models.orders import InvoiceAddress
|
|
from pretix.base.services.invoices import (
|
|
generate_invoice, invoice_pdf, invoice_qualified,
|
|
)
|
|
from pretix.base.services.orders import OrderError, cancel_order
|
|
from pretix.base.services.tickets import generate
|
|
from pretix.base.signals import (
|
|
register_payment_providers, register_ticket_outputs,
|
|
)
|
|
from pretix.multidomain.urlreverse import eventreverse
|
|
from pretix.presale.forms.checkout import InvoiceAddressForm
|
|
from pretix.presale.views import CartMixin, EventViewMixin
|
|
from pretix.presale.views.async import AsyncAction
|
|
from pretix.presale.views.questions import QuestionsViewMixin
|
|
|
|
|
|
class OrderDetailMixin:
|
|
@cached_property
|
|
def order(self):
|
|
try:
|
|
return Order.objects.get(secret=self.kwargs['secret'],
|
|
event=self.request.event, code=self.kwargs['order'])
|
|
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 eventreverse(self.request.event, 'presale:event.order', kwargs={
|
|
'order': self.order.code,
|
|
'secret': self.order.secret
|
|
})
|
|
|
|
|
|
class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TemplateView):
|
|
template_name = "pretixpresale/event/order.html"
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
self.kwargs = kwargs
|
|
if not self.order:
|
|
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
@cached_property
|
|
def download_buttons(self):
|
|
buttons = []
|
|
responses = register_ticket_outputs.send(self.request.event)
|
|
for receiver, response in responses:
|
|
provider = response(self.request.event)
|
|
if not provider.is_enabled:
|
|
continue
|
|
buttons.append({
|
|
'icon': provider.download_button_icon or 'fa-download',
|
|
'text': provider.download_button_text or 'fa-download',
|
|
'identifier': provider.identifier,
|
|
})
|
|
return buttons
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
ctx['order'] = self.order
|
|
ctx['can_download'] = (
|
|
self.request.event.settings.ticket_download
|
|
and (
|
|
self.request.event.settings.ticket_download_date is None
|
|
or now() > self.request.event.settings.ticket_download_date
|
|
) and self.order.status == Order.STATUS_PAID
|
|
)
|
|
ctx['download_buttons'] = self.download_buttons
|
|
ctx['cart'] = self.get_cart(
|
|
answers=True,
|
|
queryset=OrderPosition.objects.filter(order=self.order),
|
|
payment_fee=self.order.payment_fee, payment_fee_tax_rate=self.order.payment_fee_tax_rate
|
|
)
|
|
ctx['invoices'] = list(self.order.invoices.all())
|
|
ctx['can_generate_invoice'] = invoice_qualified(self.order) and (
|
|
self.request.event.settings.invoice_generate == 'user'
|
|
)
|
|
|
|
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
|
|
and self.order._can_be_paid()
|
|
)
|
|
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, OrderDetailMixin, TemplateView):
|
|
template_name = "pretixpresale/event/order_pay.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)
|
|
or 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() + '?paid=yes')
|
|
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 eventreverse(self.request.event, 'presale:event.order.pay.confirm', kwargs={
|
|
'order': self.order.code,
|
|
'secret': self.order.secret
|
|
})
|
|
|
|
|
|
class OrderInvoiceCreate(EventViewMixin, OrderDetailMixin, View):
|
|
|
|
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.'))
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
if self.request.event.settings.get('invoice_generate') != 'user' or not invoice_qualified(self.order):
|
|
messages.error(self.request, _('You cannot generate an invoice for this order.'))
|
|
elif self.order.invoices.exists():
|
|
messages.error(self.request, _('An invoice for this order already exists.'))
|
|
else:
|
|
i = generate_invoice(self.order)
|
|
self.order.log_action('pretix.event.order.invoice.generated', data={
|
|
'invoice': i.pk
|
|
})
|
|
messages.success(self.request, _('The invoice has been generated.'))
|
|
return redirect(self.get_order_url())
|
|
|
|
|
|
class OrderPayDo(EventViewMixin, OrderDetailMixin, TemplateView):
|
|
template_name = "pretixpresale/event/order_pay_confirm.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 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):
|
|
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
|
|
|
|
def get_payment_url(self):
|
|
return eventreverse(self.request.event, 'presale:event.order.pay', kwargs={
|
|
'order': self.order.code,
|
|
'secret': self.order.secret
|
|
})
|
|
|
|
|
|
class OrderPayComplete(EventViewMixin, OrderDetailMixin, View):
|
|
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 (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)
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
resp = self.payment_provider.payment_perform(request, self.order)
|
|
return redirect(resp or self.get_order_url() + '?paid=yes')
|
|
|
|
def get_payment_url(self):
|
|
return eventreverse(self.request.event, 'presale:event.order.pay', kwargs={
|
|
'order': self.order.code,
|
|
'secret': self.order.secret
|
|
})
|
|
|
|
|
|
class OrderModify(EventViewMixin, OrderDetailMixin, QuestionsViewMixin, TemplateView):
|
|
template_name = "pretixpresale/event/order_modify.html"
|
|
|
|
@cached_property
|
|
def positions(self):
|
|
return list(self.order.positions.order_by(
|
|
'item', 'variation'
|
|
).select_related(
|
|
'item', 'variation'
|
|
).prefetch_related(
|
|
'variation', 'item__questions', 'answers'
|
|
))
|
|
|
|
@cached_property
|
|
def invoice_address(self):
|
|
try:
|
|
return self.order.invoice_address
|
|
except InvoiceAddress.DoesNotExist:
|
|
return InvoiceAddress(order=self.order)
|
|
|
|
@cached_property
|
|
def invoice_form(self):
|
|
return InvoiceAddressForm(data=self.request.POST if self.request.method == "POST" else None,
|
|
event=self.request.event,
|
|
instance=self.invoice_address)
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
failed = not self.save() or not self.invoice_form.is_valid()
|
|
if failed:
|
|
messages.error(self.request,
|
|
_("We had difficulties processing your input. Please review the errors below."))
|
|
return self.get(request, *args, **kwargs)
|
|
self.invoice_form.save()
|
|
self.order.log_action('pretix.event.order.modified')
|
|
if self.invoice_form.has_changed():
|
|
success_message = ('Your invoice address has been updated. Please contact us if you need us '
|
|
'to regenerate your invoice.')
|
|
messages.success(self.request, _(success_message))
|
|
return redirect(self.get_order_url())
|
|
|
|
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:
|
|
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
|
if not self.order.can_modify_answers:
|
|
messages.error(request, _('You cannot modify this order'))
|
|
return redirect(self.get_order_url())
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
ctx['order'] = self.order
|
|
ctx['forms'] = self.forms
|
|
ctx['invoice_form'] = self.invoice_form
|
|
return ctx
|
|
|
|
|
|
class OrderCancel(EventViewMixin, OrderDetailMixin, TemplateView):
|
|
template_name = "pretixpresale/event/order_cancel.html"
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.request = request
|
|
self.kwargs = kwargs
|
|
if not self.order:
|
|
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
|
if self.order.status != Order.STATUS_PENDING or not self.order.can_user_cancel:
|
|
messages.error(request, _('You cannot cancel this order.'))
|
|
return redirect(self.get_order_url())
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
ctx['order'] = self.order
|
|
return ctx
|
|
|
|
|
|
class OrderCancelDo(EventViewMixin, OrderDetailMixin, AsyncAction, View):
|
|
task = cancel_order
|
|
|
|
def get_success_url(self, value):
|
|
return self.get_order_url()
|
|
|
|
def get_error_url(self):
|
|
return self.get_order_url()
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
if not self.order:
|
|
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
|
if not self.order.can_user_cancel:
|
|
messages.error(request, _('You cannot cancel this order.'))
|
|
return redirect(self.get_order_url())
|
|
return self.do(self.order.pk)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
ctx['order'] = self.order
|
|
return ctx
|
|
|
|
def get_success_message(self, value):
|
|
return _('The order has been cancelled.')
|
|
|
|
def get_error_message(self, exception):
|
|
if isinstance(exception, dict) and exception['exc_type'] == 'OrderError':
|
|
return gettext(exception['exc_message'])
|
|
elif isinstance(exception, OrderError):
|
|
return str(exception)
|
|
return super().get_error_message(exception)
|
|
|
|
|
|
class OrderDownload(EventViewMixin, OrderDetailMixin, View):
|
|
@cached_property
|
|
def output(self):
|
|
responses = register_ticket_outputs.send(self.request.event)
|
|
for receiver, response in responses:
|
|
provider = response(self.request.event)
|
|
if provider.identifier == self.kwargs.get('output'):
|
|
return provider
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
if not self.output or not self.output.is_enabled:
|
|
messages.error(request, _('You requested an invalid ticket output type.'))
|
|
return redirect(self.get_order_url())
|
|
if not self.order:
|
|
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
|
if self.order.status != Order.STATUS_PAID:
|
|
messages.error(request, _('Order is not paid.'))
|
|
return redirect(self.get_order_url())
|
|
if (not self.request.event.settings.ticket_download
|
|
or (self.request.event.settings.ticket_download_date is not None
|
|
and now() < self.request.event.settings.ticket_download_date)):
|
|
messages.error(request, _('Ticket download is not (yet) enabled.'))
|
|
return redirect(self.get_order_url())
|
|
|
|
try:
|
|
ct = CachedTicket.objects.get(order=self.order, provider=self.output.identifier)
|
|
except CachedTicket.DoesNotExist:
|
|
ct = CachedTicket(order=self.order, provider=self.output.identifier)
|
|
try:
|
|
ct.cachedfile
|
|
except CachedFile.DoesNotExist:
|
|
cf = CachedFile()
|
|
cf.date = now()
|
|
cf.expires = self.request.event.date_from + timedelta(days=30)
|
|
cf.save()
|
|
ct.cachedfile = cf
|
|
ct.save()
|
|
generate(self.order.id, self.output.identifier)
|
|
return redirect(reverse('cachedfile.download', kwargs={'id': ct.cachedfile.id}))
|
|
|
|
|
|
class InvoiceDownload(EventViewMixin, OrderDetailMixin, View):
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
if not self.order:
|
|
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
|
|
|
try:
|
|
invoice = Invoice.objects.get(
|
|
event=self.request.event,
|
|
order=self.order,
|
|
id=self.kwargs['invoice']
|
|
)
|
|
except Invoice.DoesNotExist:
|
|
raise Http404(_('This invoice has not been found'))
|
|
|
|
if not invoice.file:
|
|
invoice_pdf(invoice.pk)
|
|
invoice = Invoice.objects.get(pk=invoice.pk)
|
|
|
|
if not invoice.file:
|
|
# This happens if we have celery installed and the file will be generated in the background
|
|
messages.warning(request, _('The invoice file has not yet been generated, we will generate it for you '
|
|
'now. Please try again in a few seconds.'))
|
|
return redirect(self.get_order_url())
|
|
|
|
return redirect(invoice.file.url)
|