Proof of concept

This commit is contained in:
Raphael Michel
2019-09-10 23:01:23 +02:00
parent f7f00fe735
commit ed370fa913
6 changed files with 119 additions and 22 deletions

View File

@@ -1,7 +1,8 @@
# Generated by Django 2.2.1 on 2019-09-10 20:20
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
import pretix.base.models.fields
import pretix.base.models.giftcards

View File

@@ -888,6 +888,21 @@ class OffsettingProvider(BasePaymentProvider):
return _('Balanced against orders: %s' % ', '.join(payment.info_data['orders']))
class GiftCardPayment(BasePaymentProvider):
is_enabled = True
identifier = "giftcard"
verbose_name = _("Gift card")
is_implicit = True
def is_allowed(self, request: HttpRequest, total: Decimal=None) -> bool:
return False
def order_change_allowed(self, order: Order) -> bool:
return False
# TODO: execute, refund, api, control render
@receiver(register_payment_providers, dispatch_uid="payment_free")
def register_payment_provider(sender, **kwargs):
return [FreeOrderProvider, BoxOfficeProvider, OffsettingProvider, ManualPayment]
return [FreeOrderProvider, BoxOfficeProvider, OffsettingProvider, ManualPayment, GiftCardPayment]

View File

@@ -14,8 +14,8 @@ from django_scopes import scopes_disabled
from pretix.base.i18n import language
from pretix.base.models import (
CartPosition, Event, InvoiceAddress, Item, ItemBundle, ItemVariation, Seat,
SeatCategoryMapping, Voucher,
CartPosition, Event, GiftCard, InvoiceAddress, Item, ItemBundle,
ItemVariation, Seat, SeatCategoryMapping, Voucher,
)
from pretix.base.models.event import SubEvent
from pretix.base.models.orders import OrderFee
@@ -958,14 +958,36 @@ def update_tax_rates(event: Event, cart_id: str, invoice_address: InvoiceAddress
def get_fees(event, request, total, invoice_address, provider):
fees = []
from pretix.presale.views.cart import cart_session
fees = []
for recv, resp in fee_calculation_for_cart.send(sender=event, request=request, invoice_address=invoice_address,
total=total):
if resp:
fees += resp
total = total + sum(f.value for f in fees)
cs = cart_session(request)
if cs.get('gift_cards'):
gc_qs = GiftCard.objects.filter(pk__in=cs.get('gift_cards'))
summed = 0
for gc in gc_qs:
fval = Decimal(gc.value) # TODO: don't require an extra query
fval = min(fval, total - summed)
if fval > 0:
total -= fval
summed += fval
fees.append(OrderFee(
fee_type=OrderFee.FEE_TYPE_GIFTCARD,
internal_type='giftcard',
description=gc.secret,
value=-1 * fval,
tax_rate=Decimal('0.00'),
tax_value=Decimal('0.00'),
tax_rule=TaxRule.zero()
))
if provider and total != 0:
provider = event.get_payment_providers().get(provider)
if provider:

View File

@@ -20,8 +20,9 @@ from pretix.api.models import OAuthApplication
from pretix.base.email import get_email_context
from pretix.base.i18n import LazyLocaleException, language
from pretix.base.models import (
CartPosition, Device, Event, Item, ItemVariation, Order, OrderPayment,
OrderPosition, Quota, Seat, SeatCategoryMapping, User, Voucher,
CartPosition, Device, Event, GiftCard, Item, ItemVariation, Order,
OrderPayment, OrderPosition, Quota, Seat, SeatCategoryMapping, User,
Voucher,
)
from pretix.base.models.event import SubEvent
from pretix.base.models.items import ItemBundle
@@ -545,7 +546,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
def _get_fees(positions: List[CartPosition], payment_provider: BasePaymentProvider, address: InvoiceAddress,
meta_info: dict, event: Event):
meta_info: dict, event: Event, gift_cards: List[GiftCard]):
fees = []
total = sum([c.price for c in positions])
@@ -553,8 +554,18 @@ def _get_fees(positions: List[CartPosition], payment_provider: BasePaymentProvid
meta_info=meta_info, positions=positions):
if resp:
fees += resp
total += sum(f.value for f in fees)
summed = 0
gift_card_values = {}
for gc in gift_cards:
fval = Decimal(gc.value) # TODO: don't require an extra query
fval = min(fval, total - summed)
if fval > 0:
total -= fval
summed += fval
gift_card_values[gc] = fval
if payment_provider:
payment_fee = payment_provider.calculate_fee(total)
else:
@@ -565,17 +576,24 @@ def _get_fees(positions: List[CartPosition], payment_provider: BasePaymentProvid
internal_type=payment_provider.identifier)
fees.append(pf)
return fees, pf
return fees, pf, gift_card_values
def _create_order(event: Event, email: str, positions: List[CartPosition], now_dt: datetime,
payment_provider: BasePaymentProvider, locale: str=None, address: InvoiceAddress=None,
meta_info: dict=None, sales_channel: str='web'):
fees, pf = _get_fees(positions, payment_provider, address, meta_info, event)
total = sum([c.price for c in positions]) + sum([c.value for c in fees])
meta_info: dict=None, sales_channel: str='web', gift_cards: list=None):
p = None
with transaction.atomic():
checked_gift_cards = []
if gift_cards:
gc_qs = GiftCard.objects.select_for_update().filter(pk__in=gift_cards) # TODO: Make sure to prevent race conditions
for gc in gc_qs:
# TODO: Re-check acceptance
checked_gift_cards.append(gc)
fees, pf, gift_card_values = _get_fees(positions, payment_provider, address, meta_info, event, checked_gift_cards)
total = pending_sum = sum([c.price for c in positions]) + sum([c.value for c in fees])
order = Order(
status=Order.STATUS_PENDING,
event=event,
@@ -606,11 +624,30 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
fee.tax_rule = None # TODO: deprecate
fee.save()
for gc, val in gift_card_values.items():
p = order.payments.create(
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
provider='giftcard',
amount=val,
fee=pf
)
trans = gc.transactions.create(
value=-1 * val,
order=order,
payment=p
)
p.info_data = {
'gift_card': gc.pk,
'transaction_id': trans.pk,
}
p.save()
pending_sum -= val
if payment_provider and not order.require_approval:
p = order.payments.create(
state=OrderPayment.PAYMENT_STATE_CREATED,
provider=payment_provider.identifier,
amount=total,
amount=pending_sum,
fee=pf
)
@@ -658,7 +695,8 @@ def _order_placed_email_attendee(event: Event, order: Order, position: OrderPosi
def _perform_order(event: Event, payment_provider: str, position_ids: List[str],
email: str, locale: str, address: int, meta_info: dict=None, sales_channel: str='web'):
email: str, locale: str, address: int, meta_info: dict=None, sales_channel: str='web',
gift_cards: list=None):
if payment_provider:
pprov = event.get_payment_providers().get(payment_provider)
if not pprov:
@@ -707,9 +745,10 @@ def _perform_order(event: Event, payment_provider: str, position_ids: List[str],
raise OrderError(error_messages['internal'])
_check_positions(event, now_dt, positions, address=addr)
order, payment = _create_order(event, email, positions, now_dt, pprov,
locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel)
locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel,
gift_cards=gift_cards)
free_order_flow = payment and payment_provider == 'free' and order.total == Decimal('0.00') and not order.require_approval
free_order_flow = payment and payment_provider == 'free' and order.pending_sum == Decimal('0.00') and not order.require_approval
if free_order_flow:
try:
payment.confirm(send_mail=False, lock=not locked)
@@ -1466,12 +1505,12 @@ class OrderChangeManager:
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
def perform_order(self, event: Event, payment_provider: str, positions: List[str],
email: str=None, locale: str=None, address: int=None, meta_info: dict=None,
sales_channel: str='web'):
sales_channel: str='web', gift_cards: list=None):
with language(locale):
try:
try:
return _perform_order(event, payment_provider, positions, email, locale, address, meta_info,
sales_channel)
sales_channel, gift_cards)
except LockTimeoutException:
self.retry()
except (MaxRetriesExceededError, LockTimeoutException):

View File

@@ -15,7 +15,7 @@ from django.utils.translation import (
from django.views.generic.base import TemplateResponseMixin
from django_scopes import scopes_disabled
from pretix.base.models import Order
from pretix.base.models import GiftCard, Order
from pretix.base.models.orders import InvoiceAddress, OrderPayment
from pretix.base.services.cart import (
get_fees, set_cart_addons, update_tax_rates,
@@ -530,6 +530,24 @@ class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
def post(self, request):
self.request = request
if request.POST.get("giftcard"):
# TODO: cross-organizer acceptance, check for valid money, …
try:
gc = GiftCard.objects.get(
issuer=request.organizer,
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 'gift_cards' not in self.cart_session:
self.cart_session['gift_cards'] = []
self.cart_session['gift_cards'] = self.cart_session['gift_cards'] + [gc.pk]
return self.render()
except GiftCard.DoesNotExist:
messages.error(self.request, _("This gift card is not known."))
return self.render()
for p in self.provider_forms:
if p['provider'].identifier == request.POST.get('payment', ''):
self.cart_session['payment'] = p['provider'].identifier
@@ -709,7 +727,7 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
return self.do(self.request.event.id, self.payment_provider.identifier if self.payment_provider else None,
[p.id for p in self.positions], self.cart_session.get('email'),
translation.get_language(), self.invoice_address.pk, meta_info,
request.sales_channel)
request.sales_channel, self.cart_session.get('gift_cards'))
def get_success_message(self, value):
create_empty_cart_id(self.request)

View File

@@ -11,6 +11,8 @@
<form method="post">
{% csrf_token %}
<div class="panel-group" id="payment_accordion">
{# TODO: make this proper #}
<input type="text" class="form-control" placeholder="Add gift card" name="giftcard">
{% for p in providers %}
<div class="panel panel-default" data-total="{{ p.total|floatformat:2 }}">
<label class="accordion-radio">