forked from CGM_Public/pretix_original
Refator payment provider, deal with cancellations
This commit is contained in:
@@ -458,11 +458,15 @@ class Order(LockModel, LoggedModel):
|
||||
positions = list(
|
||||
self.positions.all().annotate(
|
||||
has_checkin=Exists(Checkin.objects.filter(position_id=OuterRef('pk')))
|
||||
).select_related('item')
|
||||
).select_related('item').prefetch_related('issued_gift_cards')
|
||||
)
|
||||
cancelable = all([op.item.allow_cancel and not op.has_checkin for op in positions])
|
||||
if not cancelable or not positions:
|
||||
return False
|
||||
for op in positions:
|
||||
for gc in op.issued_gift_cards.all():
|
||||
if gc.value != op.price:
|
||||
return False
|
||||
if self.user_cancel_deadline and now() > self.user_cancel_deadline:
|
||||
return False
|
||||
if self.status == Order.STATUS_PENDING:
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Any, Dict, Union
|
||||
import pytz
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import transaction
|
||||
from django.dispatch import receiver
|
||||
@@ -21,8 +22,8 @@ from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.forms import PlaceholderValidator
|
||||
from pretix.base.models import (
|
||||
CartPosition, Event, InvoiceAddress, Order, OrderPayment, OrderRefund,
|
||||
Quota,
|
||||
CartPosition, Event, GiftCard, InvoiceAddress, Order, OrderPayment,
|
||||
OrderRefund, Quota,
|
||||
)
|
||||
from pretix.base.reldate import RelativeDateField, RelativeDateWrapper
|
||||
from pretix.base.settings import SettingsSandbox
|
||||
@@ -30,6 +31,7 @@ from pretix.base.signals import register_payment_providers
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
from pretix.base.templatetags.rich_text import rich_text
|
||||
from pretix.helpers.money import DecimalTextInput
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
from pretix.presale.views import get_cart_total
|
||||
from pretix.presale.views.cart import cart_session, get_or_create_cart_id
|
||||
|
||||
@@ -890,12 +892,11 @@ class OffsettingProvider(BasePaymentProvider):
|
||||
|
||||
|
||||
class GiftCardPayment(BasePaymentProvider):
|
||||
is_enabled = True
|
||||
identifier = "giftcard"
|
||||
verbose_name = _("Gift card")
|
||||
|
||||
def is_allowed(self, request: HttpRequest, total: Decimal=None) -> bool:
|
||||
return False
|
||||
return super().is_allowed(request, total) and self.event.organizer.has_gift_cards
|
||||
|
||||
def order_change_allowed(self, order: Order) -> bool:
|
||||
return False
|
||||
@@ -903,6 +904,9 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
def execute_payment(self, request: HttpRequest, payment: OrderPayment) -> str:
|
||||
raise PaymentException("Invalid state, should never occur.")
|
||||
|
||||
def payment_form_render(self, request: HttpRequest, total: Decimal) -> str:
|
||||
return get_template('pretixcontrol/giftcards/checkout.html').render({})
|
||||
|
||||
def payment_control_render(self, request, payment) -> str:
|
||||
from .models import GiftCard
|
||||
|
||||
@@ -933,6 +937,42 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
def payment_refund_supported(self, payment: OrderPayment) -> bool:
|
||||
return True
|
||||
|
||||
def checkout_prepare(self, request: HttpRequest, cart: Dict[str, Any]) -> Union[bool, str, None]:
|
||||
cs = cart_session(request)
|
||||
try:
|
||||
gc = self.event.organizer.accepted_gift_cards.get(
|
||||
secret=request.POST.get("giftcard")
|
||||
)
|
||||
if gc.currency != self.event.currency:
|
||||
messages.error(request, _("This gift card does not support this currency."))
|
||||
return
|
||||
if gc.value <= Decimal("0.00"):
|
||||
messages.error(request, _("All credit on this gift card has been used."))
|
||||
return
|
||||
if 'gift_cards' not in cs:
|
||||
cs['gift_cards'] = []
|
||||
elif gc.pk in cs['gift_cards']:
|
||||
messages.error(request, _("This gift card is already used for your payment."))
|
||||
return
|
||||
cs['gift_cards'] = cs['gift_cards'] + [gc.pk]
|
||||
|
||||
remainder = cart['total'] - gc.value
|
||||
if remainder >= Decimal('0.00'):
|
||||
messages.success(request, _("Your gift card has been applied, but {} still need to be paid. Please select a payment method.").format(
|
||||
money_filter(remainder, self.event.currency)
|
||||
))
|
||||
else:
|
||||
messages.success(request, _("Your gift card has been applied."))
|
||||
|
||||
kwargs = {'step': 'payment'}
|
||||
if request.resolver_match and 'cart_namespace' in request.resolver_match.kwargs:
|
||||
kwargs['cart_namespace'] = request.resolver_match.kwargs['cart_namespace']
|
||||
return eventreverse(self.event, 'presale:event.checkout', kwargs=kwargs)
|
||||
except GiftCard.DoesNotExist:
|
||||
messages.error(request, _("This gift card is not known."))
|
||||
except GiftCard.MultipleObjectsReturned:
|
||||
messages.error(request, _("This gift card can not be redeemed since its code is not unique. Please contact the organizer of this event."))
|
||||
|
||||
@transaction.atomic()
|
||||
def execute_refund(self, refund: OrderRefund):
|
||||
from .models import GiftCard
|
||||
|
||||
@@ -329,6 +329,15 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
|
||||
if position.voucher:
|
||||
Voucher.objects.filter(pk=position.voucher.pk).update(redeemed=Greatest(0, F('redeemed') - 1))
|
||||
|
||||
for position in order.positions.all():
|
||||
for gc in position.issued_gift_cards.all():
|
||||
if gc.value < position.price:
|
||||
raise OrderError(_('This order can not be canceled since the gift card {card} purchased in this order has already been redeemed.').format(
|
||||
card=gc.secret
|
||||
))
|
||||
else:
|
||||
gc.transactions.create(value=-position.price, order=order)
|
||||
|
||||
order.log_action('pretix.event.order.canceled', user=user, auth=api_token or oauth_application or device,
|
||||
data={'cancellation_fee': cancellation_fee})
|
||||
|
||||
@@ -1659,9 +1668,16 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
|
||||
@receiver(order_paid, dispatch_uid="pretixbase_order_paid_giftcards")
|
||||
@transaction.atomic()
|
||||
def signal_listener_issue_giftcards(sender: Event, order: Order, **kwargs):
|
||||
any_giftcards = False
|
||||
for p in order.positions.all():
|
||||
if p.item.issue_giftcard:
|
||||
gc = sender.organizer.issued_gift_cards.create(
|
||||
currency=sender.currency, issued_in=p
|
||||
)
|
||||
gc.transactions.create(value=p.price, order=order)
|
||||
any_giftcards = True
|
||||
p.secret = gc.secret
|
||||
p.save(update_fields=['secret'])
|
||||
|
||||
if any_giftcards:
|
||||
tickets.invalidate_cache.apply_async(kwargs={'event': sender.pk, 'order': order.pk})
|
||||
|
||||
@@ -133,6 +133,10 @@ DEFAULTS = {
|
||||
'default': 'True',
|
||||
'type': bool
|
||||
},
|
||||
'payment_giftcard__enabled': {
|
||||
'default': 'True',
|
||||
'type': bool
|
||||
},
|
||||
'payment_term_accept_late': {
|
||||
'default': 'True',
|
||||
'type': bool
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{% load i18n %}
|
||||
|
||||
<p>
|
||||
{% blocktrans %}
|
||||
If you have a gift card, please enter the gift card code here. If the gift card does not have
|
||||
enough credit to pay for the full order, you will be shown this page again and you can either
|
||||
redeem another gift card or select a different payment method for the difference.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<input name="giftcard" class="form-control" placeholder="{% trans "Gift card code" %}">
|
||||
@@ -897,23 +897,26 @@ class OrderTransition(OrderView):
|
||||
else:
|
||||
messages.success(self.request, _('The payment has been created successfully.'))
|
||||
elif self.order.cancel_allowed() and to == 'c' and self.mark_canceled_form.is_valid():
|
||||
cancel_order(self.order, user=self.request.user,
|
||||
send_mail=self.mark_canceled_form.cleaned_data['send_email'],
|
||||
cancellation_fee=self.mark_canceled_form.cleaned_data.get('cancellation_fee'))
|
||||
self.order.refresh_from_db()
|
||||
try:
|
||||
cancel_order(self.order, user=self.request.user,
|
||||
send_mail=self.mark_canceled_form.cleaned_data['send_email'],
|
||||
cancellation_fee=self.mark_canceled_form.cleaned_data.get('cancellation_fee'))
|
||||
except OrderError as e:
|
||||
messages.error(self.request, str(e))
|
||||
else:
|
||||
self.order.refresh_from_db()
|
||||
if self.order.pending_sum < 0:
|
||||
messages.success(self.request, _('The order has been canceled. You can now select how you want to '
|
||||
'transfer the money back to the user.'))
|
||||
return redirect(reverse('control:event.order.refunds.start', kwargs={
|
||||
'event': self.request.event.slug,
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'code': self.order.code
|
||||
}) + '?start-action=do_nothing&start-mode=partial&start-partial_amount={}'.format(
|
||||
self.order.pending_sum * -1
|
||||
))
|
||||
|
||||
if self.order.pending_sum < 0:
|
||||
messages.success(self.request, _('The order has been canceled. You can now select how you want to '
|
||||
'transfer the money back to the user.'))
|
||||
return redirect(reverse('control:event.order.refunds.start', kwargs={
|
||||
'event': self.request.event.slug,
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'code': self.order.code
|
||||
}) + '?start-action=do_nothing&start-mode=partial&start-partial_amount={}'.format(
|
||||
self.order.pending_sum * -1
|
||||
))
|
||||
|
||||
messages.success(self.request, _('The order has been canceled.'))
|
||||
messages.success(self.request, _('The order has been canceled.'))
|
||||
elif self.order.status == Order.STATUS_PENDING and to == 'e':
|
||||
mark_order_expired(self.order, user=self.request.user)
|
||||
messages.success(self.request, _('The order has been marked as expired.'))
|
||||
|
||||
@@ -15,13 +15,12 @@ from django.utils.translation import (
|
||||
from django.views.generic.base import TemplateResponseMixin
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import GiftCard, Order
|
||||
from pretix.base.models import Order
|
||||
from pretix.base.models.orders import InvoiceAddress, OrderPayment
|
||||
from pretix.base.services.cart import (
|
||||
get_fees, set_cart_addons, update_tax_rates,
|
||||
)
|
||||
from pretix.base.services.orders import perform_order
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
from pretix.base.views.tasks import AsyncAction
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
from pretix.presale.forms.checkout import (
|
||||
@@ -531,40 +530,6 @@ class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
|
||||
def post(self, request):
|
||||
self.request = request
|
||||
if request.POST.get("giftcard") and request.POST.get("payment") == "giftcard":
|
||||
# TODO: cross-organizer acceptance, …
|
||||
try:
|
||||
gc = request.organizer.accepted_gift_cards.get(
|
||||
secret=request.POST.get("giftcard")
|
||||
)
|
||||
if gc.currency != request.event.currency:
|
||||
messages.error(self.request, _("This gift card does not support this currency."))
|
||||
return self.render()
|
||||
if gc.value <= Decimal("0.00"):
|
||||
messages.error(self.request, _("All credit on this gift card has been used."))
|
||||
return self.render()
|
||||
if 'gift_cards' not in self.cart_session:
|
||||
self.cart_session['gift_cards'] = []
|
||||
elif gc.pk in self.cart_session['gift_cards']:
|
||||
messages.error(self.request, _("This gift card is already used for your payment."))
|
||||
return self.render()
|
||||
self.cart_session['gift_cards'] = self.cart_session['gift_cards'] + [gc.pk]
|
||||
|
||||
remainder = self._total_order_value - gc.value
|
||||
if remainder >= Decimal('0.00'):
|
||||
messages.success(self.request, _("Your gift card has been applied, but {} still need to be paid. Please select a payment method.").format(
|
||||
money_filter(remainder, self.event.currency)
|
||||
))
|
||||
else:
|
||||
messages.success(self.request, _("Your gift card has been applied."))
|
||||
return redirect(self.get_step_url(request))
|
||||
except GiftCard.DoesNotExist:
|
||||
messages.error(self.request, _("This gift card is not known."))
|
||||
return self.render()
|
||||
except GiftCard.MultipleObjectsReturned:
|
||||
messages.error(self.request, _("This gift card can not be redeemed since its code is not unique. Please contact the organizer of this event."))
|
||||
return self.render()
|
||||
|
||||
for p in self.provider_forms:
|
||||
if p['provider'].identifier == request.POST.get('payment', ''):
|
||||
self.cart_session['payment'] = p['provider'].identifier
|
||||
@@ -589,7 +554,6 @@ class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
if len(self.provider_forms) == 1:
|
||||
ctx['selected'] = self.provider_forms[0]['provider'].identifier
|
||||
ctx['cart'] = self.get_cart()
|
||||
ctx['has_gift_cards'] = self.request.organizer.has_gift_cards
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
|
||||
@@ -51,33 +51,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if has_gift_cards %}
|
||||
<div class="panel panel-default">
|
||||
<label class="accordion-radio">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<input type="radio" name="payment" value="giftcard"
|
||||
data-parent="#payment_accordion"
|
||||
data-toggle="radiocollapse" data-target="#payment_giftcard"/>
|
||||
<strong>{% trans "Redeem a gift card" %}</strong>
|
||||
</h4>
|
||||
</div>
|
||||
</label>
|
||||
<div id="payment_giftcard"
|
||||
class="panel-collapse collapsed">
|
||||
<div class="panel-body form-horizontal">
|
||||
<p>
|
||||
{% blocktrans %}
|
||||
If you have a gift card, please enter the gift card code here. If the gift card does not have
|
||||
enough credit to pay for the full order, you will be shown this page again and you can either
|
||||
redeem another gift card or select a different payment method for the difference.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<input name="giftcard" class="form-control" placeholder="{% trans "Gift card code" %}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not providers %}
|
||||
<p><em>{% trans "There are no payment providers enabled." %}</em></p>
|
||||
{% if not event.live %}
|
||||
|
||||
Reference in New Issue
Block a user