Repository: svthalia/concrexit

Issue:
ThaliaPay Payment type can be None
### Describe the bug
Mark tried to pay for his pizza with ThaliaPay on the Craft's Guild event food event. 

### How to reproduce

(Not sure if this works)

1. Go to pay for a pizza
2. Pay with ThaliaPay
3. Don't confirm maybe??

### Expected behaviour
ThaliaPay Payments with type cannot be None

### Screenshots
![image](https://user-images.githubusercontent.com/1576660/135886943-f34b4205-c4ed-49a8-b424-9bd3a9136554.png)

### Additional context
Mark has board permissions. This also happened with Lars who is also board, but he said he didn't try to pay with ThaliaPay



Discussion:
In the pizza event overview Mark and Lars' payments where green but showed `None`

Resolving patch:
diff --git a/website/payments/services.py b/website/payments/services.py
--- a/website/payments/services.py
+++ b/website/payments/services.py
@@ -27,6 +27,9 @@ def create_payment(
     :param pay_type: Payment type
     :return: Payment object
     """
+    if pay_type not in (Payment.CASH, Payment.CARD, Payment.WIRE, Payment.TPAY):
+        raise PaymentError("Invalid payment type")
+
     if isinstance(model_payable, Payable):
         payable = model_payable
     else:
diff --git a/website/pizzas/admin.py b/website/pizzas/admin.py
--- a/website/pizzas/admin.py
+++ b/website/pizzas/admin.py
@@ -2,12 +2,14 @@
 from django.conf import settings
 from django.contrib import admin
 from django.core.exceptions import PermissionDenied
+from django.forms import Field
 from django.urls import reverse, path
 from django.utils.html import format_html
 from django.utils.translation import gettext_lazy as _
 
 from events import services
 from events.services import is_organiser
+from payments.widgets import PaymentWidget
 from pizzas import admin_views
 from utils.admin import DoNextModelAdmin
 from .models import FoodOrder, FoodEvent, Product
@@ -87,7 +89,23 @@ class FoodOrderAdmin(DoNextModelAdmin):
         "product",
         "payment",
     )
-    exclude = ("payment",)
+
+    fields = (
+        "food_event",
+        "member",
+        "name",
+        "product",
+        "payment",
+    )
+
+    def formfield_for_dbfield(self, db_field, request, obj=None, **kwargs):
+        """Payment field widget."""
+        field = super().formfield_for_dbfield(db_field, request, **kwargs)
+        if db_field.name == "payment":
+            return Field(
+                widget=PaymentWidget(obj=obj), initial=field.initial, required=False,
+            )
+        return field
 
     def save_model(self, request, obj, form, change):
         """You can only save the orders if you have permission."""
diff --git a/website/pizzas/api/v1/viewsets.py b/website/pizzas/api/v1/viewsets.py
--- a/website/pizzas/api/v1/viewsets.py
+++ b/website/pizzas/api/v1/viewsets.py
@@ -110,6 +110,6 @@ def perform_update(self, serializer):
     def _update_payment(order, payment_type=None, processed_by=None):
         if order.payment and payment_type == PaymentTypeField.NO_PAYMENT:
             delete_payment(order, processed_by)
-        else:
+        elif payment_type != PaymentTypeField.NO_PAYMENT:
             order.payment = create_payment(order, processed_by, payment_type)
             order.save()


Test patch:
diff --git a/website/payments/tests/test_services.py b/website/payments/tests/test_services.py
--- a/website/payments/tests/test_services.py
+++ b/website/payments/tests/test_services.py
@@ -66,6 +66,14 @@ def test_create_payment(self):
                     Payment.TPAY,
                 )
 
+        with self.subTest("Only allow valid payment types"):
+            with self.assertRaises(PaymentError):
+                services.create_payment(
+                    MockPayable(MockModel(payer=self.member, amount=0)),
+                    self.member,
+                    "no_payment",
+                )
+
     def test_delete_payment(self):
         existing_payment = MagicMock(batch=None)
         payable = MockPayable(MockModel(payer=self.member, payment=existing_payment))