forked from CGM_Public/pretix_original
Proof of concept
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
# Generated by Django 2.2.1 on 2019-09-10 20:20
|
# Generated by Django 2.2.1 on 2019-09-10 20:20
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
import pretix.base.models.fields
|
import pretix.base.models.fields
|
||||||
import pretix.base.models.giftcards
|
import pretix.base.models.giftcards
|
||||||
|
|
||||||
|
|||||||
@@ -888,6 +888,21 @@ class OffsettingProvider(BasePaymentProvider):
|
|||||||
return _('Balanced against orders: %s' % ', '.join(payment.info_data['orders']))
|
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")
|
@receiver(register_payment_providers, dispatch_uid="payment_free")
|
||||||
def register_payment_provider(sender, **kwargs):
|
def register_payment_provider(sender, **kwargs):
|
||||||
return [FreeOrderProvider, BoxOfficeProvider, OffsettingProvider, ManualPayment]
|
return [FreeOrderProvider, BoxOfficeProvider, OffsettingProvider, ManualPayment, GiftCardPayment]
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ from django_scopes import scopes_disabled
|
|||||||
|
|
||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CartPosition, Event, InvoiceAddress, Item, ItemBundle, ItemVariation, Seat,
|
CartPosition, Event, GiftCard, InvoiceAddress, Item, ItemBundle,
|
||||||
SeatCategoryMapping, Voucher,
|
ItemVariation, Seat, SeatCategoryMapping, Voucher,
|
||||||
)
|
)
|
||||||
from pretix.base.models.event import SubEvent
|
from pretix.base.models.event import SubEvent
|
||||||
from pretix.base.models.orders import OrderFee
|
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):
|
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,
|
for recv, resp in fee_calculation_for_cart.send(sender=event, request=request, invoice_address=invoice_address,
|
||||||
total=total):
|
total=total):
|
||||||
if resp:
|
if resp:
|
||||||
fees += resp
|
fees += resp
|
||||||
|
|
||||||
total = total + sum(f.value for f in fees)
|
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:
|
if provider and total != 0:
|
||||||
provider = event.get_payment_providers().get(provider)
|
provider = event.get_payment_providers().get(provider)
|
||||||
if provider:
|
if provider:
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ from pretix.api.models import OAuthApplication
|
|||||||
from pretix.base.email import get_email_context
|
from pretix.base.email import get_email_context
|
||||||
from pretix.base.i18n import LazyLocaleException, language
|
from pretix.base.i18n import LazyLocaleException, language
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CartPosition, Device, Event, Item, ItemVariation, Order, OrderPayment,
|
CartPosition, Device, Event, GiftCard, Item, ItemVariation, Order,
|
||||||
OrderPosition, Quota, Seat, SeatCategoryMapping, User, Voucher,
|
OrderPayment, OrderPosition, Quota, Seat, SeatCategoryMapping, User,
|
||||||
|
Voucher,
|
||||||
)
|
)
|
||||||
from pretix.base.models.event import SubEvent
|
from pretix.base.models.event import SubEvent
|
||||||
from pretix.base.models.items import ItemBundle
|
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,
|
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 = []
|
fees = []
|
||||||
total = sum([c.price for c in positions])
|
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):
|
meta_info=meta_info, positions=positions):
|
||||||
if resp:
|
if resp:
|
||||||
fees += resp
|
fees += resp
|
||||||
|
|
||||||
total += sum(f.value for f in fees)
|
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:
|
if payment_provider:
|
||||||
payment_fee = payment_provider.calculate_fee(total)
|
payment_fee = payment_provider.calculate_fee(total)
|
||||||
else:
|
else:
|
||||||
@@ -565,17 +576,24 @@ def _get_fees(positions: List[CartPosition], payment_provider: BasePaymentProvid
|
|||||||
internal_type=payment_provider.identifier)
|
internal_type=payment_provider.identifier)
|
||||||
fees.append(pf)
|
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,
|
def _create_order(event: Event, email: str, positions: List[CartPosition], now_dt: datetime,
|
||||||
payment_provider: BasePaymentProvider, locale: str=None, address: InvoiceAddress=None,
|
payment_provider: BasePaymentProvider, locale: str=None, address: InvoiceAddress=None,
|
||||||
meta_info: dict=None, sales_channel: str='web'):
|
meta_info: dict=None, sales_channel: str='web', gift_cards: list=None):
|
||||||
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])
|
|
||||||
p = None
|
p = None
|
||||||
|
|
||||||
with transaction.atomic():
|
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(
|
order = Order(
|
||||||
status=Order.STATUS_PENDING,
|
status=Order.STATUS_PENDING,
|
||||||
event=event,
|
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.tax_rule = None # TODO: deprecate
|
||||||
fee.save()
|
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:
|
if payment_provider and not order.require_approval:
|
||||||
p = order.payments.create(
|
p = order.payments.create(
|
||||||
state=OrderPayment.PAYMENT_STATE_CREATED,
|
state=OrderPayment.PAYMENT_STATE_CREATED,
|
||||||
provider=payment_provider.identifier,
|
provider=payment_provider.identifier,
|
||||||
amount=total,
|
amount=pending_sum,
|
||||||
fee=pf
|
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],
|
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:
|
if payment_provider:
|
||||||
pprov = event.get_payment_providers().get(payment_provider)
|
pprov = event.get_payment_providers().get(payment_provider)
|
||||||
if not pprov:
|
if not pprov:
|
||||||
@@ -707,9 +745,10 @@ def _perform_order(event: Event, payment_provider: str, position_ids: List[str],
|
|||||||
raise OrderError(error_messages['internal'])
|
raise OrderError(error_messages['internal'])
|
||||||
_check_positions(event, now_dt, positions, address=addr)
|
_check_positions(event, now_dt, positions, address=addr)
|
||||||
order, payment = _create_order(event, email, positions, now_dt, pprov,
|
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:
|
if free_order_flow:
|
||||||
try:
|
try:
|
||||||
payment.confirm(send_mail=False, lock=not locked)
|
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,))
|
@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],
|
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,
|
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):
|
with language(locale):
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
return _perform_order(event, payment_provider, positions, email, locale, address, meta_info,
|
return _perform_order(event, payment_provider, positions, email, locale, address, meta_info,
|
||||||
sales_channel)
|
sales_channel, gift_cards)
|
||||||
except LockTimeoutException:
|
except LockTimeoutException:
|
||||||
self.retry()
|
self.retry()
|
||||||
except (MaxRetriesExceededError, LockTimeoutException):
|
except (MaxRetriesExceededError, LockTimeoutException):
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from django.utils.translation import (
|
|||||||
from django.views.generic.base import TemplateResponseMixin
|
from django.views.generic.base import TemplateResponseMixin
|
||||||
from django_scopes import scopes_disabled
|
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.models.orders import InvoiceAddress, OrderPayment
|
||||||
from pretix.base.services.cart import (
|
from pretix.base.services.cart import (
|
||||||
get_fees, set_cart_addons, update_tax_rates,
|
get_fees, set_cart_addons, update_tax_rates,
|
||||||
@@ -530,6 +530,24 @@ class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
|||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
self.request = 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:
|
for p in self.provider_forms:
|
||||||
if p['provider'].identifier == request.POST.get('payment', ''):
|
if p['provider'].identifier == request.POST.get('payment', ''):
|
||||||
self.cart_session['payment'] = p['provider'].identifier
|
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,
|
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'),
|
[p.id for p in self.positions], self.cart_session.get('email'),
|
||||||
translation.get_language(), self.invoice_address.pk, meta_info,
|
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):
|
def get_success_message(self, value):
|
||||||
create_empty_cart_id(self.request)
|
create_empty_cart_id(self.request)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="panel-group" id="payment_accordion">
|
<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 %}
|
{% for p in providers %}
|
||||||
<div class="panel panel-default" data-total="{{ p.total|floatformat:2 }}">
|
<div class="panel panel-default" data-total="{{ p.total|floatformat:2 }}">
|
||||||
<label class="accordion-radio">
|
<label class="accordion-radio">
|
||||||
|
|||||||
Reference in New Issue
Block a user