diff --git a/server/vbv_lernwelt/shop/tests/test_checkout_api.py b/server/vbv_lernwelt/shop/tests/test_checkout_api.py index a243ae36..1ec7c52b 100644 --- a/server/vbv_lernwelt/shop/tests/test_checkout_api.py +++ b/server/vbv_lernwelt/shop/tests/test_checkout_api.py @@ -11,7 +11,6 @@ from vbv_lernwelt.shop.models import ( Product, VV_PRODUCT_SKU, ) -from vbv_lernwelt.shop.services import get_payment_url USER_USERNAME = "testuser" USER_EMAIL = "test@example.com" @@ -33,8 +32,10 @@ TEST_ADDRESS_DATA = { "company_country": "Test Company Country", } +REDIRECT_URL = "http://testserver/redirect-url" -class CheckoutAPIView(APITestCase): + +class CheckoutAPITestCase(APITestCase): def setUp(self) -> None: Product.objects.create( sku=VV_PRODUCT_SKU, @@ -53,17 +54,16 @@ class CheckoutAPIView(APITestCase): self.client.login(username=USER_USERNAME, password=USER_PASSWORD) @patch("vbv_lernwelt.shop.views.init_transaction") - def test_checkout(self, mock_init_transaction): + def test_checkout_happy_case(self, mock_init_transaction): # GIVEN mock_init_transaction.return_value = "1234567890" - redirect_url = "http://testserver/redirect-url" # WHEN response = self.client.post( path=reverse("checkout-vv"), format="json", data={ - "redirect_url": redirect_url, + "redirect_url": REDIRECT_URL, "address": TEST_ADDRESS_DATA, }, ) @@ -71,8 +71,8 @@ class CheckoutAPIView(APITestCase): # THEN self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual( + f"https://pay.sandbox.datatrans.com/v1/start/1234567890", response.json()["next_step_url"], - get_payment_url("1234567890"), ) self.assertTrue( @@ -82,3 +82,38 @@ class CheckoutAPIView(APITestCase): state=CheckoutState.INITIALIZED.value, ).exists() ) + + mock_init_transaction.assert_called_once_with( + user=self.user, + amount_chf_centimes=300_00, + redirect_url_success=f"{REDIRECT_URL}/onboarding/vv/checkout/complete", + redirect_url_error=f"{REDIRECT_URL}/onboarding/vv/checkout/address?error", + redirect_url_cancel=f"{REDIRECT_URL}/", + webhook_url=f"{REDIRECT_URL}/api/shop/transaction/webhook/", + ) + + @patch("vbv_lernwelt.shop.views.init_transaction") + def test_incomplete_setup(self, mock_init_transaction): + # GIVEN + Product.objects.all().delete() + mock_init_transaction.return_value = "1234567890" + + # WHEN + response = self.client.post( + path=reverse("checkout-vv"), + format="json", + data={ + "redirect_url": REDIRECT_URL, + "address": TEST_ADDRESS_DATA, + }, + ) + + # THEN + self.assertEqual(response.status_code, status.HTTP_200_OK) + + expected = ( + f"{REDIRECT_URL}/onboarding/vv/checkout/address?error&" + f"message=vv_product_does_not_exist_needs_to_be_created" + ) + + self.assertEqual(expected, response.json()["next_step_url"]) diff --git a/server/vbv_lernwelt/shop/tests/test_create_signature.py b/server/vbv_lernwelt/shop/tests/test_create_signature.py index 1c6892e5..ac7297d6 100644 --- a/server/vbv_lernwelt/shop/tests/test_create_signature.py +++ b/server/vbv_lernwelt/shop/tests/test_create_signature.py @@ -3,7 +3,7 @@ from unittest import TestCase from vbv_lernwelt.shop.services import is_signature_valid -class DatatransWebhookSigning(TestCase): +class DatatransWebhookSigningTestCase(TestCase): # Key is from their example in the docs, not ours! :D HMAC_KEY_FROM_THE_DOCS_NOT_HAZARDOUS = ( "861bbfc01e089259091927d6ad7f71c8" diff --git a/server/vbv_lernwelt/shop/views.py b/server/vbv_lernwelt/shop/views.py index ee6df6eb..0a5496b3 100644 --- a/server/vbv_lernwelt/shop/views.py +++ b/server/vbv_lernwelt/shop/views.py @@ -107,12 +107,18 @@ def checkout_vv(request): """ sku = VV_PRODUCT_SKU logger.info(f"Checkout requested: sku={sku}", user_id=request.user.id) + base_redirect_url = request.data["redirect_url"] try: product = Product.objects.get(sku=sku) except Product.DoesNotExist: - raise Exception( - f"Required Product not found: {sku} must be created in the admin interface first.", + return JsonResponse( + { + "next_step_url": checkout_error_url( + base_url=base_redirect_url, + message="vv_product_does_not_exist_needs_to_be_created", + ) + }, ) checkouts = CheckoutInformation.objects.filter( @@ -131,7 +137,6 @@ def checkout_vv(request): return JsonResponse({"next_step_url": get_payment_url(checkout.transaction_id)}) # not yet initialized at all, or canceled/failed - base_redirect_url = request.data["redirect_url"] transaction_id = init_transaction( user=request.user, amount_chf_centimes=product.price, @@ -162,8 +167,13 @@ def webhook_url(base_url: str) -> str: return f"{base_url}/api/shop/transaction/webhook/" -def checkout_error_url(base_url: str) -> str: - return f"{base_url}/onboarding/vv/checkout/address?error" +def checkout_error_url(base_url: str, message: str | None = None) -> str: + url = f"{base_url}/onboarding/vv/checkout/address?error" + + if message: + url += f"&message={message}" + + return url def checkout_cancel_url(base_url: str) -> str: