diff --git a/doc/api/resources/orders.rst b/doc/api/resources/orders.rst index 1c5a822b04..e08551dbcb 100644 --- a/doc/api/resources/orders.rst +++ b/doc/api/resources/orders.rst @@ -137,13 +137,17 @@ last_modified datetime Last modificati The ``event`` attribute has been added. The organizer-level endpoint has been added. +.. versionchanged:: 2023.9 + + The ``customer`` query parameter has been added. + .. versionchanged:: 2023.10 The ``checkin_text`` attribute has been added. -.. versionchanged:: 2023.9 +.. versionchanged:: 2024.1 - The ``customer`` query parameter has been added. + The ``expires`` attribute can now be passed during order creation. .. _order-position-resource: @@ -729,6 +733,8 @@ Updating order fields * ``valid_if_pending`` + * ``expires`` + **Example request**: .. sourcecode:: http diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index fdcc7686bc..02c08f7392 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -1035,13 +1035,14 @@ class OrderCreateSerializer(I18nAwareModelSerializer): super().__init__(*args, **kwargs) self.fields['positions'].child.fields['voucher'].queryset = self.context['event'].vouchers.all() self.fields['customer'].queryset = self.context['event'].organizer.customers.all() + self.fields['expires'].required = False class Meta: model = Order fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel', 'invoice_address', 'positions', 'checkin_attention', 'checkin_text', 'payment_info', 'payment_date', 'consume_carts', 'force', 'send_email', 'simulate', 'customer', 'custom_followup_at', - 'require_approval', 'valid_if_pending') + 'require_approval', 'valid_if_pending', 'expires') def validate_payment_provider(self, pp): if pp is None: @@ -1050,6 +1051,11 @@ class OrderCreateSerializer(I18nAwareModelSerializer): raise ValidationError('The given payment provider is not known.') return pp + def validate_expires(self, expires): + if expires < now(): + raise ValidationError('Expiration date must be in the future.') + return expires + def validate_sales_channel(self, channel): if channel not in get_all_sales_channels(): raise ValidationError('Unknown sales channel.') @@ -1356,7 +1362,8 @@ class OrderCreateSerializer(I18nAwareModelSerializer): if validated_data.get('locale', None) is None: validated_data['locale'] = self.context['event'].settings.locale order = Order(event=self.context['event'], **validated_data) - order.set_expires(subevents=[p.get('subevent') for p in positions_data]) + if not validated_data.get('expires'): + order.set_expires(subevents=[p.get('subevent') for p in positions_data]) order.meta_info = "{}" order.total = Decimal('0.00') if validated_data.get('require_approval') is not None: diff --git a/src/tests/api/test_order_create.py b/src/tests/api/test_order_create.py index f8cfb0569d..59bd055228 100644 --- a/src/tests/api/test_order_create.py +++ b/src/tests/api/test_order_create.py @@ -246,6 +246,7 @@ def test_order_create(token_client, organizer, event, item, quota, question): assert o.status == Order.STATUS_PENDING assert o.sales_channel == "web" assert o.valid_if_pending + assert o.expires > now() assert not o.testmode with scopes_disabled(): @@ -277,6 +278,37 @@ def test_order_create(token_client, organizer, event, item, quota, question): assert o.transactions.count() == 2 +@pytest.mark.django_db +def test_order_create_expires(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 + + expires = now() - datetime.timedelta(hours=7) + res['expires'] = expires.isoformat() + resp = token_client.post( + '/api/v1/organizers/{}/events/{}/orders/'.format( + organizer.slug, event.slug + ), format='json', data=res + ) + assert resp.status_code == 400 + assert resp.data == {"expires": ["Expiration date must be in the future."]} + + expires = now() + datetime.timedelta(hours=7) + res['expires'] = expires.isoformat() + resp = token_client.post( + '/api/v1/organizers/{}/events/{}/orders/'.format( + organizer.slug, event.slug + ), format='json', data=res + ) + assert resp.status_code == 201 + assert not resp.data['positions'][0].get('pdf_data') + with scopes_disabled(): + o = Order.objects.get(code=resp.data['code']) + assert o.expires == expires + assert o.status == Order.STATUS_PENDING + + @pytest.mark.django_db def test_order_create_simulate(token_client, organizer, event, item, quota, question): res = copy.deepcopy(ORDER_CREATE_PAYLOAD)