diff --git a/caprover_create_app.py b/caprover_create_app.py index 24dba123..e137bcf1 100644 --- a/caprover_create_app.py +++ b/caprover_create_app.py @@ -93,8 +93,12 @@ def main(app_name, image_name, environment_file): "AWS_S3_SECRET_ACCESS_KEY": env.str("AWS_S3_SECRET_ACCESS_KEY", ""), "AWS_S3_REGION_NAME": "eu-central-1", "AWS_STORAGE_BUCKET_NAME": "myvbv-dev.iterativ.ch", - "DATATRANS_HMAC_KEY": env.str("DATATRANS_HMAC_KEY", ""), - "DATATRANS_BASIC_AUTH_KEY": env.str("DATATRANS_BASIC_AUTH_KEY", ""), + "DATATRANS_HMAC_KEY": env.str("PIPELINES_DATATRANS_HMAC_KEY", ""), + "DATATRANS_BASIC_AUTH_KEY": env.str( + "PIPELINES_DATATRANS_BASIC_AUTH_KEY", "" + ), + "DATATRANS_API_ENDPOINT": "https://api.sandbox.datatrans.com", + "DATATRANS_PAY_URL": "https://pay.sandbox.datatrans.com", "FILE_UPLOAD_STORAGE": "s3", "IT_DJANGO_DEBUG": "false", "IT_SERVE_VUE": "false", diff --git a/client/src/components/onboarding/PersonalAddress.vue b/client/src/components/onboarding/PersonalAddress.vue index c4fcbeea..3425afeb 100644 --- a/client/src/components/onboarding/PersonalAddress.vue +++ b/client/src/components/onboarding/PersonalAddress.vue @@ -30,13 +30,6 @@ const paymentMethods = [ { value: "cembra_byjuno", label: t("a.Rechnung") }, ]; -// TODO: remove after cembra is ready for production -const appEnv = import.meta.env.VITE_APP_ENVIRONMENT || "local"; -if (appEnv.startsWith("prod")) { - paymentMethods.splice(1, 1); -} -// END TODO - const address = computed({ get() { return props.modelValue; diff --git a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue index d6d79516..48ae62a3 100644 --- a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue +++ b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue @@ -51,10 +51,6 @@ const submissionDeadline = computed(() => { ?.submission_deadline; }); -// FIXME daniel: `useRouteQuery` from usevue is currently the reason that we have to -// fix the version of @vueuse/router and @vueuse/core to 10.1.0 -// it fails with version 10.2.0. I have a reminder to check out the situation -// at the end of July 2023 // 0 = introduction, 1 - n = tasks, n+1 = submission const stepIndex = useRouteQuery("step", "0", { transform: Number, mode: "push" }); diff --git a/env_secrets/caprover_vbv-develop.env b/env_secrets/caprover_vbv-develop.env index c633f417..5432d3c8 100644 Binary files a/env_secrets/caprover_vbv-develop.env and b/env_secrets/caprover_vbv-develop.env differ diff --git a/env_secrets/local_daniel.env b/env_secrets/local_daniel.env index 7ae4f656..703bd5e9 100644 Binary files a/env_secrets/local_daniel.env and b/env_secrets/local_daniel.env differ diff --git a/server/config/settings/base.py b/server/config/settings/base.py index a4fda6fd..031cb063 100644 --- a/server/config/settings/base.py +++ b/server/config/settings/base.py @@ -332,7 +332,6 @@ X_FRAME_OPTIONS = "DENY" # EMAIL # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend -# FIXME how to send emails? EMAIL_BACKEND = env( "DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend" ) @@ -682,10 +681,12 @@ if APP_ENVIRONMENT.startswith("prod"): DATATRANS_PAY_URL = "https://pay.datatrans.com" else: DATATRANS_API_ENDPOINT = env( - "DATATRANS_API_ENDPOINT", default="https://api.sandbox.datatrans.com" + "DATATRANS_API_ENDPOINT", + default="http://localhost:8000/server/fakeapi/datatrans/api", ) DATATRANS_PAY_URL = env( - "DATATRANS_PAY_URL", default="https://pay.sandbox.datatrans.com" + "DATATRANS_PAY_URL", + default="http://localhost:8000/server/fakeapi/datatrans/pay", ) # default settings for python sftpserver test-server diff --git a/server/vbv_lernwelt/core/constants.py b/server/vbv_lernwelt/core/constants.py index 75f24848..e36702b8 100644 --- a/server/vbv_lernwelt/core/constants.py +++ b/server/vbv_lernwelt/core/constants.py @@ -28,6 +28,7 @@ TEST_MENTOR1_USER_ID = "d1f5f5a9-5b0a-4e1a-9e1a-9e9b5b5e1b1b" TEST_STUDENT1_VV_USER_ID = "5ff59857-8de5-415e-a387-4449f9a0337a" TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID = "7e8ebf0b-e6e2-4022-88f4-6e663ba0a9db" TEST_USER_EMPTY_ID = "daecbabe-4ab9-4edf-a71f-4119042ccb02" +TEST_USER_DATATRANS_HANNA_ID = "6bec1a0d-f852-47aa-a4de-072df6e07ad1" TEST_COURSE_SESSION_BERN_ID = -1 TEST_COURSE_SESSION_ZURICH_ID = -2 diff --git a/server/vbv_lernwelt/core/create_default_users.py b/server/vbv_lernwelt/core/create_default_users.py index 17acd036..beeaca53 100644 --- a/server/vbv_lernwelt/core/create_default_users.py +++ b/server/vbv_lernwelt/core/create_default_users.py @@ -4,6 +4,7 @@ from django.contrib.auth.models import Group, Permission from django.core.files import File from environs import Env +from vbv_lernwelt.core.model_utils import add_countries from vbv_lernwelt.media_files.models import UserImage env = Env() @@ -20,6 +21,7 @@ from vbv_lernwelt.core.constants import ( TEST_SUPERVISOR1_USER_ID, TEST_TRAINER1_USER_ID, TEST_TRAINER2_USER_ID, + TEST_USER_DATATRANS_HANNA_ID, TEST_USER_EMPTY_ID, ) from vbv_lernwelt.core.models import User @@ -78,7 +80,30 @@ default_users = [ AVATAR_DIR = settings.APPS_DIR / "static" / "avatars" +def create_datatrans_hanna_user(): + hanna, _ = User.objects.get_or_create( + id=TEST_USER_DATATRANS_HANNA_ID, + ) + hanna.username = "datatrans.hanna.vbv@example.com" + hanna.email = "datatrans.hanna.vbv@example.com" + hanna.language = "de" + hanna.first_name = "Hanna" + hanna.last_name = "Vbv" + hanna.street = "Bahnstrasse" + hanna.street_number = "2" + hanna.postal_code = "8603" + hanna.city = "Schwerzenbach" + hanna.country_id = "CH" + hanna.birth_date = "1970-01-01" + hanna.phone_number = "+41792018586" + hanna.password = make_password("test") + hanna.save() + return hanna + + def create_default_users(default_password="test", set_avatar=False): + add_countries(small_set=True) + admin_group, created = Group.objects.get_or_create(name="admin_group") _content_creator_group, _created = Group.objects.get_or_create( name="content_creator_grop" @@ -202,6 +227,8 @@ def create_default_users(default_password="test", set_avatar=False): language="de", ) + hanna = create_datatrans_hanna_user() + for user_data in default_users: _create_student_user(**user_data) diff --git a/server/vbv_lernwelt/core/management/commands/cypress_reset.py b/server/vbv_lernwelt/core/management/commands/cypress_reset.py index abfae404..1a02a343 100644 --- a/server/vbv_lernwelt/core/management/commands/cypress_reset.py +++ b/server/vbv_lernwelt/core/management/commands/cypress_reset.py @@ -17,8 +17,10 @@ from vbv_lernwelt.core.constants import ( TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID, TEST_STUDENT3_USER_ID, TEST_TRAINER1_USER_ID, + TEST_USER_DATATRANS_HANNA_ID, TEST_USER_EMPTY_ID, ) +from vbv_lernwelt.core.create_default_users import create_datatrans_hanna_user from vbv_lernwelt.core.models import Organisation, User from vbv_lernwelt.course.consts import ( COURSE_TEST_ID, @@ -159,6 +161,9 @@ def command( password=make_password("test"), ) + User.objects.filter(id=TEST_USER_DATATRANS_HANNA_ID).delete() + create_datatrans_hanna_user() + cursor = connection.cursor() cursor.execute("truncate core_securityrequestresponselog;") cursor.execute("truncate core_externalapirequestlog;") diff --git a/server/vbv_lernwelt/shop/services.py b/server/vbv_lernwelt/shop/services.py index f5998d38..0e7bd7b3 100644 --- a/server/vbv_lernwelt/shop/services.py +++ b/server/vbv_lernwelt/shop/services.py @@ -1,13 +1,11 @@ import hashlib import hmac -import uuid import requests import structlog from django.conf import settings from vbv_lernwelt.core.admin import User -from vbv_lernwelt.shop.const import VV_PRODUCT_NUMBER from vbv_lernwelt.shop.datatrans.datatrans_api_client import DatatransApiClient from vbv_lernwelt.shop.models import CheckoutState @@ -73,6 +71,7 @@ def is_signature_valid( def init_datatrans_transaction( user: User, + refno: str, amount_chf_centimes: int, redirect_url_success: str, redirect_url_error: str, @@ -91,8 +90,8 @@ def init_datatrans_transaction( "amount": amount_chf_centimes, "currency": "CHF", "language": user.language, - "refno": str(uuid.uuid4()), - "refno2": refno2, + "refno": str(refno), + "refno2": str(refno2), "webhook": {"url": webhook_url}, "redirect": { "successUrl": redirect_url_success, @@ -101,9 +100,8 @@ def init_datatrans_transaction( }, } - # FIXME: test with working cembra byjuno invoice customer? - # if with_cembra_byjuno_invoice: - # payload["paymentMethods"] = ["INT"] + if with_cembra_byjuno_invoice: + payload["paymentMethods"] = ["INT"] if datatrans_customer_data: payload["customer"] = datatrans_customer_data if datatrans_int_data: diff --git a/server/vbv_lernwelt/shop/tests/test_checkout_api.py b/server/vbv_lernwelt/shop/tests/test_checkout_api.py index efbee5da..f5f46b37 100644 --- a/server/vbv_lernwelt/shop/tests/test_checkout_api.py +++ b/server/vbv_lernwelt/shop/tests/test_checkout_api.py @@ -71,9 +71,8 @@ class CheckoutAPITestCase(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"], + self.assertTrue( + response.json()["next_step_url"].endswith("v1/start/1234567890") ) ci = CheckoutInformation.objects.first() @@ -154,9 +153,11 @@ class CheckoutAPITestCase(APITestCase): ) self.assertEqual( - 0, + 1, CheckoutInformation.objects.count(), ) + ci = CheckoutInformation.objects.first() + self.assertEqual(ci.state, CheckoutState.FAILED) def test_checkout_already_paid(self): # GIVEN @@ -217,9 +218,8 @@ class CheckoutAPITestCase(APITestCase): # THEN self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id_next}", - response.json()["next_step_url"], + self.assertTrue( + response.json()["next_step_url"].endswith(f"v1/start/{transaction_id_next}") ) # check that we have two checkouts @@ -277,9 +277,8 @@ class CheckoutAPITestCase(APITestCase): # THEN self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id}", - response.json()["next_step_url"], + self.assertTrue( + response.json()["next_step_url"].endswith(f"v1/start/{transaction_id}") ) @patch("vbv_lernwelt.shop.views.init_datatrans_transaction") @@ -310,7 +309,6 @@ class CheckoutAPITestCase(APITestCase): # THEN self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id}", - response.json()["next_step_url"], + self.assertTrue( + response.json()["next_step_url"].endswith(f"v1/start/{transaction_id}") ) diff --git a/server/vbv_lernwelt/shop/tests/test_datatrans_service.py b/server/vbv_lernwelt/shop/tests/test_datatrans_service.py index 6dc907fe..7f22c8ee 100644 --- a/server/vbv_lernwelt/shop/tests/test_datatrans_service.py +++ b/server/vbv_lernwelt/shop/tests/test_datatrans_service.py @@ -24,10 +24,8 @@ class DatatransServiceTest(TestCase): @override_settings(DATATRANS_BASIC_AUTH_KEY="BASIC_AUTH_KEY") @patch("vbv_lernwelt.shop.services.requests.post") - @patch("vbv_lernwelt.shop.services.uuid.uuid4") - def test_init_transaction_201(self, mock_uuid, mock_post): + def test_init_transaction_201(self, mock_post): # GIVEN - mock_uuid.return_value = uuid.uuid4() mock_post.return_value.status_code = 201 mock_post.return_value.json.return_value = { "transactionId": 1234567890, @@ -38,6 +36,7 @@ class DatatransServiceTest(TestCase): # WHEN transaction_id = init_datatrans_transaction( user=self.user, + refno="123321", amount_chf_centimes=324_30, redirect_url_success=f"{REDIRECT_URL}/success", redirect_url_error=f"{REDIRECT_URL}/error", @@ -64,11 +63,12 @@ class DatatransServiceTest(TestCase): with self.assertRaises(InitTransactionException): init_datatrans_transaction( user=self.user, + refno="123321", amount_chf_centimes=324_30, - redirect_url_success=f"/success", - redirect_url_error=f"/error", - redirect_url_cancel=f"/cancel", - webhook_url=f"/webhook", + redirect_url_success="/success", + redirect_url_error="/error", + redirect_url_cancel="/cancel", + webhook_url="/webhook", refno2="", ) @@ -80,7 +80,4 @@ class DatatransServiceTest(TestCase): url = get_payment_url(transaction_id) # THEN - self.assertEqual( - url, - f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id}", - ) + self.assertTrue(url.endswith(f"v1/start/{transaction_id}")) diff --git a/server/vbv_lernwelt/shop/views.py b/server/vbv_lernwelt/shop/views.py index 1ce904a3..a245520a 100644 --- a/server/vbv_lernwelt/shop/views.py +++ b/server/vbv_lernwelt/shop/views.py @@ -93,7 +93,7 @@ def checkout_vv(request): sku = request.data["product"] base_redirect_url = request.data["redirect_url"] - log.info(f"Checkout requested: sku", user_id=request.user.id, sku=sku) + log.info("Checkout requested: sku", user_id=request.user.id, sku=sku) try: product = Product.objects.get(sku=sku) @@ -124,6 +124,38 @@ def checkout_vv(request): disable_save="fakeapi" in settings.DATATRANS_API_ENDPOINT ) + address_data = request.data["address"] + country_code = address_data.pop("country_code") + address_data["country_id"] = country_code + + organisation_country_code = "CH" + if "organisation_country_code" in address_data: + organisation_country_code = address_data.pop("organisation_country_code") + address_data["organisation_country_id"] = organisation_country_code + + if "birth_date" in address_data and address_data["birth_date"]: + address_data["birth_date"] = date.fromisoformat(address_data["birth_date"]) + + checkout_info = CheckoutInformation.objects.create( + user=request.user, + state=CheckoutState.ONGOING, + # product + product_sku=sku, + product_price=product.price, + product_name=product.name, + product_description=product.description, + email=email, + ip_address=ip_address, + cembra_byjuno_invoice=with_cembra_byjuno_invoice, + device_fingerprint_session_key=request.data.get( + "device_fingerprint_session_key", "" + ), + # address + **request.data["address"], + ) + + checkout_info.set_increment_abacus_order_id() + refno2 = f"{request.user.abacus_debitor_number}_{VV_PRODUCT_NUMBER}" try: @@ -138,10 +170,10 @@ def checkout_vv(request): "street": f'{request.data["address"]["street"]} {request.data["address"]["street_number"]}', "city": request.data["address"]["city"], "zipCode": request.data["address"]["postal_code"], - "country": request.data["address"]["country_code"], + "country": request.data["address"]["country_id"], "phone": request.data["address"]["phone_number"], "email": email, - "birthDate": request.data["address"]["birth_date"], + "birthDate": str(request.data["address"]["birth_date"]), "language": request.user.language, "ipAddress": ip_address, "type": "P", @@ -154,6 +186,7 @@ def checkout_vv(request): } transaction_id = init_datatrans_transaction( user=request.user, + refno=str(checkout_info.abacus_order_id), amount_chf_centimes=product.price, redirect_url_success=checkout_success_url( base_url=base_redirect_url, product_sku=sku @@ -170,6 +203,8 @@ def checkout_vv(request): with_cembra_byjuno_invoice=with_cembra_byjuno_invoice, ) except InitTransactionException as e: + checkout_info.state = CheckoutState.FAILED.value + checkout_info.save() if not settings.DEBUG: log.error("Transaction initiation failed", exc_info=True, error=str(e)) capture_exception(e) @@ -180,36 +215,8 @@ def checkout_vv(request): ), ) - address_data = request.data["address"] - country_code = address_data.pop("country_code") - address_data["country_id"] = country_code - - organisation_country_code = "CH" - if "organisation_country_code" in address_data: - organisation_country_code = address_data.pop("organisation_country_code") - address_data["organisation_country_id"] = organisation_country_code - - if "birth_date" in address_data and address_data["birth_date"]: - address_data["birth_date"] = date.fromisoformat(address_data["birth_date"]) - - checkout_info = CheckoutInformation.objects.create( - user=request.user, - state=CheckoutState.ONGOING, - transaction_id=transaction_id, - # product - product_sku=sku, - product_price=product.price, - product_name=product.name, - product_description=product.description, - email=email, - ip_address=ip_address, - cembra_byjuno_invoice=with_cembra_byjuno_invoice, - device_fingerprint_session_key=request.data.get( - "device_fingerprint_session_key", "" - ), - # address - **request.data["address"], - ) + checkout_info.transaction_id = transaction_id + checkout_info.save() return next_step_response(url=get_payment_url(transaction_id))