Compare commits

...
4 Commits
20 changed files with 255 additions and 101 deletions

No files matched your search

+1 -1
View File
@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
__version__ = "2026.8.0"
__version__ = "2026.8.1"
+5 -5
View File
@@ -19,12 +19,12 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
def get_session_key_for_api_auth(user, auth):
def get_session_key_for_api_auth(user, auth, prefix='api-upload'):
if user.is_authenticated:
return f'api-upload-User-{user.pk}'
return f'{prefix}-User-{user.pk}'
else:
return f'api-upload-{str(type(auth))}-{auth.pk}'
return f'{prefix}-{str(type(auth))}-{auth.pk}'
def get_session_key_for_api_request(request):
return get_session_key_for_api_auth(request.user, request.auth)
def get_session_key_for_api_request(request, prefix='api-upload'):
return get_session_key_for_api_auth(request.user, request.auth, prefix)
+17 -3
View File
@@ -1240,6 +1240,9 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
return None
if pp not in self.context['event'].get_payment_providers():
raise ValidationError('The given payment provider is not known.')
if pp == "giftcard":
raise serializers.ValidationError("Cannot create giftcard payment. Use `use_gift_cards` instead")
return pp
def validate_payment_info(self, info):
@@ -1960,10 +1963,15 @@ class OrderPaymentCreateSerializer(I18nAwareModelSerializer):
model = OrderPayment
fields = ('state', 'amount', 'payment_date', 'provider', 'info')
def validate_provider(self, value):
if value == "giftcard":
raise serializers.ValidationError("Cannot create giftcard payment")
return value
def create(self, validated_data):
order = OrderPayment(order=self.context['order'], **validated_data)
order.save()
return order
payment = OrderPayment(order=self.context['order'], **validated_data)
payment.save()
return payment
class OrderRefundCreateSerializer(I18nAwareModelSerializer):
@@ -1975,6 +1983,12 @@ class OrderRefundCreateSerializer(I18nAwareModelSerializer):
model = OrderRefund
fields = ('state', 'source', 'amount', 'payment', 'execution_date', 'provider', 'info', 'comment')
def validate_provider(self, value):
if value == "giftcard":
raise serializers.ValidationError("Cannot create giftcard payment")
return value
def create(self, validated_data):
pid = validated_data.pop('payment', None)
if pid:
+4 -13
View File
@@ -41,10 +41,9 @@ from pretix.api.serializers.settings import SettingsSerializer
from pretix.base.auth import get_auth_backends
from pretix.base.i18n import get_language_without_region
from pretix.base.models import (
Customer, Device, EventMetaProperty, GiftCard, GiftCardAcceptance,
GiftCardTransaction, Membership, MembershipType, OrderPosition, Organizer,
ReusableMedium, SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite,
User,
Customer, Device, EventMetaProperty, GiftCard, GiftCardTransaction,
Membership, MembershipType, OrderPosition, Organizer, ReusableMedium,
SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite, User,
)
from pretix.base.models.seating import SeatingPlanLayoutValidator
from pretix.base.permissions import (
@@ -270,15 +269,7 @@ class GiftCardSerializer(I18nAwareModelSerializer):
data = super().validate(data)
if 'secret' in data:
s = data['secret']
qs = GiftCard.objects.filter(
secret=s
).filter(
Q(issuer=self.context["organizer"]) |
Q(issuer__in=GiftCardAcceptance.objects.filter(
acceptor=self.context["organizer"],
active=True,
).values_list('issuer', flat=True))
)
qs = self.context["organizer"].accepted_gift_cards.filter(secret=s)
if self.instance:
qs = qs.exclude(pk=self.instance.pk)
if qs.exists():
+1 -1
View File
@@ -75,7 +75,7 @@ class ExportersMixin:
@action(detail=True, methods=['GET'], url_name='download', url_path='download/(?P<asyncid>[^/]+)/(?P<cfid>[^/]+)')
def download(self, *args, **kwargs):
cf = get_object_or_404(CachedFile, id=kwargs['cfid'])
cf = get_object_or_404(CachedFile, id=kwargs['cfid'], web_download=True)
if not cf.allowed_for_session(self.request, "exporters-api"):
return Response(
{'status': 'failed', 'message': 'Unknown file ID or export failed'},
+2 -2
View File
@@ -39,7 +39,7 @@ from pretix.api.serializers.media import (
)
from pretix.base.media import MEDIA_TYPES
from pretix.base.models import (
Checkin, GiftCard, GiftCardAcceptance, GiftCardTransaction, OrderPosition,
Checkin, GiftCardAcceptance, GiftCardTransaction, OrderPosition,
ReusableMedium,
)
from pretix.base.models.orders import PrintLog
@@ -88,7 +88,7 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
),
Prefetch(
'linked_giftcard',
queryset=GiftCard.objects.annotate(
queryset=self.request.organizer.accepted_gift_cards.annotate(
cached_value=Coalesce(Subquery(s), Decimal('0.00'))
)
)
+25 -6
View File
@@ -33,6 +33,7 @@ from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
from rest_framework.reverse import reverse
from pretix.api.auth.utils import get_session_key_for_api_request
from pretix.api.serializers.shredders import (
JobRunSerializer, ShredderSerializer,
)
@@ -68,8 +69,14 @@ class ShreddersMixin:
cf = get_object_or_404(
CachedFile,
id=kwargs['cfid'],
session_key=f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}'
)
if cf.session_key == "api-shredder-<class 'django.contrib.auth.models.AnonymousUser'>-None":
# OK, backwards-compatibility of a security bug fixed 2026-10, delete this at some point, but should
# also be harmless because all files with this key are expired one day after deployment of this fix
# and no new files with this key are created
pass
elif cf.session_key != get_session_key_for_api_request(self.request, prefix='api-shredder'):
raise Http404()
if cf.file:
resp = ChunkBasedFileResponse(cf.file.file, content_type=cf.type)
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename).encode("ascii", "ignore")
@@ -118,10 +125,16 @@ class ShreddersMixin:
)
try:
CachedFile.objects.get(
cf = CachedFile.objects.get(
id=kwargs['cfid'],
session_key=f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}'
)
if cf.session_key == "api-shredder-<class 'django.contrib.auth.models.AnonymousUser'>-None":
# OK, backwards-compatibility of a security bug fixed 2026-10, delete this at some point, but should
# also be harmless because all files with this key are expired one day after deployment of this fix
# and no new files with this key are created
pass
elif cf.session_key != get_session_key_for_api_request(self.request, prefix='api-shredder'):
raise CachedFile.DoesNotExist()
except CachedFile.DoesNotExist:
return Response(
{'status': 'gone', 'message': 'May have succeeded or timed out'},
@@ -140,8 +153,14 @@ class ShreddersMixin:
cf = get_object_or_404(
CachedFile,
id=kwargs['cfid'],
session_key=f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}'
)
if cf.session_key == "api-shredder-<class 'django.contrib.auth.models.AnonymousUser'>-None":
# OK, backwards-compatibility of a security bug fixed 2026-10, delete this at some point, but should
# also be harmless because all files with this key are expired one day after deployment of this fix
# and no new files with this key are created
pass
elif cf.session_key != get_session_key_for_api_request(self.request, prefix='api-shredder'):
raise Http404()
if cf.file:
async_result = self.do_shred(cf)
url_kwargs = {
@@ -184,7 +203,7 @@ class ShreddersMixin:
cf = CachedFile(web_download=False)
cf.date = now()
cf.expires = now() + timedelta(hours=2)
cf.session_key = f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}'
cf.session_key = get_session_key_for_api_request(self.request, prefix='api-shredder')
cf.save()
d = serializer.data
for k, v in d.items():
@@ -224,7 +243,7 @@ class EventShreddersViewSet(ShreddersMixin, viewsets.ViewSet):
return export.apply_async(args=(
self.request.event.id,
list(shredders),
f'api-shredder-{str(type(self.request.user or self.request.auth))}-{(self.request.user or self.request.auth).pk}',
get_session_key_for_api_request(self.request, prefix='api-shredder'),
str(cf.pk)
))
+6 -5
View File
@@ -55,9 +55,10 @@ from openpyxl.comments import Comment
from openpyxl.styles import Font, PatternFill
from pretix.base.models import (
Checkin, GiftCard, GiftCardTransaction, Invoice, InvoiceAddress, Order,
Checkin, GiftCardTransaction, Invoice, InvoiceAddress, Order,
OrderPosition, Question,
)
from pretix.base.models.giftcards import GiftCard
from pretix.base.models.orders import (
OrderFee, OrderPayment, OrderRefund, Transaction,
)
@@ -1326,12 +1327,12 @@ class GiftcardRedemptionListExporter(ListExporter):
order__event__in=self.events,
provider='giftcard',
state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED),
).order_by('created')
).order_by('created').select_related("order__event__organizer")
refunds = OrderRefund.objects.filter(
order__event__in=self.events,
provider='giftcard',
state=OrderRefund.REFUND_STATE_DONE
).order_by('created')
).order_by('created').select_related("order__event__organizer")
objs = sorted(list(payments) + list(refunds), key=lambda o: (o.order.code, o.created))
@@ -1348,9 +1349,9 @@ class GiftcardRedemptionListExporter(ListExporter):
obj.order.code,
obj.full_id,
obj.created.astimezone(tz).date().strftime('%Y-%m-%d'),
gc.secret,
gc.secret if gc.accepted_by(obj.order.event.organizer) else '?',
obj.amount * (-1 if isinstance(obj, OrderRefund) else 1),
gc.issuer
gc.issuer.slug
]
yield row
+35 -29
View File
@@ -1527,42 +1527,37 @@ class GiftCardPayment(BasePaymentProvider):
})
def refund_control_render(self, request, refund) -> str:
from .models import GiftCard
if 'gift_card' in refund.info_data:
gc = GiftCard.objects.get(pk=refund.info_data.get('gift_card'))
template = get_template('pretixcontrol/giftcards/payment.html')
ctx = {
'request': request,
'event': self.event,
'gc': gc,
}
try:
gc = self.event.organizer.accepted_gift_cards.get(pk=refund.info_data.get('gift_card'))
ctx['gc'] = gc
except GiftCard.DoesNotExist:
pass
return template.render(ctx)
def payment_control_render(self, request, payment) -> str:
from .models import GiftCard
template = get_template('pretixcontrol/giftcards/payment.html')
ctx = {
'request': request,
'event': self.event,
**({'error': payment.info_data[
'error']} if 'error' in payment.info_data else {}),
**({'gift_card_secret': payment.info_data[
'gift_card_secret']} if 'gift_card_secret' in payment.info_data else {})
}
if any(key in payment.info_data for key in ('gift_card', 'error')):
template = get_template('pretixcontrol/giftcards/payment.html')
ctx = {
'request': request,
'event': self.event,
**({'error': payment.info_data[
'error']} if 'error' in payment.info_data else {}),
**({'gift_card_secret': payment.info_data[
'gift_card_secret']} if 'gift_card_secret' in payment.info_data else {})
}
try:
ctx['gc'] = self.event.organizer.accepted_gift_cards.get(pk=payment.info_data.get('gift_card'))
except GiftCard.DoesNotExist:
pass
try:
gc = GiftCard.objects.get(pk=payment.info_data.get('gift_card'))
ctx = {
'gc': gc,
}
except GiftCard.DoesNotExist:
pass
finally:
return template.render(ctx)
if any(key in ctx for key in ('gc', 'gift_card_secret', 'error')):
return template.render(ctx)
def payment_control_render_short(self, payment: OrderPayment) -> str:
d = payment.info_data
@@ -1572,10 +1567,9 @@ class GiftCardPayment(BasePaymentProvider):
d = refund.info_data
return d.get('gift_card_secret', d.get('gift_card_code', self.public_name))
def api_payment_details(self, payment: OrderPayment):
from .models import GiftCard
def api_payment_details(self, payment: OrderPayment | OrderRefund):
try:
gc = GiftCard.objects.get(pk=payment.info_data.get('gift_card'))
gc = self.event.organizer.accepted_gift_cards.get(pk=payment.info_data.get('gift_card'))
except GiftCard.DoesNotExist:
return {
**({'error': payment.info_data[
@@ -1698,7 +1692,19 @@ class GiftCardPayment(BasePaymentProvider):
@transaction.atomic()
def execute_refund(self, refund: OrderRefund):
from .models import GiftCard
gc = GiftCard.objects.get(pk=refund.info_data.get('gift_card') or refund.payment.info_data.get('gift_card'))
try:
gc = self.event.organizer.accepted_gift_cards.get(
pk=refund.info_data.get('gift_card') or refund.payment.info_data.get('gift_card'),
currency=self.event.currency
)
except GiftCard.DoesNotExist:
raise PaymentException(_("Gift card not found."))
if gc.testmode != refund.order.testmode:
raise PaymentException(_("Only test gift card can be used in test mode and vice versa."))
if gc.expires and gc.expires < time_machine_now():
raise PaymentException(_("This gift card is no longer valid."))
trans = gc.transactions.create(
value=refund.amount,
order=refund.order,
+1 -1
View File
@@ -969,7 +969,7 @@ def _apply_rounding_and_fees(positions: List[CartPosition], payment_requests: Li
gift_cards = [] # for backwards compatibility
for p in payment_requests:
if p['provider'] == 'giftcard':
gift_cards.append(GiftCard.objects.get(pk=p['info_data']['gift_card']))
gift_cards.extend(event.organizer.accepted_gift_cards.filter(pk=p['info_data']['gift_card']))
for recv, resp in order_fee_calculation.send(sender=event, invoice_address=address, total=total, payment_requests=payment_requests,
meta_info=meta_info, positions=positions, gift_cards=gift_cards):
+1 -1
View File
@@ -172,7 +172,7 @@ class CachedFileInput(forms.ClearableFileInput):
from ...base.models import CachedFile
v = super().value_from_datadict(data, files, name)
if v is None and data.get(name + '-cachedfile'): # An explicit "[x] clear" would be False, not None
v = CachedFile.objects.filter(id=data[name + '-cachedfile']).first()
v = CachedFile.objects.filter(id=data[name + '-cachedfile'], web_download=True).first()
if not v.allowed_for_session(self.request):
v = None
return v
+4 -13
View File
@@ -38,7 +38,6 @@ from urllib.parse import urlparse
from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.forms import formset_factory, inlineformset_factory
from django.forms.utils import ErrorDict
from django.urls import reverse
@@ -70,9 +69,9 @@ from pretix.base.forms.widgets import (
SplitDateTimePickerWidget, format_placeholders_help_text,
)
from pretix.base.models import (
Customer, Device, Event, EventMetaProperty, Gate, GiftCard,
GiftCardAcceptance, Membership, MembershipType, OrderPosition, Organizer,
ReusableMedium, SalesChannel, Team,
Customer, Device, Event, EventMetaProperty, Gate, GiftCard, Membership,
MembershipType, OrderPosition, Organizer, ReusableMedium, SalesChannel,
Team,
)
from pretix.base.models.customers import CustomerSSOClient, CustomerSSOProvider
from pretix.base.models.organizer import OrganizerFooterLink, TeamQuerySet
@@ -894,15 +893,7 @@ class GiftCardCreateForm(forms.ModelForm):
def clean_secret(self):
s = self.cleaned_data['secret']
if GiftCard.objects.filter(
secret__iexact=s
).filter(
Q(issuer=self.organizer) |
Q(issuer__in=GiftCardAcceptance.objects.filter(
acceptor=self.organizer,
active=True,
).values_list('issuer', flat=True))
).exists():
if self.organizer.accepted_gift_cards.filter(secret__iexact=s).exists():
raise ValidationError(
_('A gift card with the same secret already exists in your or an affiliated organizer account.')
)
@@ -1,22 +1,24 @@
{% load i18n %}
<dl class="dl-horizontal">
<dt>{% trans "Gift card code" %}</dt>
<dd>
{% if gc %}
<a href="{% url "control:organizer.giftcard" organizer=gc.issuer.slug giftcard=gc.pk %}">
{{ gc.secret }}
</a>
{% if gc.issuer.slug != request.organizer %}
<span class="text-muted">
<br>
<span class="fa fa-group"></span> {{ gc.issuer }}
</span>
{% if gc or gift_card_secret %}
<dt>{% trans "Gift card code" %}</dt>
<dd>
{% if gc %}
<a href="{% url "control:organizer.giftcard" organizer=gc.issuer.slug giftcard=gc.pk %}">
{{ gc.secret }}
</a>
{% if gc.issuer.slug != request.organizer %}
<span class="text-muted">
<br>
<span class="fa fa-group"></span> {{ gc.issuer }}
</span>
{% endif %}
{% elif gift_card_secret %}
{{ gift_card_secret }}
{% endif %}
{% elif gift_card_secret %}
{{ gift_card_secret }}
{% endif %}
</dd>
</dd>
{% endif %}
{% if gc %}
<dt>{% trans "Issuer" %}</dt>
<dd>{{ gc.issuer }}</dd>
@@ -25,4 +27,4 @@
<dt>{% trans "Error" %}</dt>
<dd>{{ error }}</dd>
{% endif %}
</dl>
</dl>
+3 -1
View File
@@ -250,6 +250,8 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
if request.POST.get("background", "").strip():
try:
cf = CachedFile.objects.get(id=request.POST.get("background"), web_download=True)
if not cf.allowed_for_session(request, "ticketoutput-pdf-background"):
cf = None
except CachedFile.DoesNotExist:
pass
@@ -305,7 +307,7 @@ class FontsCSSView(TemplateView):
class PdfView(TemplateView):
def get(self, request, *args, **kwargs):
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf")
cf = get_object_or_404(CachedFile, id=kwargs.get("filename"), filename="background_preview.pdf", web_download=True)
if not cf.allowed_for_session(request, "ticketoutput-pdf-background"):
raise PermissionDenied()
resp = FileResponse(cf.file, filename=cf.filename, content_type='application/pdf')
+2 -2
View File
@@ -228,7 +228,7 @@ class TicketRendererViewSet(viewsets.ViewSet):
@action(detail=False, methods=['GET'], url_name='download', url_path='download/(?P<asyncid>[^/]+)/(?P<cfid>[^/]+)')
def download(self, *args, **kwargs):
cf = get_object_or_404(CachedFile, id=kwargs['cfid'])
cf = get_object_or_404(CachedFile, id=kwargs['cfid'], web_download=True)
if not cf.allowed_for_session(self.request, "ticketoutputpdf-api"):
return Response(
{'status': 'failed', 'message': 'Unknown file ID or export failed'},
@@ -269,7 +269,7 @@ class TicketRendererViewSet(viewsets.ViewSet):
})
serializer.is_valid(raise_exception=True)
cf = CachedFile(web_download=False)
cf = CachedFile(web_download=True)
cf.bind_to_session(self.request, "ticketoutputpdf-api")
cf.date = now()
cf.expires = now() + timedelta(hours=24)
+1 -1
View File
@@ -453,7 +453,7 @@ class CartApplyVoucher(EventViewMixin, CartActionMixin, AsyncAction, View):
if not self.request.event.vouchers.filter(code__iexact=code):
try:
gc = self.request.event.organizer.accepted_gift_cards.get(secret=code)
gc = self.request.organizer.accepted_gift_cards.get(secret=code)
gcp = GiftCardPayment(self.request.event)
if not gcp.is_enabled or not gcp.is_allowed(self.request, Decimal("1.00")):
raise ValidationError(error_messages['voucher_invalid'])
+5
View File
@@ -235,6 +235,11 @@ class AuthorizeView(View):
if response_type not in RESPONSE_TYPES_SUPPORTED:
return self._final_error("unsupported_response_type", "response_type unsupported")
if client.authorization_grant_type == "authorization-code" and response_type != "code":
# with authorization_grant_type=implicit, response_type=code is still allowed, otherwise "hybrid flow" would be impossible
return self._final_error("unsupported_response_type", "For clients with grant type set "
"to authorization code, using implicit or hybrid flows is not allowed")
if response_type != "code" and response_mode == "query":
return self._final_error("invalid_request", "response_mode query must not be used with implicit or hybrid flow")
elif not response_mode:
+1 -1
View File
@@ -375,7 +375,7 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TicketPageMixin,
ctx['user_cancel_allowed'] = self.order.user_cancel_allowed
for r in ctx['refunds']:
if r.provider == 'giftcard':
gc = GiftCard.objects.get(pk=r.info_data.get('gift_card'))
gc = self.request.organizer.accepted_gift_cards.get(pk=r.info_data.get('gift_card'))
r.giftcard = gc
return ctx
+94
View File
@@ -35,7 +35,9 @@ from tests.plugins.stripe.test_checkout import apple_domain_create
from tests.plugins.stripe.test_provider import MockedCharge
from pretix.base.models import InvoiceAddress, Order, OrderPosition, Team
from pretix.base.models.giftcards import GiftCard
from pretix.base.models.orders import OrderFee, OrderPayment, OrderRefund
from pretix.base.models.organizer import Organizer
@pytest.fixture
@@ -780,6 +782,28 @@ def test_payment_create_pending(token_client, organizer, event, order):
assert order.status == Order.STATUS_PENDING
@pytest.mark.django_db
def test_payment_create_giftcard(token_client, organizer, event, order):
djmail.outbox = []
with scopes_disabled():
p_before = order.payments.count()
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/'.format(
organizer.slug, event.slug, order.code
), format='json', data={
'provider': 'giftcard',
'state': 'confirmed',
'amount': order.total,
'info': {
'foo': 'bar'
}
})
assert resp.status_code == 400
order.refresh_from_db()
with scopes_disabled():
assert order.payments.count() == p_before
assert order.status == Order.STATUS_PENDING
@pytest.mark.django_db
def test_payment_confirm(token_client, organizer, event, order):
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/2/confirm/'.format(
@@ -873,6 +897,24 @@ def test_payment_refund_fail(token_client, organizer, event, order, monkeypatch)
assert resp.status_code == 400
assert resp.data == {'detail': 'Invalid state of payment.'}
with scopes_disabled():
o2 = Organizer.objects.create(name='Dummy 2', slug='dummy2', plugins='pretix.plugins.banktransfer')
gc = GiftCard.objects.create(issuer=o2, currency='EUR')
p1 = order.payments.create(
provider='giftcard',
state='confirmed',
amount=Decimal('23.00'),
payment_date=order.datetime,
info=json.dumps({"gift_card": gc.pk})
)
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/{}/refund/'.format(
organizer.slug, event.slug, order.code, p1.pk
), format='json', data={})
assert resp.status_code == 400
assert resp.data == {'detail': 'External error: Gift card not found.'}
p1.refresh_from_db()
assert p1.state == OrderPayment.PAYMENT_STATE_CONFIRMED
@pytest.mark.django_db
def test_payment_refund_success(token_client, organizer, event, order, monkeypatch):
@@ -916,6 +958,22 @@ def test_payment_refund_success(token_client, organizer, event, order, monkeypat
assert r.source == OrderRefund.REFUND_SOURCE_ADMIN
assert r.comment == "Foo"
with scopes_disabled():
gc = GiftCard.objects.create(issuer=organizer, currency='EUR')
p1 = order.payments.create(
provider='giftcard',
state='confirmed',
amount=Decimal('23.00'),
payment_date=order.datetime,
info=json.dumps({"gift_card": gc.pk})
)
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/{}/refund/'.format(
organizer.slug, event.slug, order.code, p1.pk
), format='json', data={})
assert resp.status_code == 200
p1.refresh_from_db()
assert p1.state == OrderPayment.PAYMENT_STATE_REFUNDED
@pytest.mark.django_db
def test_payment_refund_unavailable(token_client, organizer, event, order, monkeypatch):
@@ -1935,6 +1993,42 @@ def test_refund_create_invalid_payment(token_client, organizer, event, order):
assert resp.status_code == 400
@pytest.mark.django_db
def test_refund_create_giftcard(token_client, organizer, event, order):
with scopes_disabled():
o2 = Organizer.objects.create(name='Dummy 2', slug='dummy2', plugins='pretix.plugins.banktransfer')
gc = GiftCard.objects.create(issuer=o2, currency='EUR')
p1 = order.payments.create(
provider='giftcard',
state='confirmed',
amount=Decimal('23.00'),
payment_date=order.datetime,
info=json.dumps({"gift_card": gc.pk})
)
with scopes_disabled():
r_before = order.refunds.count()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/{}/refunds/'.format(
organizer.slug, event.slug, order.code
), format='json', data={
"state": "created",
"provider": "giftcard",
"amount": "23.00",
"source": "admin",
"payment": p1.pk,
"info": {
"gift_card": gc.pk,
}
}
)
assert resp.status_code == 400
with scopes_disabled():
assert order.refunds.count() == r_before
order.refresh_from_db()
assert order.status == Order.STATUS_PENDING
@pytest.mark.django_db
def test_order_delete(token_client, organizer, event, order):
resp = token_client.delete(
+29
View File
@@ -72,6 +72,9 @@ def ssoclient(env, client):
@pytest.mark.django_db
def test_authorize_final_errors(env, client, ssoclient):
ssoclient[0].authorization_grant_type = "implicit"
ssoclient[0].save()
r = client.get('/bigevents/oauth2/v1/authorize')
assert r.status_code == 400
assert b'client_id missing' in r.content
@@ -111,6 +114,9 @@ def test_authorize_final_errors(env, client, ssoclient):
@pytest.mark.django_db
def test_authorize_basic_redirect_errors(env, client, ssoclient):
ssoclient[0].authorization_grant_type = "implicit"
ssoclient[0].save()
r = client.get(
f'/bigevents/oauth2/v1/authorize?client_id={ssoclient[0].client_id}&redirect_uri=https://example.net&response_type=code')
assert r.status_code == 302
@@ -494,8 +500,29 @@ def test_token_client_secret_post(env, client, ssoclient):
assert 86390 < d['expires_in'] <= 86400
@pytest.mark.django_db
def test_authorize_implicit_flow_not_allowed_for_authcode_client(env, client, ssoclient):
r = client.post('/bigevents/account/login', {
'email': 'john@example.org',
'password': 'foo',
})
assert r.status_code == 302
url = f'/bigevents/oauth2/v1/authorize?client_id={ssoclient[0].client_id}&' \
f'redirect_uri=https://example.net&' \
f'response_type=id_token&' \
f'state=STATE&' \
f'nonce=NONCE123&' \
f'scope=openid+profile+email'
r = client.get(url)
assert r.status_code == 400
assert b'clients with grant type set to authorization code' in r.content
@pytest.mark.django_db
def test_authorize_implicit_flow(env, client, ssoclient):
ssoclient[0].authorization_grant_type = "implicit"
ssoclient[0].save()
r = client.post('/bigevents/account/login', {
'email': 'john@example.org',
'password': 'foo',
@@ -544,6 +571,8 @@ def test_authorize_implicit_flow(env, client, ssoclient):
@pytest.mark.django_db
def test_authorize_hybrid_flow(env, client, ssoclient):
ssoclient[0].authorization_grant_type = "implicit"
ssoclient[0].save()
r = client.post('/bigevents/account/login', {
'email': 'john@example.org',
'password': 'foo',