diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index 2ac2ab8ba..0e20f9c9c 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -53,7 +53,7 @@ from pretix.base.decimal import round_decimal from pretix.base.i18n import language from pretix.base.invoicing.transmission import get_transmission_types from pretix.base.models import ( - CachedFile, Checkin, Customer, Device, Invoice, InvoiceAddress, + CachedFile, Checkin, Customer, Device, GiftCard, Invoice, InvoiceAddress, InvoiceLine, Item, ItemVariation, Order, OrderPosition, Question, QuestionAnswer, ReusableMedium, SalesChannel, Seat, SubEvent, TaxRule, Voucher, @@ -1831,6 +1831,18 @@ class OrderCreateSerializer(I18nAwareModelSerializer): except PaymentException: pass + except GiftCard.DoesNotExist as e: + payment = order.payments.create( + amount=order.pending_sum, + provider=GiftCardPayment.identifier, + info_data={ + 'gift_card_secret': gift_card_secret, + }, + state=OrderPayment.PAYMENT_STATE_CREATED + ) + payment.fail(info={**payment.info_data, 'error': str(e)}, + send_mail=False) + if order.total == Decimal('0.00') and validated_data.get('status') != Order.STATUS_PAID and not validated_data.get('require_approval'): order.status = Order.STATUS_PAID order.save() diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py index f7fdd3c7a..14fe05829 100644 --- a/src/pretix/base/payment.py +++ b/src/pretix/base/payment.py @@ -1525,18 +1525,26 @@ class GiftCardPayment(BasePaymentProvider): def payment_control_render(self, request, payment) -> str: from .models import GiftCard - if 'gift_card' in payment.info_data: - gc = GiftCard.objects.get(pk=payment.info_data.get('gift_card')) + 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, - 'gc': gc, **({'error': payment.info_data[ - 'error']} if 'error' in payment.info_data else {}) + '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 {}) } - return template.render(ctx) + + try: + gc = GiftCard.objects.get(pk=payment.info_data.get('gift_card')) + ctx = { + 'gc': gc, + } + except GiftCard.DoesNotExist: + pass + finally: + return template.render(ctx) def payment_control_render_short(self, payment: OrderPayment) -> str: d = payment.info_data @@ -1551,7 +1559,10 @@ class GiftCardPayment(BasePaymentProvider): try: gc = GiftCard.objects.get(pk=payment.info_data.get('gift_card')) except GiftCard.DoesNotExist: - return {} + return { + **({'error': payment.info_data[ + 'error']} if 'error' in payment.info_data else {}) + } return { 'gift_card': { 'id': gc.pk, diff --git a/src/pretix/control/templates/pretixcontrol/giftcards/payment.html b/src/pretix/control/templates/pretixcontrol/giftcards/payment.html index 8ccbb0565..ae3368707 100644 --- a/src/pretix/control/templates/pretixcontrol/giftcards/payment.html +++ b/src/pretix/control/templates/pretixcontrol/giftcards/payment.html @@ -3,18 +3,24 @@
{% trans "Gift card code" %}
- - {{ gc.secret }} - - {% if gc.issuer != request.organizer %} - -
- {{ gc.issuer }} -
+ {% if gc %} + + {{ gc.secret }} + + {% if gc.issuer.slug != request.organizer %} + +
+ {{ gc.issuer }} +
+ {% endif %} + {% elif gift_card_secret %} + {{ gift_card_secret }} {% endif %}
-
{% trans "Issuer" %}
-
{{ gc.issuer }}
+ {% if gc %} +
{% trans "Issuer" %}
+
{{ gc.issuer }}
+ {% endif %} {% if error %}
{% trans "Error" %}
{{ error }}
diff --git a/src/tests/api/test_order_create.py b/src/tests/api/test_order_create.py index a7ac6e51c..fe0377946 100644 --- a/src/tests/api/test_order_create.py +++ b/src/tests/api/test_order_create.py @@ -3535,14 +3535,14 @@ def test_order_create_use_gift_card_exclusive_with_payment_provider(token_client res['use_gift_cards'] = [gc.secret] - res_with_payment_provider=copy.deepcopy(res) + res_with_payment_provider = copy.deepcopy(res) resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/'.format( organizer.slug, event.slug ), format='json', data=res_with_payment_provider ) assert resp.status_code == 400 - assert resp.json() == {"use_gift_cards":["The attribute use_gift_cards is not compatible with payment_provider or payment_info"]} + assert resp.json() == {"use_gift_cards": ["The attribute use_gift_cards is not compatible with payment_provider or payment_info"]} res_with_payment_info = copy.deepcopy(res) res_with_payment_info['payment_info'] = {"a": "b"} @@ -3553,7 +3553,7 @@ def test_order_create_use_gift_card_exclusive_with_payment_provider(token_client ), format='json', data=res_with_payment_info ) assert resp.status_code == 400 - assert resp.json() == {"use_gift_cards":["The attribute use_gift_cards is not compatible with payment_provider or payment_info"]} + assert resp.json() == {"use_gift_cards": ["The attribute use_gift_cards is not compatible with payment_provider or payment_info"]} @pytest.mark.django_db @@ -3584,3 +3584,38 @@ def test_order_create_use_gift_card_repeated(token_client, organizer, event, ite ) assert resp.status_code == 400 assert resp.json() == {'use_gift_cards': ['Multiple copies of the same gift card secret are not allowed']} + + +@pytest.mark.django_db +def test_order_create_use_gift_card_invalid_secret(token_client, organizer, event, item, quota, question): + res = copy.deepcopy(ORDER_CREATE_PAYLOAD) + res['positions'][0]['item'] = item.pk + res['positions'][0]['answers'][0]['question'] = question.pk + with scopes_disabled(): + customer = organizer.customers.create() + + res['customer'] = customer.identifier + res['api_meta'] = { + 'test': 1 + } + del res['payment_provider'] + + gc_enough_eur = GiftCard.objects.create(issuer=organizer, currency='EUR') + gc_enough_eur.transactions.create(value=Decimal("100.00"), + acceptor=organizer).save() + + res['use_gift_cards'] = ["INVALID", gc_enough_eur.secret] + + resp = token_client.post( + '/api/v1/organizers/{}/events/{}/orders/'.format( + organizer.slug, event.slug + ), format='json', data=res + ) + assert resp.status_code == 201 + + with scopes_disabled(): + o = Order.objects.get(code=resp.data['code']) + assert o.status == Order.STATUS_PAID + assert o.payments.count() == 2 + assert o.payments.all()[0].state == OrderPayment.PAYMENT_STATE_FAILED + assert o.payments.all()[1].state == OrderPayment.PAYMENT_STATE_CONFIRMED