We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
PaymentDetailView in admin API allows deleting payments unauthorized
https://github.com/svthalia/concrexit/blob/4ab37961f50e398cc52422cdc1df66f6ab8ff2ee/website/payments/api/v2/admin/views.py#L69

### Describe the bug
Payments sometimes should be undeletable. For example, TPay payments that are in a batch. The PaymentAdmin prevents such deletions. However, the rest framework DestroyAPIView does not respect that.

### How to reproduce
Steps to reproduce the behaviour:
1. Have a payment 
2. Add it to a batch
3. Process the batch
4. Do the API `DELETE` request at `/api/v2/admin/payments/<id>`

### Expected behaviour
Either disable payment deletion at all from the API, or manually implement a check that the payment is not in a processed batch.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `website/payments/api/v2/admin/views.py`
Content:
```
1 import rest_framework.filters as framework_filters
2 from django.apps import apps
3 from django.http import Http404
4 from django.utils.translation import gettext_lazy as _
5 from oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope
6 from rest_framework import status, serializers
7 from rest_framework.exceptions import PermissionDenied, ValidationError
8 from rest_framework.generics import get_object_or_404
9 from rest_framework.permissions import IsAdminUser
10 from rest_framework.response import Response
11 from rest_framework.settings import api_settings
12 from rest_framework.views import APIView
13 
14 from payments import services, payables, NotRegistered
15 from payments.api.v2 import filters
16 from payments.api.v2.admin.serializers.payable_create import (
17     PayableCreateAdminSerializer,
18 )
19 from payments.api.v2.admin.serializers.payable_detail import PayableAdminSerializer
20 from payments.api.v2.admin.serializers.payment import (
21     PaymentAdminSerializer,
22     PaymentCreateSerializer,
23 )
24 from payments.exceptions import PaymentError
25 from payments.models import Payment, PaymentUser
26 from thaliawebsite.api.v2.admin import (
27     AdminListAPIView,
28     AdminCreateAPIView,
29     AdminRetrieveAPIView,
30     AdminDestroyAPIView,
31 )
32 
33 
34 class PaymentListCreateView(AdminListAPIView, AdminCreateAPIView):
35     """View that allows you to create and list payments as admin."""
36 
37     queryset = Payment.objects.prefetch_related(
38         "paid_by__profile",
39         "paid_by__membership_set",
40         "processed_by__profile",
41         "processed_by__membership_set",
42     )
43 
44     required_scopes = ["payments:admin"]
45     filter_backends = (
46         framework_filters.OrderingFilter,
47         filters.CreatedAtFilter,
48         filters.PaymentTypeFilter,
49     )
50     ordering_fields = ("created_at",)
51 
52     def get_serializer_class(self):
53         if self.request.method.lower() == "post":
54             return PaymentCreateSerializer
55         return PaymentAdminSerializer
56 
57     def create(self, request, *args, **kwargs):
58         serializer = self.get_serializer(data=request.data)
59         serializer.is_valid(raise_exception=True)
60         self.perform_create(serializer)
61         return Response(
62             PaymentAdminSerializer(
63                 serializer.instance, context=self.get_serializer_context()
64             ).data,
65             status=status.HTTP_201_CREATED,
66         )
67 
68 
69 class PaymentDetailView(AdminRetrieveAPIView, AdminDestroyAPIView):
70     """View that allows you to manage a single payment as admin."""
71 
72     queryset = Payment.objects.all()
73     serializer_class = PaymentAdminSerializer
74     permission_classes = [IsAuthenticatedOrTokenHasScope]
75     required_scopes = ["payments:admin"]
76 
77 
78 class PayableDetailView(APIView):
79     """View that allows you to manipulate the payment for the payable.
80 
81     Permissions of this view are based on the payable.
82     """
83 
84     required_scopes = ["payments:admin"]
85     permission_classes = [IsAuthenticatedOrTokenHasScope, IsAdminUser]
86 
87     def get_serializer_context(self):
88         return {"request": self.request, "format": self.format_kwarg, "view": self}
89 
90     def get_payable(self):
91         app_label = self.kwargs["app_label"]
92         model_name = self.kwargs["model_name"]
93         payable_pk = self.kwargs["payable_pk"]
94 
95         try:
96             payable_model = apps.get_model(app_label=app_label, model_name=model_name)
97             payable = payables.get_payable(
98                 get_object_or_404(payable_model, pk=payable_pk)
99             )
100         except (LookupError, NotRegistered) as e:
101             raise serializers.ValidationError(
102                 {api_settings.NON_FIELD_ERRORS_KEY: [_("Payable model not found")]}
103             ) from e
104 
105         if not payable.can_manage_payment(self.request.member):
106             raise PermissionDenied(
107                 detail=_("You do not have permission to perform this action.")
108             )
109 
110         return payable
111 
112     def get(self, request, *args, **kwargs):
113         """Get information about a payable."""
114         serializer = PayableAdminSerializer(
115             self.get_payable(), context=self.get_serializer_context()
116         )
117         return Response(serializer.data, status=status.HTTP_200_OK)
118 
119     def delete(self, request, *args, **kwargs):
120         """Remove the current payment for a payable."""
121         payable = self.get_payable()
122 
123         if not payable.model.payment:
124             raise Http404
125 
126         try:
127             services.delete_payment(
128                 payable.model, request.member,
129             )
130             payable.model.save()
131         except PaymentError as e:
132             raise PermissionDenied(detail=str(e))
133 
134         return Response(status=status.HTTP_204_NO_CONTENT)
135 
136     def patch(self, request, *args, **kwargs):
137         """Mark the payable as paid by creating a payment for it."""
138         serializer = PayableCreateAdminSerializer(
139             data=request.data, context=self.get_serializer_context()
140         )
141         serializer.is_valid(raise_exception=True)
142 
143         payable = self.get_payable()
144 
145         try:
146             services.create_payment(
147                 payable,
148                 PaymentUser.objects.get(pk=request.user.pk),
149                 serializer.data["payment_type"],
150             )
151             payable.model.save()
152         except PaymentError as e:
153             raise ValidationError(detail={api_settings.NON_FIELD_ERRORS_KEY: [str(e)]})
154 
155         return Response(
156             PayableAdminSerializer(payable, context=self.get_serializer_context()).data,
157             status=status.HTTP_201_CREATED,
158         )
159
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/website/payments/api/v2/admin/views.py b/website/payments/api/v2/admin/views.py
--- a/website/payments/api/v2/admin/views.py
+++ b/website/payments/api/v2/admin/views.py
@@ -74,6 +74,11 @@
     permission_classes = [IsAuthenticatedOrTokenHasScope]
     required_scopes = ["payments:admin"]
 
+    def delete(self, request, *args, **kwargs):
+        if self.get_object().batch and self.get_object().batch.processed:
+            raise PermissionDenied("This payment cannot be deleted.")
+        return super().delete(request, *args, **kwargs)
+
 
 class PayableDetailView(APIView):
     """View that allows you to manipulate the payment for the payable.

{"golden_diff": "diff --git a/website/payments/api/v2/admin/views.py b/website/payments/api/v2/admin/views.py\n--- a/website/payments/api/v2/admin/views.py\n+++ b/website/payments/api/v2/admin/views.py\n@@ -74,6 +74,11 @@\n     permission_classes = [IsAuthenticatedOrTokenHasScope]\n     required_scopes = [\"payments:admin\"]\n \n+    def delete(self, request, *args, **kwargs):\n+        if self.get_object().batch and self.get_object().batch.processed:\n+            raise PermissionDenied(\"This payment cannot be deleted.\")\n+        return super().delete(request, *args, **kwargs)\n+\n \n class PayableDetailView(APIView):\n     \"\"\"View that allows you to manipulate the payment for the payable.\n", "issue": "PaymentDetailView in admin API allows deleting payments unauthorized\nhttps://github.com/svthalia/concrexit/blob/4ab37961f50e398cc52422cdc1df66f6ab8ff2ee/website/payments/api/v2/admin/views.py#L69\r\n\r\n### Describe the bug\r\nPayments sometimes should be undeletable. For example, TPay payments that are in a batch. The PaymentAdmin prevents such deletions. However, the rest framework DestroyAPIView does not respect that.\r\n\r\n### How to reproduce\r\nSteps to reproduce the behaviour:\r\n1. Have a payment \r\n2. Add it to a batch\r\n3. Process the batch\r\n4. Do the API `DELETE` request at `/api/v2/admin/payments/<id>`\r\n\r\n### Expected behaviour\r\nEither disable payment deletion at all from the API, or manually implement a check that the payment is not in a processed batch.\r\n\n", "before_files": [{"content": "import rest_framework.filters as framework_filters\nfrom django.apps import apps\nfrom django.http import Http404\nfrom django.utils.translation import gettext_lazy as _\nfrom oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope\nfrom rest_framework import status, serializers\nfrom rest_framework.exceptions import PermissionDenied, ValidationError\nfrom rest_framework.generics import get_object_or_404\nfrom rest_framework.permissions import IsAdminUser\nfrom rest_framework.response import Response\nfrom rest_framework.settings import api_settings\nfrom rest_framework.views import APIView\n\nfrom payments import services, payables, NotRegistered\nfrom payments.api.v2 import filters\nfrom payments.api.v2.admin.serializers.payable_create import (\n    PayableCreateAdminSerializer,\n)\nfrom payments.api.v2.admin.serializers.payable_detail import PayableAdminSerializer\nfrom payments.api.v2.admin.serializers.payment import (\n    PaymentAdminSerializer,\n    PaymentCreateSerializer,\n)\nfrom payments.exceptions import PaymentError\nfrom payments.models import Payment, PaymentUser\nfrom thaliawebsite.api.v2.admin import (\n    AdminListAPIView,\n    AdminCreateAPIView,\n    AdminRetrieveAPIView,\n    AdminDestroyAPIView,\n)\n\n\nclass PaymentListCreateView(AdminListAPIView, AdminCreateAPIView):\n    \"\"\"View that allows you to create and list payments as admin.\"\"\"\n\n    queryset = Payment.objects.prefetch_related(\n        \"paid_by__profile\",\n        \"paid_by__membership_set\",\n        \"processed_by__profile\",\n        \"processed_by__membership_set\",\n    )\n\n    required_scopes = [\"payments:admin\"]\n    filter_backends = (\n        framework_filters.OrderingFilter,\n        filters.CreatedAtFilter,\n        filters.PaymentTypeFilter,\n    )\n    ordering_fields = (\"created_at\",)\n\n    def get_serializer_class(self):\n        if self.request.method.lower() == \"post\":\n            return PaymentCreateSerializer\n        return PaymentAdminSerializer\n\n    def create(self, request, *args, **kwargs):\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n        self.perform_create(serializer)\n        return Response(\n            PaymentAdminSerializer(\n                serializer.instance, context=self.get_serializer_context()\n            ).data,\n            status=status.HTTP_201_CREATED,\n        )\n\n\nclass PaymentDetailView(AdminRetrieveAPIView, AdminDestroyAPIView):\n    \"\"\"View that allows you to manage a single payment as admin.\"\"\"\n\n    queryset = Payment.objects.all()\n    serializer_class = PaymentAdminSerializer\n    permission_classes = [IsAuthenticatedOrTokenHasScope]\n    required_scopes = [\"payments:admin\"]\n\n\nclass PayableDetailView(APIView):\n    \"\"\"View that allows you to manipulate the payment for the payable.\n\n    Permissions of this view are based on the payable.\n    \"\"\"\n\n    required_scopes = [\"payments:admin\"]\n    permission_classes = [IsAuthenticatedOrTokenHasScope, IsAdminUser]\n\n    def get_serializer_context(self):\n        return {\"request\": self.request, \"format\": self.format_kwarg, \"view\": self}\n\n    def get_payable(self):\n        app_label = self.kwargs[\"app_label\"]\n        model_name = self.kwargs[\"model_name\"]\n        payable_pk = self.kwargs[\"payable_pk\"]\n\n        try:\n            payable_model = apps.get_model(app_label=app_label, model_name=model_name)\n            payable = payables.get_payable(\n                get_object_or_404(payable_model, pk=payable_pk)\n            )\n        except (LookupError, NotRegistered) as e:\n            raise serializers.ValidationError(\n                {api_settings.NON_FIELD_ERRORS_KEY: [_(\"Payable model not found\")]}\n            ) from e\n\n        if not payable.can_manage_payment(self.request.member):\n            raise PermissionDenied(\n                detail=_(\"You do not have permission to perform this action.\")\n            )\n\n        return payable\n\n    def get(self, request, *args, **kwargs):\n        \"\"\"Get information about a payable.\"\"\"\n        serializer = PayableAdminSerializer(\n            self.get_payable(), context=self.get_serializer_context()\n        )\n        return Response(serializer.data, status=status.HTTP_200_OK)\n\n    def delete(self, request, *args, **kwargs):\n        \"\"\"Remove the current payment for a payable.\"\"\"\n        payable = self.get_payable()\n\n        if not payable.model.payment:\n            raise Http404\n\n        try:\n            services.delete_payment(\n                payable.model, request.member,\n            )\n            payable.model.save()\n        except PaymentError as e:\n            raise PermissionDenied(detail=str(e))\n\n        return Response(status=status.HTTP_204_NO_CONTENT)\n\n    def patch(self, request, *args, **kwargs):\n        \"\"\"Mark the payable as paid by creating a payment for it.\"\"\"\n        serializer = PayableCreateAdminSerializer(\n            data=request.data, context=self.get_serializer_context()\n        )\n        serializer.is_valid(raise_exception=True)\n\n        payable = self.get_payable()\n\n        try:\n            services.create_payment(\n                payable,\n                PaymentUser.objects.get(pk=request.user.pk),\n                serializer.data[\"payment_type\"],\n            )\n            payable.model.save()\n        except PaymentError as e:\n            raise ValidationError(detail={api_settings.NON_FIELD_ERRORS_KEY: [str(e)]})\n\n        return Response(\n            PayableAdminSerializer(payable, context=self.get_serializer_context()).data,\n            status=status.HTTP_201_CREATED,\n        )\n", "path": "website/payments/api/v2/admin/views.py"}], "after_files": [{"content": "import rest_framework.filters as framework_filters\nfrom django.apps import apps\nfrom django.http import Http404\nfrom django.utils.translation import gettext_lazy as _\nfrom oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope\nfrom rest_framework import status, serializers\nfrom rest_framework.exceptions import PermissionDenied, ValidationError\nfrom rest_framework.generics import get_object_or_404\nfrom rest_framework.permissions import IsAdminUser\nfrom rest_framework.response import Response\nfrom rest_framework.settings import api_settings\nfrom rest_framework.views import APIView\n\nfrom payments import services, payables, NotRegistered\nfrom payments.api.v2 import filters\nfrom payments.api.v2.admin.serializers.payable_create import (\n    PayableCreateAdminSerializer,\n)\nfrom payments.api.v2.admin.serializers.payable_detail import PayableAdminSerializer\nfrom payments.api.v2.admin.serializers.payment import (\n    PaymentAdminSerializer,\n    PaymentCreateSerializer,\n)\nfrom payments.exceptions import PaymentError\nfrom payments.models import Payment, PaymentUser\nfrom thaliawebsite.api.v2.admin import (\n    AdminListAPIView,\n    AdminCreateAPIView,\n    AdminRetrieveAPIView,\n    AdminDestroyAPIView,\n)\n\n\nclass PaymentListCreateView(AdminListAPIView, AdminCreateAPIView):\n    \"\"\"View that allows you to create and list payments as admin.\"\"\"\n\n    queryset = Payment.objects.prefetch_related(\n        \"paid_by__profile\",\n        \"paid_by__membership_set\",\n        \"processed_by__profile\",\n        \"processed_by__membership_set\",\n    )\n\n    required_scopes = [\"payments:admin\"]\n    filter_backends = (\n        framework_filters.OrderingFilter,\n        filters.CreatedAtFilter,\n        filters.PaymentTypeFilter,\n    )\n    ordering_fields = (\"created_at\",)\n\n    def get_serializer_class(self):\n        if self.request.method.lower() == \"post\":\n            return PaymentCreateSerializer\n        return PaymentAdminSerializer\n\n    def create(self, request, *args, **kwargs):\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n        self.perform_create(serializer)\n        return Response(\n            PaymentAdminSerializer(\n                serializer.instance, context=self.get_serializer_context()\n            ).data,\n            status=status.HTTP_201_CREATED,\n        )\n\n\nclass PaymentDetailView(AdminRetrieveAPIView, AdminDestroyAPIView):\n    \"\"\"View that allows you to manage a single payment as admin.\"\"\"\n\n    queryset = Payment.objects.all()\n    serializer_class = PaymentAdminSerializer\n    permission_classes = [IsAuthenticatedOrTokenHasScope]\n    required_scopes = [\"payments:admin\"]\n\n    def delete(self, request, *args, **kwargs):\n        if self.get_object().batch and self.get_object().batch.processed:\n            raise PermissionDenied(\"This payment cannot be deleted.\")\n        return super().delete(request, *args, **kwargs)\n\n\nclass PayableDetailView(APIView):\n    \"\"\"View that allows you to manipulate the payment for the payable.\n\n    Permissions of this view are based on the payable.\n    \"\"\"\n\n    required_scopes = [\"payments:admin\"]\n    permission_classes = [IsAuthenticatedOrTokenHasScope, IsAdminUser]\n\n    def get_serializer_context(self):\n        return {\"request\": self.request, \"format\": self.format_kwarg, \"view\": self}\n\n    def get_payable(self):\n        app_label = self.kwargs[\"app_label\"]\n        model_name = self.kwargs[\"model_name\"]\n        payable_pk = self.kwargs[\"payable_pk\"]\n\n        try:\n            payable_model = apps.get_model(app_label=app_label, model_name=model_name)\n            payable = payables.get_payable(\n                get_object_or_404(payable_model, pk=payable_pk)\n            )\n        except (LookupError, NotRegistered) as e:\n            raise serializers.ValidationError(\n                {api_settings.NON_FIELD_ERRORS_KEY: [_(\"Payable model not found\")]}\n            ) from e\n\n        if not payable.can_manage_payment(self.request.member):\n            raise PermissionDenied(\n                detail=_(\"You do not have permission to perform this action.\")\n            )\n\n        return payable\n\n    def get(self, request, *args, **kwargs):\n        \"\"\"Get information about a payable.\"\"\"\n        serializer = PayableAdminSerializer(\n            self.get_payable(), context=self.get_serializer_context()\n        )\n        return Response(serializer.data, status=status.HTTP_200_OK)\n\n    def delete(self, request, *args, **kwargs):\n        \"\"\"Remove the current payment for a payable.\"\"\"\n        payable = self.get_payable()\n\n        if not payable.model.payment:\n            raise Http404\n\n        try:\n            services.delete_payment(\n                payable.model, request.member,\n            )\n            payable.model.save()\n        except PaymentError as e:\n            raise PermissionDenied(detail=str(e))\n\n        return Response(status=status.HTTP_204_NO_CONTENT)\n\n    def patch(self, request, *args, **kwargs):\n        \"\"\"Mark the payable as paid by creating a payment for it.\"\"\"\n        serializer = PayableCreateAdminSerializer(\n            data=request.data, context=self.get_serializer_context()\n        )\n        serializer.is_valid(raise_exception=True)\n\n        payable = self.get_payable()\n\n        try:\n            services.create_payment(\n                payable,\n                PaymentUser.objects.get(pk=request.user.pk),\n                serializer.data[\"payment_type\"],\n            )\n            payable.model.save()\n        except PaymentError as e:\n            raise ValidationError(detail={api_settings.NON_FIELD_ERRORS_KEY: [str(e)]})\n\n        return Response(\n            PayableAdminSerializer(payable, context=self.get_serializer_context()).data,\n            status=status.HTTP_201_CREATED,\n        )\n", "path": "website/payments/api/v2/admin/views.py"}]}