Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix sales channels issue #24

Merged
merged 4 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ recursive-include pretix_wallet/static *
recursive-include pretix_wallet/templates *
recursive-include pretix_wallet/locale *
include LICENSE
include *.md
exclude .gitlab-ci.yml
12 changes: 8 additions & 4 deletions pretix_wallet/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ class TerminalAuthentication(TokenAuthentication):

def authenticate(self, request):
self.request = request
request.event = Event.objects.filter(
slug=request.resolver_match.kwargs['event'],
organizer__slug=request.resolver_match.kwargs['organizer'],
).select_related('organizer').first()
request.event = (
Event.objects.filter(
slug=request.resolver_match.kwargs["event"],
organizer__slug=request.resolver_match.kwargs["organizer"],
)
.select_related("organizer")
.first()
)
if not request.event:
return False
return super().authenticate(request)
Expand Down
31 changes: 25 additions & 6 deletions pretix_wallet/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,43 @@
# Generated by Django 4.2.10 on 2024-02-24 18:00

from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
('pretixbase', '0256_itemvariation_unavail_modes'),
("pretixbase", "0256_itemvariation_unavail_modes"),
]

operations = [
migrations.CreateModel(
name='CustomerWallet',
name="CustomerWallet",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('customer', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='wallet', to='pretixbase.customer')),
('giftcard', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='wallet', to='pretixbase.giftcard')),
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False
),
),
(
"customer",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="wallet",
to="pretixbase.customer",
),
),
(
"giftcard",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="wallet",
to="pretixbase.giftcard",
),
),
],
),
]
8 changes: 6 additions & 2 deletions pretix_wallet/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@


class CustomerWallet(models.Model):
customer = models.OneToOneField(Customer, on_delete=models.CASCADE, related_name='wallet')
giftcard = models.OneToOneField(GiftCard, on_delete=models.CASCADE, related_name='wallet')
customer = models.OneToOneField(
Customer, on_delete=models.CASCADE, related_name="wallet"
)
giftcard = models.OneToOneField(
GiftCard, on_delete=models.CASCADE, related_name="wallet"
)
38 changes: 21 additions & 17 deletions pretix_wallet/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,29 @@

class CustomPagination(pagination.PageNumberPagination):
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'data': data
})
return Response(
{
"links": {
"next": self.get_next_link(),
"previous": self.get_previous_link(),
},
"count": self.page.paginator.count,
"data": data,
}
)


class ProductPagination(pagination.PageNumberPagination):
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'data': {
"products": data,
return Response(
{
"links": {
"next": self.get_next_link(),
"previous": self.get_previous_link(),
},
"count": self.page.paginator.count,
"data": {
"products": data,
},
}
})
)
134 changes: 94 additions & 40 deletions pretix_wallet/payment.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
from typing import Any, Dict, Union

from _decimal import Decimal
from collections import OrderedDict
from typing import Dict, Any, Union

from django.contrib import messages
from django.db import transaction
from django.forms import CharField
from django.http import HttpRequest
from django.utils.crypto import get_random_string
from django.utils.translation import gettext_lazy as _
from pretix.base.customersso.oidc import oidc_authorize_url
from pretix.base.models import Order, OrderPayment, GiftCard
from pretix.base.models import GiftCard, Order, OrderPayment
from pretix.base.models.customers import CustomerSSOProvider
from pretix.base.payment import PaymentException, GiftCardPayment
from pretix.base.payment import GiftCardPayment, PaymentException
from pretix.base.services.cart import add_payment_to_cart
from pretix.helpers import OF_SELF
from pretix.multidomain.urlreverse import build_absolute_uri
Expand All @@ -25,19 +25,35 @@ class WalletPaymentProvider(GiftCardPayment):
verbose_name = _("Wallet")
public_name = _("Wallet")

def payment_form_render(self, request: HttpRequest, total: Decimal, order: Order=None) -> str:
return _("Pay with balance on your wallet. Please note that this is only possible if you already topped up your wallet and the balance is not negative.")

def checkout_confirm_render(self, request, order: Order=None, info_data: dict=None) -> str:
return _("The payment amount will be deducted from your wallet after you confirm the order.")

def checkout_prepare(self, request: HttpRequest, cart: Dict[str, Any]) -> Union[bool, str]:
def payment_form_render(
self, request: HttpRequest, total: Decimal, order: Order = None
) -> str:
return _(
"Pay with balance on your wallet. Please note that this is only possible "
"if you already topped up your wallet and the balance is not negative."
)

def checkout_confirm_render(
self, request, order: Order = None, info_data: dict = None
) -> str:
return _(
"The payment amount will be deducted from your wallet after you confirm the order."
)

def checkout_prepare(
self, request: HttpRequest, cart: Dict[str, Any]
) -> Union[bool, str]:
if request.customer:
if not request.customer.wallet:
messages.error(request, _("You do not have a wallet."))
return False
if request.customer.wallet.giftcard.value < 0:
messages.error(request, _("Your wallet has a negative balance. Please top it up or use another payment method."))
messages.error(
request,
_(
"Your wallet has a negative balance. Please top it up or use another payment method."
),
)
return False
cart_session(request)
add_payment_to_cart(
Expand All @@ -47,36 +63,46 @@ def checkout_prepare(self, request: HttpRequest, cart: Dict[str, Any]) -> Union[
info_data=self._get_payment_info_data(request.customer.wallet),
)
return True
return self._redirect_user(request, build_absolute_uri(request.event, "presale:event.checkout", kwargs={"step": "payment"}))
return self._redirect_user(
request,
build_absolute_uri(
request.event, "presale:event.checkout", kwargs={"step": "payment"}
),
)

def _redirect_user(self, request: HttpRequest, next_url: str):
provider = CustomerSSOProvider.objects.last()

# taken from pretix.presale.views.customer.SSOLoginView as it does not allow for a custom next_url
nonce = get_random_string(32)
request.session[f'pretix_customerauth_{provider.pk}_nonce'] = nonce
request.session[f'pretix_customerauth_{provider.pk}_popup_origin'] = None
request.session[f'pretix_customerauth_{provider.pk}_cross_domain_requested'] = request.GET.get(
"request_cross_domain_customer_auth") == "true"
redirect_uri = build_absolute_uri(request.organizer, 'presale:organizer.customer.login.return', kwargs={
'provider': provider.pk
})

return oidc_authorize_url(provider, f'{nonce}%{next_url}', redirect_uri)
request.session[f"pretix_customerauth_{provider.pk}_nonce"] = nonce
request.session[f"pretix_customerauth_{provider.pk}_popup_origin"] = None
request.session[f"pretix_customerauth_{provider.pk}_cross_domain_requested"] = (
request.GET.get("request_cross_domain_customer_auth") == "true"
)
redirect_uri = build_absolute_uri(
request.organizer,
"presale:organizer.customer.login.return",
kwargs={"provider": provider.pk},
)

return oidc_authorize_url(provider, f"{nonce}%{next_url}", redirect_uri)

def _get_payment_info_data(self, wallet: CustomerWallet):
return {
'gift_card': wallet.giftcard.pk,
'gift_card_secret': wallet.giftcard.secret,
'user': wallet.customer.name_cached,
'user_id': wallet.customer.external_identifier,
'retry': True
"gift_card": wallet.giftcard.pk,
"gift_card_secret": wallet.giftcard.secret,
"user": wallet.customer.name_cached,
"user_id": wallet.customer.external_identifier,
"retry": True,
}

def execute_payment(self, request: HttpRequest, payment: OrderPayment, is_early_special_case=False) -> str:
def execute_payment(
self, request: HttpRequest, payment: OrderPayment, is_early_special_case=False
) -> str:
# re-implemented as the original method does not allow for giftcards to have negative balance

gcpk = payment.info_data.get('gift_card')
gcpk = payment.info_data.get("gift_card")
if not gcpk:
raise PaymentException("Invalid state, should never occur.")
try:
Expand All @@ -86,33 +112,49 @@ def execute_payment(self, request: HttpRequest, payment: OrderPayment, is_early_
except GiftCard.DoesNotExist:
raise PaymentException("Invalid state, should never occur.")
if gc.currency != self.event.currency: # noqa - just a safeguard
raise PaymentException(_("This gift card does not support this currency."))
raise PaymentException(
_("This gift card does not support this currency.")
)
if not gc.accepted_by(self.event.organizer):
raise PaymentException(_("This gift card is not accepted by this event organizer."))
raise PaymentException(
_("This gift card is not accepted by this event organizer.")
)

trans = gc.transactions.create(
value=-1 * payment.amount,
order=payment.order,
payment=payment,
acceptor=self.event.organizer,
)
payment.info_data['transaction_id'] = trans.pk
payment.confirm(send_mail=not is_early_special_case, generate_invoice=not is_early_special_case)
payment.info_data["transaction_id"] = trans.pk
payment.confirm(
send_mail=not is_early_special_case,
generate_invoice=not is_early_special_case,
)
except PaymentException as e:
payment.fail(info={'error': str(e)})
payment.fail(info={"error": str(e)})
raise e

def payment_prepare(self, request: HttpRequest, payment: OrderPayment) -> Union[bool, str, None]:
def payment_prepare(
self, request: HttpRequest, payment: OrderPayment
) -> Union[bool, str, None]:
if request.customer:
if not request.customer.wallet:
messages.error(request, _("You do not have a wallet."))
return False
if request.customer.wallet.giftcard.value < 0:
messages.error(request, _("Your wallet has a negative balance. Please top it up or use another payment method."))
messages.error(
request,
_(
"Your wallet has a negative balance. Please top it up or use another payment method."
),
)
return False
gc = request.customer.wallet.giftcard
if gc not in self.event.organizer.accepted_gift_cards:
raise PaymentException(_("Wallet payments cannot be accepted for this event."))
raise PaymentException(
_("Wallet payments cannot be accepted for this event.")
)
payment.amount = min(payment.amount, max(gc.value, 0))
payment.info_data = self._get_payment_info_data(request.customer.wallet)
payment.save()
Expand All @@ -121,9 +163,21 @@ def payment_prepare(self, request: HttpRequest, payment: OrderPayment) -> Union[

@property
def settings_form_fields(self):
return OrderedDict(list(super().settings_form_fields.items()) + [
('api_key', CharField(label=_("API key"), help_text=_("The API key that the terminal uses to authenticate against the POS api provided by this plugin."))),
])
return OrderedDict(
list(super().settings_form_fields.items())
+ [
(
"api_key",
CharField(
label=_("API key"),
help_text=_(
"The API key that the terminal uses to authenticate against "
"the POS api provided by this plugin."
),
),
),
]
)

@property
def test_mode_message(self) -> str:
Expand Down
Loading
Loading