diff --git a/doc/api/resources/orders.rst b/doc/api/resources/orders.rst index 967ed7a2b..c0dc26bd6 100644 --- a/doc/api/resources/orders.rst +++ b/doc/api/resources/orders.rst @@ -103,7 +103,7 @@ last_modified datetime Last modificati .. versionchanged:: 1.16 The attributes ``order.last_modified`` as well as the corresponding filters to the resource have been added. - An endpoint for order creation has been added. + An endpoint for order creation as well as ``…/mark_refunded/`` has been added. .. _order-position-resource: @@ -720,6 +720,44 @@ Order endpoints :statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource. :statuscode 404: The requested order does not exist. +.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/mark_refunded/ + + Marks a paid order as refunded. + + .. warning:: In the current implementation, this will **bypass** the payment provider, i.e. the money will **not** be + transferred back to the user automatically, the order will only be *marked* as refunded within pretix. + + **Example request**: + + .. sourcecode:: http + + POST /api/v1/organizers/bigevents/events/sampleconf/orders/ABC12/mark_expired/ HTTP/1.1 + Host: pretix.eu + Accept: application/json, text/javascript + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + { + "code": "ABC12", + "status": "r", + ... + } + + :param organizer: The ``slug`` field of the organizer to modify + :param event: The ``slug`` field of the event to modify + :param code: The ``code`` field of the order to modify + :statuscode 200: no error + :statuscode 400: The order cannot be marked as expired since the current order status does not allow it. + :statuscode 401: Authentication failure + :statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource. + :statuscode 404: The requested order does not exist. + .. http:post:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/mark_expired/ Marks a unpaid order as expired. diff --git a/src/pretix/api/views/order.py b/src/pretix/api/views/order.py index 198113c44..08c7426df 100644 --- a/src/pretix/api/views/order.py +++ b/src/pretix/api/views/order.py @@ -30,7 +30,7 @@ from pretix.base.services.invoices import ( from pretix.base.services.mail import SendMailException from pretix.base.services.orders import ( OrderError, cancel_order, extend_order, mark_order_expired, - mark_order_paid, + mark_order_paid, mark_order_refunded, ) from pretix.base.services.tickets import ( get_cachedticket_for_order, get_cachedticket_for_position, @@ -193,7 +193,22 @@ class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet): ) return self.retrieve(request, [], **kwargs) - # TODO: Find a way to implement mark_refunded + @detail_route(methods=['POST']) + def mark_refunded(self, request, **kwargs): + order = self.get_object() + + if order.status != Order.STATUS_PAID: + return Response( + {'detail': 'The order is not paid.'}, + status=status.HTTP_400_BAD_REQUEST + ) + + mark_order_refunded( + order, + user=request.user if request.user.is_authenticated else None, + api_token=(request.auth if isinstance(request.auth, TeamAPIToken) else None), + ) + return self.retrieve(request, [], **kwargs) @detail_route(methods=['POST']) def extend(self, request, **kwargs): diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index 4d54f8517..2b535e66c 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -214,7 +214,7 @@ def extend_order(order: Order, new_date: datetime, force: bool=False, user: User @transaction.atomic -def mark_order_refunded(order, user=None): +def mark_order_refunded(order, user=None, api_token=None): """ Mark this order as refunded. This sets the payment status and returns the order object. :param order: The order to change @@ -228,7 +228,7 @@ def mark_order_refunded(order, user=None): order.status = Order.STATUS_REFUNDED order.save() - order.log_action('pretix.event.order.refunded', user=user) + order.log_action('pretix.event.order.refunded', user=user, api_token=api_token) i = order.invoices.filter(is_cancellation=False).last() if i: generate_cancellation(i) diff --git a/src/tests/api/test_orders.py b/src/tests/api/test_orders.py index a165689b4..1f940b6bb 100644 --- a/src/tests/api/test_orders.py +++ b/src/tests/api/test_orders.py @@ -599,6 +599,33 @@ def test_order_mark_canceled_paid(token_client, organizer, event, order): assert order.status == Order.STATUS_PAID +@pytest.mark.django_db +def test_order_mark_paid_refunded(token_client, organizer, event, order): + order.status = Order.STATUS_PAID + order.save() + resp = token_client.post( + '/api/v1/organizers/{}/events/{}/orders/{}/mark_refunded/'.format( + organizer.slug, event.slug, order.code + ) + ) + assert resp.status_code == 200 + assert resp.data['status'] == Order.STATUS_REFUNDED + + +@pytest.mark.django_db +def test_order_mark_canceled_refunded(token_client, organizer, event, order): + order.status = Order.STATUS_CANCELED + order.save() + resp = token_client.post( + '/api/v1/organizers/{}/events/{}/orders/{}/mark_refunded/'.format( + organizer.slug, event.slug, order.code + ) + ) + assert resp.status_code == 400 + order.refresh_from_db() + assert order.status == Order.STATUS_CANCELED + + @pytest.mark.django_db def test_order_mark_paid_unpaid(token_client, organizer, event, order): order.status = Order.STATUS_PAID