Remove unused BillingAddress model

This commit is contained in:
Daniel Egger 2024-05-30 18:02:46 +02:00
parent 2646b072ee
commit ec21238ece
12 changed files with 218 additions and 366 deletions

View File

@ -4,12 +4,12 @@ import { useEntities } from "@/services/entities";
const props = defineProps<{ const props = defineProps<{
modelValue: { modelValue: {
company_name: string; organisation_detail_name: string;
company_street: string; organisation_street: string;
company_street_number: string; organisation_street_number: string;
company_postal_code: string; organisation_postal_code: string;
company_city: string; organisation_city: string;
company_country: string; organisation_country_code: string;
}; };
}>(); }>();
@ -39,7 +39,7 @@ const orgAddress = computed({
<div class="mt-2"> <div class="mt-2">
<input <input
id="company-name" id="company-name"
v-model="orgAddress.company_name" v-model="orgAddress.organisation_detail_name"
type="text" type="text"
required required
name="company-name" name="company-name"
@ -57,7 +57,7 @@ const orgAddress = computed({
<div class="mt-2"> <div class="mt-2">
<input <input
id="company-street-address" id="company-street-address"
v-model="orgAddress.company_street" v-model="orgAddress.organisation_street"
type="text" type="text"
required required
name="street-address" name="street-address"
@ -77,7 +77,7 @@ const orgAddress = computed({
<div class="mt-2"> <div class="mt-2">
<input <input
id="company-street-number" id="company-street-number"
v-model="orgAddress.company_street_number" v-model="orgAddress.organisation_street_number"
name="street-number" name="street-number"
type="text" type="text"
autocomplete="street-number" autocomplete="street-number"
@ -95,7 +95,7 @@ const orgAddress = computed({
<div class="mt-2"> <div class="mt-2">
<input <input
id="company-postal-code" id="company-postal-code"
v-model="orgAddress.company_postal_code" v-model="orgAddress.organisation_postal_code"
type="text" type="text"
required required
name="postal-code" name="postal-code"
@ -115,7 +115,7 @@ const orgAddress = computed({
<div class="mt-2"> <div class="mt-2">
<input <input
id="company-city" id="company-city"
v-model="orgAddress.company_city" v-model="orgAddress.organisation_city"
type="text" type="text"
name="city" name="city"
required required
@ -135,7 +135,7 @@ const orgAddress = computed({
<div class="mt-2"> <div class="mt-2">
<select <select
id="company-country" id="company-country"
v-model="orgAddress.company_country" v-model="orgAddress.organisation_country_code"
required required
name="country" name="country"
autocomplete="country-name" autocomplete="country-name"

View File

@ -10,7 +10,7 @@ const props = defineProps<{
street_number: string; street_number: string;
postal_code: string; postal_code: string;
city: string; city: string;
country: string; country_code: string;
}; };
}>(); }>();
@ -147,7 +147,7 @@ const address = computed({
<div class="mt-2"> <div class="mt-2">
<select <select
id="country" id="country"
v-model="address.country" v-model="address.country_code"
required required
name="country" name="country"
autocomplete="country-name" autocomplete="country-name"

View File

@ -2,32 +2,15 @@
import WizardPage from "@/components/onboarding/WizardPage.vue"; import WizardPage from "@/components/onboarding/WizardPage.vue";
import type { Ref } from "vue"; import type { Ref } from "vue";
import { computed, ref, watch } from "vue"; import { computed, ref, watch } from "vue";
import { useUserStore } from "@/stores/user"; import { type User, useUserStore } from "@/stores/user";
import PersonalAddress from "@/components/onboarding/PersonalAddress.vue"; import PersonalAddress from "@/components/onboarding/PersonalAddress.vue";
import OrganisationAddress from "@/components/onboarding/OrganisationAddress.vue"; import OrganisationAddress from "@/components/onboarding/OrganisationAddress.vue";
import { itPost, itPut } from "@/fetchHelpers"; import { itPost } from "@/fetchHelpers";
import { useEntities } from "@/services/entities"; import { useEntities } from "@/services/entities";
import { useDebounceFn, useFetch } from "@vueuse/core";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { useTranslation } from "i18next-vue"; import { useTranslation } from "i18next-vue";
import { getVVCourseName } from "./composables"; import { getVVCourseName } from "./composables";
type BillingAddressType = {
first_name: string;
last_name: string;
street: string;
street_number: string;
postal_code: string;
city: string;
country: string;
company_name: string;
company_street: string;
company_street_number: string;
company_postal_code: string;
company_city: string;
company_country: string;
};
const props = defineProps({ const props = defineProps({
courseType: { courseType: {
type: String, type: String,
@ -37,7 +20,7 @@ const props = defineProps({
const user = useUserStore(); const user = useUserStore();
const route = useRoute(); const route = useRoute();
const { organisations } = useEntities(); const { organisations, countries } = useEntities();
const userOrganisationName = computed(() => { const userOrganisationName = computed(() => {
if (!user.organisation) { if (!user.organisation) {
@ -61,56 +44,27 @@ const paymentError = computed(() => {
}); });
const address = ref({ const address = ref({
first_name: "", first_name: user.first_name,
last_name: "", last_name: user.last_name,
street: "", street: user.street,
street_number: "", street_number: user.street_number,
postal_code: "", postal_code: user.postal_code,
city: "", city: user.city,
country: "", country_code: user.country?.country_code ?? "CH",
company_name: "", organisation_detail_name: user.organisation_detail_name,
company_street: "", organisation_street: user.organisation_street,
company_street_number: "", organisation_street_number: user.organisation_street_number,
company_postal_code: "", organisation_postal_code: user.organisation_postal_code,
company_city: "", organisation_city: user.organisation_city,
company_country: "", organisation_country_code: user.organisation_country?.country_code ?? "CH",
invoice_address: user.invoice_address ?? "prv",
}); });
const useCompanyAddress = ref(false); const useCompanyAddress = ref(user.invoice_address === "org");
const fetchBillingAddress = useFetch("/api/shop/billing-address/").json();
const billingAddressData: Ref<BillingAddressType | null> = fetchBillingAddress.data;
watch(billingAddressData, (newVal) => { const setUseCompanyAddress = (value: boolean) => {
if (newVal) { useCompanyAddress.value = value;
address.value = newVal; address.value.invoice_address = value ? "org" : "prv";
useCompanyAddress.value = !!newVal.company_name;
}
});
const updateAddress = useDebounceFn(() => {
itPut("/api/shop/billing-address/update/", address.value);
}, 500);
watch(
address,
(newVal, oldVal) => {
if (Object.values(oldVal).every((x) => x === "")) {
return;
}
updateAddress();
},
{ deep: true }
);
const removeCompanyAddress = () => {
useCompanyAddress.value = false;
address.value.company_name = "";
address.value.company_street = "";
address.value.company_street_number = "";
address.value.company_postal_code = "";
address.value.company_city = "";
address.value.company_country = "";
}; };
type FormErrors = { type FormErrors = {
@ -153,44 +107,59 @@ function validateAddress() {
formErrors.value.personal.push(t("a.Ort")); formErrors.value.personal.push(t("a.Ort"));
} }
if (!address.value.country) { if (!address.value.country_code) {
formErrors.value.personal.push(t("a.Land")); formErrors.value.personal.push(t("a.Land"));
} }
if (useCompanyAddress.value) { if (useCompanyAddress.value) {
if (!address.value.company_name) { if (!address.value.organisation_detail_name) {
formErrors.value.company.push(t("a.Name")); formErrors.value.company.push(t("a.Name"));
} }
if (!address.value.company_street) { if (!address.value.organisation_street) {
formErrors.value.company.push(t("a.Strasse")); formErrors.value.company.push(t("a.Strasse"));
} }
if (!address.value.company_street_number) { if (!address.value.organisation_street_number) {
formErrors.value.company.push(t("a.Hausnummmer")); formErrors.value.company.push(t("a.Hausnummmer"));
} }
if (!address.value.company_postal_code) { if (!address.value.organisation_postal_code) {
formErrors.value.company.push(t("a.PLZ")); formErrors.value.company.push(t("a.PLZ"));
} }
if (!address.value.company_city) { if (!address.value.organisation_city) {
formErrors.value.company.push(t("a.Ort")); formErrors.value.company.push(t("a.Ort"));
} }
if (!address.value.company_country) { if (!address.value.organisation_country_code) {
formErrors.value.company.push(t("a.Land")); formErrors.value.company.push(t("a.Land"));
} }
} }
} }
const executePayment = () => { async function saveAddress() {
validateAddress(); const { country_code, organisation_country_code, ...profileData } = address.value;
const typedProfileData: Partial<User> = { ...profileData };
typedProfileData.country = countries.value.find(
(c) => c.country_code === country_code
);
typedProfileData.organisation_country = countries.value.find(
(c) => c.country_code === organisation_country_code
);
await user.updateUserProfile(typedProfileData);
}
const executePayment = async () => {
validateAddress();
if (formErrors.value.personal.length > 0 || formErrors.value.company.length > 0) { if (formErrors.value.personal.length > 0 || formErrors.value.company.length > 0) {
return; return;
} }
await saveAddress();
// Where the payment page will redirect to after the payment is done: // Where the payment page will redirect to after the payment is done:
// The reason why this is here is convenience: We could also do this in the backend // The reason why this is here is convenience: We could also do this in the backend
// then we'd need to configure this for all environments (including Caprover). // then we'd need to configure this for all environments (including Caprover).
@ -242,6 +211,7 @@ const executePayment = () => {
</p> </p>
<h3 class="mb-4 mt-10">{{ $t("a.Adresse") }}</h3> <h3 class="mb-4 mt-10">{{ $t("a.Adresse") }}</h3>
<pre>{{ address }}</pre>
<p class="mb-2"> <p class="mb-2">
{{ {{
$t( $t(
@ -266,7 +236,7 @@ const executePayment = () => {
<button <button
v-if="!useCompanyAddress" v-if="!useCompanyAddress"
class="underline" class="underline"
@click="useCompanyAddress = true" @click="setUseCompanyAddress(true)"
> >
<template v-if="userOrganisationName"> <template v-if="userOrganisationName">
{{ {{
@ -296,7 +266,7 @@ const executePayment = () => {
}} }}
</h3> </h3>
<h3 v-else>{{ $t("a.Rechnungsadresse") }}</h3> <h3 v-else>{{ $t("a.Rechnungsadresse") }}</h3>
<button class="underline" @click="removeCompanyAddress"> <button class="underline" @click="setUseCompanyAddress(false)">
{{ $t("a.Entfernen") }} {{ $t("a.Entfernen") }}
</button> </button>
</div> </div>

View File

@ -31,16 +31,16 @@ export interface User {
language: AvailableLanguages; language: AvailableLanguages;
course_session_experts: string[]; course_session_experts: string[];
invoice_address: InvoiceAddress | null; invoice_address: InvoiceAddress | null;
street: string | null; street: string;
street_number: string | null; street_number: string;
postal_code: string | null; postal_code: string;
city: string | null; city: string;
country: Country | null; country: Country | null;
organisation_detail_name: string | null; organisation_detail_name: string;
organisation_street: string | null; organisation_street: string;
organisation_street_number: string | null; organisation_street_number: string;
organisation_postal_code: string | null; organisation_postal_code: string;
organisation_city: string | null; organisation_city: string;
organisation_country: Country | null; organisation_country: Country | null;
} }
@ -74,16 +74,16 @@ const initialUserState: User = {
loggedIn: false, loggedIn: false,
language: defaultLanguage, language: defaultLanguage,
invoice_address: "prv", invoice_address: "prv",
street: null, street: "",
street_number: null, street_number: "",
postal_code: null, postal_code: "",
city: null, city: "",
country: null, country: null,
organisation_detail_name: null, organisation_detail_name: "",
organisation_street: null, organisation_street: "",
organisation_street_number: null, organisation_street_number: "",
organisation_postal_code: null, organisation_postal_code: "",
organisation_city: null, organisation_city: "",
organisation_country: null, organisation_country: null,
}; };

View File

@ -77,7 +77,7 @@ class Migration(migrations.Migration):
blank=True, blank=True,
null=True, null=True,
on_delete=models.deletion.SET_NULL, on_delete=models.deletion.SET_NULL,
related_name="user_country", related_name="+",
to="core.country", to="core.country",
), ),
), ),
@ -88,7 +88,7 @@ class Migration(migrations.Migration):
blank=True, blank=True,
null=True, null=True,
on_delete=models.deletion.SET_NULL, on_delete=models.deletion.SET_NULL,
related_name="organisation_country", related_name="+",
to="core.country", to="core.country",
), ),
), ),

View File

@ -92,7 +92,7 @@ class User(AbstractUser):
city = models.CharField(max_length=255, blank=True) city = models.CharField(max_length=255, blank=True)
country = models.ForeignKey( country = models.ForeignKey(
Country, Country,
related_name="user_country", related_name="+",
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
null=True, null=True,
blank=True, blank=True,
@ -105,7 +105,7 @@ class User(AbstractUser):
organisation_city = models.CharField(max_length=255, blank=True) organisation_city = models.CharField(max_length=255, blank=True)
organisation_country = models.ForeignKey( organisation_country = models.ForeignKey(
Country, Country,
related_name="organisation_country", related_name="+",
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
null=True, null=True,
blank=True, blank=True,

View File

@ -3,9 +3,23 @@
from django.db import migrations, models from django.db import migrations, models
def migrate_checkout_information_country(apps, schema_editor):
CheckoutInformation = apps.get_model("shop", "CheckoutInformation")
Country = apps.get_model("core", "Country")
for info in CheckoutInformation.objects.all():
if info.old_country:
country = Country.objects.get(vbv_country_id=info.old_country)
info.country = country
if info.old_company_country:
country = Country.objects.get(vbv_country_id=info.old_company_country)
info.organisation_country = country
info.save(update_fields=["country", "organisation_country"])
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("shop", "0012_delete_country"), ("shop", "0012_delete_country"),
("core", "0009_country_refactor"),
] ]
operations = [ operations = [
@ -14,4 +28,79 @@ class Migration(migrations.Migration):
name="abacus_order_id", name="abacus_order_id",
field=models.BigIntegerField(blank=True, null=True, unique=True), field=models.BigIntegerField(blank=True, null=True, unique=True),
), ),
migrations.RenameField(
model_name="checkoutinformation",
old_name="company_name",
new_name="organisation_detail_name",
),
migrations.RenameField(
model_name="checkoutinformation",
old_name="company_street",
new_name="organisation_street",
),
migrations.RenameField(
model_name="checkoutinformation",
old_name="company_street_number",
new_name="organisation_street_number",
),
migrations.RenameField(
model_name="checkoutinformation",
old_name="company_postal_code",
new_name="organisation_postal_code",
),
migrations.RenameField(
model_name="checkoutinformation",
old_name="company_city",
new_name="organisation_city",
),
migrations.RenameField(
model_name="checkoutinformation",
old_name="country",
new_name="old_country",
),
migrations.RenameField(
model_name="checkoutinformation",
old_name="company_country",
new_name="old_company_country",
),
migrations.AddField(
model_name="checkoutinformation",
name="invoice_address",
field=models.CharField(
choices=[("prv", "Private"), ("org", "Organisation")],
default="prv",
max_length=3,
),
),
migrations.AddField(
model_name="checkoutinformation",
name="country",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=models.deletion.SET_NULL,
related_name="+",
to="core.country",
),
),
migrations.AddField(
model_name="checkoutinformation",
name="organisation_country",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=models.deletion.SET_NULL,
related_name="+",
to="core.country",
),
),
migrations.RunPython(migrate_checkout_information_country),
migrations.RemoveField(
model_name="checkoutinformation",
name="old_country",
),
migrations.RemoveField(
model_name="checkoutinformation",
name="old_company_country",
),
] ]

View File

@ -1,34 +1,7 @@
from django.db import models from django.db import models
from django.db.models import Max from django.db.models import Max
from vbv_lernwelt.core.models import Country
class BillingAddress(models.Model):
"""
Draft of a billing address for a purchase from the shop.
"""
user = models.OneToOneField(
"core.User",
on_delete=models.CASCADE,
primary_key=True,
)
# user
first_name = models.CharField(max_length=255, blank=True)
last_name = models.CharField(max_length=255, blank=True)
street = models.CharField(max_length=255, blank=True)
street_number = models.CharField(max_length=255, blank=True)
postal_code = models.CharField(max_length=255, blank=True)
city = models.CharField(max_length=255, blank=True)
country = models.CharField(max_length=255, blank=True)
# company (optional)
company_name = models.CharField(max_length=255, blank=True)
company_street = models.CharField(max_length=255, blank=True)
company_street_number = models.CharField(max_length=255, blank=True)
company_postal_code = models.CharField(max_length=255, blank=True)
company_city = models.CharField(max_length=255, blank=True)
company_country = models.CharField(max_length=255, blank=True)
class Product(models.Model): class Product(models.Model):
@ -62,6 +35,14 @@ class CheckoutState(models.TextChoices):
class CheckoutInformation(models.Model): class CheckoutInformation(models.Model):
INVOICE_ADDRESS_PRIVATE = "prv"
INVOICE_ADDRESS_ORGANISATION = "org"
INVOICE_ADDRESS_CHOICES = (
(INVOICE_ADDRESS_PRIVATE, "Private"),
(INVOICE_ADDRESS_ORGANISATION, "Organisation"),
)
user = models.ForeignKey("core.User", on_delete=models.PROTECT) user = models.ForeignKey("core.User", on_delete=models.PROTECT)
product_sku = models.CharField(max_length=255) product_sku = models.CharField(max_length=255)
@ -89,15 +70,31 @@ class CheckoutInformation(models.Model):
street_number = models.CharField(max_length=255) street_number = models.CharField(max_length=255)
postal_code = models.CharField(max_length=255) postal_code = models.CharField(max_length=255)
city = models.CharField(max_length=255) city = models.CharField(max_length=255)
country = models.CharField(max_length=255) country = models.ForeignKey(
Country,
related_name="+",
on_delete=models.SET_NULL,
null=True,
blank=True,
)
# company (optional) invoice_address = models.CharField(
company_name = models.CharField(max_length=255, blank=True) max_length=3, choices=INVOICE_ADDRESS_CHOICES, default="prv"
company_street = models.CharField(max_length=255, blank=True) )
company_street_number = models.CharField(max_length=255, blank=True)
company_postal_code = models.CharField(max_length=255, blank=True) # organisation data (optional)
company_city = models.CharField(max_length=255, blank=True) organisation_detail_name = models.CharField(max_length=255, blank=True)
company_country = models.CharField(max_length=255, blank=True) organisation_street = models.CharField(max_length=255, blank=True)
organisation_street_number = models.CharField(max_length=255, blank=True)
organisation_postal_code = models.CharField(max_length=255, blank=True)
organisation_city = models.CharField(max_length=255, blank=True)
organisation_country = models.ForeignKey(
Country,
related_name="+",
on_delete=models.SET_NULL,
null=True,
blank=True,
)
# webhook metadata # webhook metadata
webhook_history = models.JSONField(default=list) webhook_history = models.JSONField(default=list)

View File

@ -1,23 +1 @@
from rest_framework import serializers
from .models import BillingAddress
class BillingAddressSerializer(serializers.ModelSerializer):
class Meta:
model = BillingAddress
fields = [
"first_name",
"last_name",
"street",
"street_number",
"postal_code",
"city",
"country",
"company_name",
"company_street",
"company_street_number",
"company_postal_code",
"company_city",
"company_country",
]

View File

@ -1,106 +0,0 @@
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from vbv_lernwelt.core.admin import User
from vbv_lernwelt.shop.models import BillingAddress
class BillingAddressViewTest(APITestCase):
def setUp(self) -> None:
self.user = User.objects.create_user(
"testuser", "test@example.com", "testpassword"
)
self.client.login(username="testuser", password="testpassword")
self.billing_address = BillingAddress.objects.create(
user=self.user,
first_name="John",
last_name="Doe",
street="123 Main St",
street_number="45A",
postal_code="12345",
city="Test City",
country="Test Country",
company_name="Test Company",
company_street="456 Company St",
company_street_number="67B",
company_postal_code="67890",
company_city="Company City",
company_country="Company Country",
)
def test_get_billing_address(self) -> None:
# GIVEN
# user is logged in and has a billing address
# WHEN
url = reverse("get-billing-address")
response = self.client.get(url)
# THEN
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["first_name"], "John")
self.assertEqual(response.data["last_name"], "Doe")
self.assertEqual(response.data["street"], "123 Main St")
self.assertEqual(response.data["street_number"], "45A")
self.assertEqual(response.data["postal_code"], "12345")
self.assertEqual(response.data["city"], "Test City")
self.assertEqual(response.data["country"], "Test Country")
self.assertEqual(response.data["company_name"], "Test Company")
self.assertEqual(response.data["company_street"], "456 Company St")
self.assertEqual(response.data["company_street_number"], "67B")
self.assertEqual(response.data["company_postal_code"], "67890")
self.assertEqual(response.data["company_city"], "Company City")
self.assertEqual(response.data["company_country"], "Company Country")
def test_update_billing_address(self) -> None:
# GIVEN
new_data = {
"first_name": "Jane",
"last_name": "Smith",
"street": "789 New St",
"street_number": "101C",
"postal_code": "54321",
"city": "New City",
"country": "New Country",
"company_name": "New Company",
"company_street": "789 Company St",
"company_street_number": "102D",
"company_postal_code": "98765",
"company_city": "New Company City",
"company_country": "New Company Country",
}
# WHEN
url = reverse("update-billing-address")
response = self.client.put(url, new_data)
# THEN
self.assertEqual(response.status_code, status.HTTP_200_OK)
updated_address = BillingAddress.objects.get(user=self.user)
self.assertEqual(updated_address.first_name, "Jane")
self.assertEqual(updated_address.last_name, "Smith")
self.assertEqual(updated_address.street, "789 New St")
self.assertEqual(updated_address.street_number, "101C")
self.assertEqual(updated_address.postal_code, "54321")
self.assertEqual(updated_address.city, "New City")
self.assertEqual(updated_address.country, "New Country")
self.assertEqual(updated_address.company_name, "New Company")
self.assertEqual(updated_address.company_street, "789 Company St")
self.assertEqual(updated_address.company_street_number, "102D")
self.assertEqual(updated_address.company_postal_code, "98765")
self.assertEqual(updated_address.company_city, "New Company City")
self.assertEqual(updated_address.company_country, "New Company Country")
def test_unauthenticated_access(self) -> None:
# GIVEN
self.client.logout()
# WHEN
get_response = self.client.get(reverse("get-billing-address"))
put_response = self.client.put(reverse("update-billing-address"), {})
# THEN
self.assertTrue(get_response["Location"], "/login/")
self.assertTrue(put_response["Location"], "/login/")

View File

@ -1,18 +1,9 @@
from django.urls import path from django.urls import path
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
from vbv_lernwelt.shop.views import ( from vbv_lernwelt.shop.views import checkout_vv, transaction_webhook
checkout_vv,
get_billing_address,
transaction_webhook,
update_billing_address,
)
urlpatterns = [ urlpatterns = [
path("billing-address/", get_billing_address, name="get-billing-address"),
path(
"billing-address/update/", update_billing_address, name="update-billing-address"
),
path("vv/checkout/", checkout_vv, name="checkout-vv"), path("vv/checkout/", checkout_vv, name="checkout-vv"),
path( path(
"transaction/webhook/", "transaction/webhook/",

View File

@ -7,7 +7,6 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from sentry_sdk import capture_exception from sentry_sdk import capture_exception
from vbv_lernwelt.core.models import Country, User
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
from vbv_lernwelt.notify.email.email_services import EmailTemplate, send_email from vbv_lernwelt.notify.email.email_services import EmailTemplate, send_email
from vbv_lernwelt.shop.const import ( from vbv_lernwelt.shop.const import (
@ -15,13 +14,7 @@ from vbv_lernwelt.shop.const import (
VV_FR_PRODUCT_SKU, VV_FR_PRODUCT_SKU,
VV_IT_PRODUCT_SKU, VV_IT_PRODUCT_SKU,
) )
from vbv_lernwelt.shop.models import ( from vbv_lernwelt.shop.models import CheckoutInformation, CheckoutState, Product
BillingAddress,
CheckoutInformation,
CheckoutState,
Product,
)
from vbv_lernwelt.shop.serializers import BillingAddressSerializer
from vbv_lernwelt.shop.services import ( from vbv_lernwelt.shop.services import (
datatrans_state_to_checkout_state, datatrans_state_to_checkout_state,
get_payment_url, get_payment_url,
@ -40,39 +33,6 @@ PRODUCT_SKU_TO_COURSE_SESSION_ID = {
} }
@api_view(["GET"])
@permission_classes([IsAuthenticated])
def get_billing_address(request):
try:
billing_address = BillingAddress.objects.get(user=request.user)
data = BillingAddressSerializer(billing_address).data
except BillingAddress.DoesNotExist:
data = BillingAddressSerializer().data
data["first_name"] = request.user.first_name # noqa
data["last_name"] = request.user.last_name # noqa
return Response(data)
@api_view(["PUT"])
@permission_classes([IsAuthenticated])
def update_billing_address(request):
try:
billing_address = BillingAddress.objects.get(user=request.user)
except BillingAddress.DoesNotExist:
billing_address = None
serializer = BillingAddressSerializer(
billing_address, data=request.data, partial=True
)
if serializer.is_valid():
serializer.save(user=request.user)
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(["POST"]) @api_view(["POST"])
def transaction_webhook(request): def transaction_webhook(request):
"""IMPORTANT: This is not called for timed out transactions!""" """IMPORTANT: This is not called for timed out transactions!"""
@ -125,7 +85,7 @@ def checkout_vv(request):
sku = request.data["product"] sku = request.data["product"]
base_redirect_url = request.data["redirect_url"] base_redirect_url = request.data["redirect_url"]
logger.info(f"Checkout requested: sku={sku}", user_id=request.user.id) logger.info(f"Checkout requested: sku", user_id=request.user.id, sku=sku)
try: try:
product = Product.objects.get(sku=sku) product = Product.objects.get(sku=sku)
@ -171,6 +131,15 @@ 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
checkout_info = CheckoutInformation.objects.create( checkout_info = CheckoutInformation.objects.create(
user=request.user, user=request.user,
state=CheckoutState.ONGOING, state=CheckoutState.ONGOING,
@ -184,8 +153,6 @@ def checkout_vv(request):
**request.data["address"], **request.data["address"],
) )
update_user_address(user=request.user, checkout_info=checkout_info)
return next_step_response(url=get_payment_url(transaction_id)) return next_step_response(url=get_payment_url(transaction_id))
@ -266,37 +233,3 @@ def checkout_cancel_url(base_url: str) -> str:
def checkout_success_url(product_sku: str, base_url: str = "") -> str: def checkout_success_url(product_sku: str, base_url: str = "") -> str:
return f"{base_url}/onboarding/{product_sku}/checkout/complete" return f"{base_url}/onboarding/{product_sku}/checkout/complete"
def update_user_address(user: User, checkout_info: CheckoutInformation):
user.street = checkout_info.street
user.street_number = checkout_info.street_number
user.postal_code = checkout_info.postal_code
user.city = checkout_info.city
if checkout_info.country:
user.country = Country.objects.filter(
country_code=checkout_info.country
).first()
if (
checkout_info.company_name
and checkout_info.company_street
and checkout_info.company_street_number
and checkout_info.company_postal_code
and checkout_info.company_city
and checkout_info.company_country
):
user.organisation_detail_name = checkout_info.company_name
user.organisation_street = checkout_info.company_street
user.organisation_street_number = checkout_info.company_street_number
user.organisation_postal_code = checkout_info.company_postal_code
user.organisation_city = checkout_info.company_city
user.organisation_country = Country.objects.filter(
country_code=checkout_info.company_country
).first()
user.invoice_address = User.INVOICE_ADDRESS_ORGANISATION
user.save()