vbv/client/src/pages/onboarding/vv/CheckoutAddress.vue

371 lines
11 KiB
Vue

<script setup lang="ts">
import WizardPage from "@/components/onboarding/WizardPage.vue";
import { computed, ref, watch } from "vue";
import { type User, useUserStore } from "@/stores/user";
import PersonalAddress from "@/components/onboarding/PersonalAddress.vue";
import OrganisationAddress from "@/components/onboarding/OrganisationAddress.vue";
import { itPost } from "@/fetchHelpers";
import { useEntities } from "@/services/entities";
import { useRoute } from "vue-router";
import { useTranslation } from "i18next-vue";
import { getVVCourseName } from "./composables";
import DatatransCembraDeviceFingerprint from "@/components/onboarding/DatatransCembraDeviceFingerprint.vue";
import { getLocalSessionKey } from "@/statistics";
import log from "loglevel";
import { normalizeSwissPhoneNumber, validatePhoneNumber } from "@/utils/phone";
import {
ORGANISATION_NO_COMPANY_ID,
ORGANISATION_OTHER_BROKER_ID,
ORGANISATION_OTHER_HEALTH_INSURANCE_ID,
ORGANISATION_OTHER_PRIVATE_INSURANCE_ID,
} from "@/consts";
const props = defineProps({
courseType: {
type: String,
required: true,
},
});
const user = useUserStore();
const route = useRoute();
const { organisations, countries } = useEntities();
const userOrganisationName = computed(() => {
if (!user.organisation) {
return null;
}
// Those IDs do not represent a company
if (
[
ORGANISATION_OTHER_BROKER_ID,
ORGANISATION_OTHER_HEALTH_INSURANCE_ID,
ORGANISATION_OTHER_PRIVATE_INSURANCE_ID,
ORGANISATION_NO_COMPANY_ID,
].includes(user.organisation)
) {
return null;
}
return organisations.value?.find((c) => c.id === user.organisation)?.name;
});
const paymentError = computed(() => {
return "error" in route.query;
});
const address = ref({
first_name: user.first_name,
last_name: user.last_name,
street: user.street,
street_number: user.street_number,
postal_code: user.postal_code,
city: user.city,
country_code: user.country?.country_code ?? "CH",
payment_method: "credit_card",
phone_number: user.phone_number,
birth_date: user.birth_date,
organisation_detail_name: user.organisation_detail_name,
organisation_street: user.organisation_street,
organisation_street_number: user.organisation_street_number,
organisation_postal_code: user.organisation_postal_code,
organisation_city: user.organisation_city,
organisation_country_code: user.organisation_country?.country_code ?? "CH",
invoice_address: user.invoice_address ?? "prv",
});
const withCompanyAddress = ref(user.invoice_address === "org");
const setWithCompanyAddress = (value: boolean) => {
withCompanyAddress.value = value;
address.value.invoice_address = value ? "org" : "prv";
};
watch(
() => address.value.payment_method,
(newValue) => {
if (newValue === "cembra_byjuno") {
setWithCompanyAddress(false);
}
}
);
type FormErrors = {
personal: string[];
company: string[];
};
const formErrors = ref<FormErrors>({
personal: [],
company: [],
});
const { t } = useTranslation();
function validateAddress() {
formErrors.value.personal = [];
formErrors.value.company = [];
if (!address.value.first_name) {
formErrors.value.personal.push(t("a.Vorname"));
}
if (!address.value.last_name) {
formErrors.value.personal.push(t("a.Nachname"));
}
if (!address.value.street) {
formErrors.value.personal.push(t("a.Strasse"));
}
if (!address.value.street_number) {
formErrors.value.personal.push(t("a.Hausnummmer"));
}
if (!address.value.postal_code) {
formErrors.value.personal.push(t("a.PLZ"));
}
if (!address.value.city) {
formErrors.value.personal.push(t("a.Ort"));
}
if (!address.value.country_code) {
formErrors.value.personal.push(t("a.Land"));
}
if (address.value.phone_number) {
const normalizedPhoneNumber = normalizeSwissPhoneNumber(address.value.phone_number);
log.debug("normalizedPhoneNumber", normalizedPhoneNumber);
if (!validatePhoneNumber(normalizedPhoneNumber)) {
formErrors.value.personal.push(t("a.Telefonnummer hat das falsche Format"));
}
}
if (address.value.payment_method === "cembra_byjuno") {
if (!address.value.phone_number) {
formErrors.value.personal.push(t("a.Telefonnummer"));
}
if (!address.value.birth_date) {
formErrors.value.personal.push(t("a.Geburtsdatum"));
}
}
if (withCompanyAddress.value) {
if (!address.value.organisation_detail_name) {
formErrors.value.company.push(t("a.Name"));
}
if (!address.value.organisation_street) {
formErrors.value.company.push(t("a.Strasse"));
}
if (!address.value.organisation_street_number) {
formErrors.value.company.push(t("a.Hausnummmer"));
}
if (!address.value.organisation_postal_code) {
formErrors.value.company.push(t("a.PLZ"));
}
if (!address.value.organisation_city) {
formErrors.value.company.push(t("a.Ort"));
}
if (!address.value.organisation_country_code) {
formErrors.value.company.push(t("a.Land"));
}
}
}
async function saveAddress() {
const { country_code, organisation_country_code, phone_number, ...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
);
if (phone_number) {
typedProfileData.phone_number = normalizeSwissPhoneNumber(phone_number);
}
await user.updateUserProfile(typedProfileData);
}
const executePayment = async () => {
validateAddress();
if (formErrors.value.personal.length > 0 || formErrors.value.company.length > 0) {
return;
}
await saveAddress();
// 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
// then we'd need to configure this for all environments (including Caprover).
// /server/transactions/redirect?... will just redirect to the frontend to the right page
// anyway, so it seems fine to do it here.
const fullHost = `${window.location.protocol}//${window.location.host}`;
if (address.value.phone_number) {
address.value.phone_number = normalizeSwissPhoneNumber(address.value.phone_number);
}
const addressData = Object.assign({}, address.value);
// @ts-ignore
delete addressData.payment_method;
itPost("/api/shop/vv/checkout/", {
redirect_url: fullHost,
address: addressData,
product: props.courseType,
with_cembra_byjuno_invoice: address.value.payment_method === "cembra_byjuno",
device_fingerprint_session_key: getLocalSessionKey(),
}).then((res: any) => {
console.log("Going to next page", res.next_step_url);
window.location.href = res.next_step_url;
});
};
</script>
<template>
<WizardPage :step="2">
<template #content>
<DatatransCembraDeviceFingerprint
v-if="address.payment_method === 'cembra_byjuno'"
/>
<h2 class="my-10" data-cy="account-checkout-title">
{{ $t("a.Lehrgang kaufen") }}
</h2>
<p class="mb-4">
<i18next
:translation="$t('a.Der Preis für den Lehrgang {course} beträgt {price}.')"
>
<template #course>
<b>«{{ getVVCourseName(props.courseType, $t) }}»</b>
</template>
<template #price>
<b class="whitespace-nowrap">300 CHF</b>
</template>
</i18next>
{{
$t("a.Mit dem Kauf erhältst du Zugang auf den gesamten Kurs (inkl. Prüfung).")
}}
</p>
<p v-if="paymentError" class="text-bold mt-12 text-lg text-red-700">
{{
$t(
"a.Fehler bei der Zahlung. Bitte versuche es erneut oder wähle eine andere Zahlungsmethode."
)
}}
</p>
<h3 class="mb-4 mt-10">{{ $t("a.Adresse") }}</h3>
<p class="mb-2">
{{
$t(
"a.Um die Zahlung vornehmen zu können, benötigen wir deine Privatadresse. Optional kannst du die Rechnungsadresse deiner Gesellschaft hinzufügen."
)
}}
</p>
<p>
{{
$t(
"Wichtig: wird die Rechnung von deinem Arbeitgeber bezahlt, dann kannst du zusätzlich die Rechnungsadresse deines Arbeitsgebers erfassen."
)
}}
</p>
<PersonalAddress v-model="address" />
<p v-if="formErrors.personal.length" class="mb-10 text-red-700">
{{ $t("a.Bitte folgende Felder ausfüllen") }}:
{{ formErrors.personal.join(", ") }}
</p>
<section v-if="address.payment_method !== 'cembra_byjuno'">
<div class="mt-4">
<button
v-if="!withCompanyAddress"
class="underline"
data-cy="add-company-address"
@click="setWithCompanyAddress(true)"
>
<template v-if="userOrganisationName">
{{
$t("a.Rechnungsadresse von {organisation} hinzufügen", {
organisation: userOrganisationName,
})
}}
</template>
<template v-else>{{ $t("a.Rechnungsadresse hinzufügen") }}</template>
</button>
</div>
<transition
enter-active-class="transition ease-out duration-100"
enter-from-class="transform opacity-0 scale-y-95"
enter-to-class="transform opacity-100 scale-y-100"
leave-active-class="transition ease-in duration-75"
leave-from-class="transform opacity-100 scale-y-100"
leave-to-class="transform opacity-0 scale-y-95"
>
<div v-if="withCompanyAddress">
<div class="flex items-center justify-between">
<h3 v-if="userOrganisationName">
{{
$t("a.Rechnungsadresse von {organisation}", {
organisation: userOrganisationName,
})
}}
</h3>
<h3 v-else>{{ $t("a.Rechnungsadresse") }}</h3>
<button class="underline" @click="setWithCompanyAddress(false)">
{{ $t("a.Entfernen") }}
</button>
</div>
<OrganisationAddress v-model="address" />
<p v-if="formErrors.company.length" class="text-red-700">
{{ $t("a.Bitte folgende Felder ausfüllen") }}:
{{ formErrors.company.join(", ") }}
</p>
</div>
</transition>
</section>
</template>
<template #footer>
<router-link
:to="{ name: 'accountProfile' }"
class="btn-secondary flex items-center"
>
<it-icon-arrow-left class="it-icon mr-2 h-6 w-6" />
{{ $t("general.back") }}
</router-link>
<button
class="btn-blue flex items-center"
data-cy="continue-pay"
@click="executePayment"
>
<template v-if="address.payment_method === 'cembra_byjuno'">
{{ $t("a.Mit Rechnung bezahlen") }}
</template>
<template v-else>
{{ $t("a.Mit Kreditkarte bezahlen") }}
</template>
<it-icon-arrow-right class="it-icon ml-2 h-6 w-6" />
</button>
</template>
</WizardPage>
</template>