diff --git a/doc/api/resources/orders.rst b/doc/api/resources/orders.rst index a649001cd4..f0944a0b20 100644 --- a/doc/api/resources/orders.rst +++ b/doc/api/resources/orders.rst @@ -141,7 +141,9 @@ last_modified datetime Last modificati .. versionchanged:: 3.1: - The ``invoice_address.state`` attribute has been added. + The ``invoice_address.state`` attribute has been added. When creating orders through the API, + vouchers are now supported and many fields are now optional. + .. _order-position-resource: @@ -754,9 +756,9 @@ Creating orders * ``email`` * ``locale`` * ``sales_channel`` - * ``payment_provider`` – The identifier of the payment provider set for this order. This needs to be an existing - payment provider. You should use ``"free"`` for free orders, and we strongly advise to use ``"manual"`` for all - orders you create as paid. + * ``payment_provider`` (optional) – The identifier of the payment provider set for this order. This needs to be an + existing payment provider. You should use ``"free"`` for free orders, and we strongly advise to use ``"manual"`` + for all orders you create as paid. * ``payment_info`` (optional) – You can pass a nested JSON object that will be set as the internal ``info`` value of the payment object that will be created. How this value is handled is up to the payment provider and you should only use this if you know the specific payment provider in detail. Please keep in mind that the payment diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index 2b9de04d1c..ea88d44ceb 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -555,7 +555,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer): min_length=5 ) comment = serializers.CharField(required=False, allow_blank=True) - payment_provider = serializers.CharField(required=True) + payment_provider = serializers.CharField(required=False, allow_null=True) payment_info = CompatibleJSONField(required=False) consume_carts = serializers.ListField(child=serializers.CharField(), required=False) force = serializers.BooleanField(default=False, required=False) @@ -573,6 +573,8 @@ class OrderCreateSerializer(I18nAwareModelSerializer): 'force', 'send_mail') def validate_payment_provider(self, pp): + if pp is None: + return None if pp not in self.context['event'].get_payment_providers(): raise ValidationError('The given payment provider is not known.') return pp @@ -642,7 +644,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer): def create(self, validated_data): fees_data = validated_data.pop('fees') if 'fees' in validated_data else [] positions_data = validated_data.pop('positions') if 'positions' in validated_data else [] - payment_provider = validated_data.pop('payment_provider') + payment_provider = validated_data.pop('payment_provider', None) payment_info = validated_data.pop('payment_info', '{}') payment_date = validated_data.pop('payment_date', now()) force = validated_data.pop('force', False) @@ -832,6 +834,8 @@ class OrderCreateSerializer(I18nAwareModelSerializer): elif payment_provider == "free" and order.total != Decimal('0.00'): raise ValidationError('You cannot use the "free" payment provider for non-free orders.') elif validated_data.get('status') == Order.STATUS_PAID: + if not payment_provider: + raise ValidationError('You cannot create a paid order without a payment provider.') order.payments.create( amount=order.total, provider=payment_provider, diff --git a/src/tests/api/test_orders.py b/src/tests/api/test_orders.py index 5a85821711..df5d6168bb 100644 --- a/src/tests/api/test_orders.py +++ b/src/tests/api/test_orders.py @@ -2495,21 +2495,6 @@ def test_order_create_free(token_client, organizer, event, item, quota, question assert p.state == "confirmed" -@pytest.mark.django_db -def test_order_create_require_payment_provider(token_client, organizer, event, item, quota, question): - res = copy.deepcopy(ORDER_CREATE_PAYLOAD) - del res['payment_provider'] - res['positions'][0]['item'] = item.pk - res['positions'][0]['answers'][0]['question'] = question.pk - 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 == {'payment_provider': ['This field is required.']} - - @pytest.mark.django_db def test_order_create_invalid_payment_provider(token_client, organizer, event, item, quota, question): res = copy.deepcopy(ORDER_CREATE_PAYLOAD) @@ -2853,6 +2838,35 @@ def test_order_create_send_emails_paid(token_client, organizer, event, item, quo assert djmail.outbox[1].subject == "Payment received for your order: {}".format(resp.data['code']) +@pytest.mark.django_db +def test_order_paid_require_payment_method(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 + del res['payment_provider'] + res['status'] = 'p' + 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 == [ + 'You cannot create a paid order without a payment provider.' + ] + + res['status'] = "n" + 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 not o.payments.exists() + + @pytest.mark.django_db def test_order_create_auto_pricing(token_client, organizer, event, item, quota, question): res = copy.deepcopy(ORDER_CREATE_PAYLOAD)