Handle phone numbers

This commit is contained in:
Daniel Egger 2024-06-21 14:18:59 +02:00
parent 42fd2f1377
commit b9f8e5d771
4 changed files with 145 additions and 6 deletions

View File

@ -12,6 +12,8 @@ import { getVVCourseName } from "./composables";
import ItToggleSwitch from "@/components/ui/ItToggleSwitch.vue"; import ItToggleSwitch from "@/components/ui/ItToggleSwitch.vue";
import DatatransCembraDeviceFingerprint from "@/components/onboarding/DatatransCembraDeviceFingerprint.vue"; import DatatransCembraDeviceFingerprint from "@/components/onboarding/DatatransCembraDeviceFingerprint.vue";
import { getLocalSessionKey } from "@/statistics"; import { getLocalSessionKey } from "@/statistics";
import log from "loglevel";
import { normalizeSwissPhoneNumber, validatePhoneNumber } from "@/utils/phone";
const props = defineProps({ const props = defineProps({
courseType: { courseType: {
@ -126,6 +128,14 @@ function validateAddress() {
formErrors.value.personal.push(t("a.Land")); 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 (withCembraInvoice.value) { if (withCembraInvoice.value) {
if (!address.value.phone_number) { if (!address.value.phone_number) {
formErrors.value.personal.push(t("a.Telefonnummer")); formErrors.value.personal.push(t("a.Telefonnummer"));
@ -164,7 +174,8 @@ function validateAddress() {
} }
async function saveAddress() { async function saveAddress() {
const { country_code, organisation_country_code, ...profileData } = address.value; const { country_code, organisation_country_code, phone_number, ...profileData } =
address.value;
const typedProfileData: Partial<User> = { ...profileData }; const typedProfileData: Partial<User> = { ...profileData };
typedProfileData.country = countries.value.find( typedProfileData.country = countries.value.find(
@ -173,6 +184,7 @@ async function saveAddress() {
typedProfileData.organisation_country = countries.value.find( typedProfileData.organisation_country = countries.value.find(
(c) => c.country_code === organisation_country_code (c) => c.country_code === organisation_country_code
); );
typedProfileData.phone_number = normalizeSwissPhoneNumber(phone_number);
await user.updateUserProfile(typedProfileData); await user.updateUserProfile(typedProfileData);
} }
@ -192,6 +204,8 @@ const executePayment = async () => {
// anyway, so it seems fine to do it here. // anyway, so it seems fine to do it here.
const fullHost = `${window.location.protocol}//${window.location.host}`; const fullHost = `${window.location.protocol}//${window.location.host}`;
address.value.phone_number = normalizeSwissPhoneNumber(address.value.phone_number);
itPost("/api/shop/vv/checkout/", { itPost("/api/shop/vv/checkout/", {
redirect_url: fullHost, redirect_url: fullHost,
address: address.value, address: address.value,
@ -343,9 +357,7 @@ const executePayment = async () => {
data-cy="continue-pay" data-cy="continue-pay"
@click="executePayment" @click="executePayment"
> >
<template v-if="withCembraInvoice"> <template v-if="withCembraInvoice">TODO: Mit Rechnung/Cembra bezahlen</template>
TODO: Mit Rechnung/Cembra bezahlen
</template>
<template v-else> <template v-else>
{{ $t("a.Mit Kreditkarte bezahlen") }} {{ $t("a.Mit Kreditkarte bezahlen") }}
</template> </template>

View File

@ -0,0 +1,58 @@
import { describe, expect, it } from "vitest";
import { normalizeSwissPhoneNumber, validatePhoneNumber } from "../phone";
describe("normalizeSwissPhoneNumber", () => {
it("should normalize a Swiss phone number", () => {
expect(normalizeSwissPhoneNumber("079 123 45 67")).toBe("+41791234567");
expect(normalizeSwissPhoneNumber("00 41 79 123 45 67")).toBe("+41791234567");
expect(normalizeSwissPhoneNumber("+41 (0)79 201 85 86")).toBe("+41792018586");
expect(normalizeSwissPhoneNumber("+41 79 201 85 86")).toBe("+41792018586");
});
it("should normalize remove spaces and special chars from foreign numbers", () => {
expect(normalizeSwissPhoneNumber("+49 30 12345678")).toBe("+493012345678");
expect(normalizeSwissPhoneNumber("+49-30-12345678")).toBe("+493012345678");
expect(normalizeSwissPhoneNumber("+33 1 23 45 67 89")).toBe("+33123456789");
expect(normalizeSwissPhoneNumber("+33-1-23-45-67-89")).toBe("+33123456789");
expect(normalizeSwissPhoneNumber("+39 06 12345678")).toBe("+390612345678");
expect(normalizeSwissPhoneNumber("+43 1 2345678")).toBe("+4312345678");
expect(normalizeSwissPhoneNumber("+423 234 5678")).toBe("+4232345678");
});
});
describe("validatePhoneNumber", () => {
it("should validate a Swiss phone number", () => {
expect(validatePhoneNumber("079 123 45 67")).toBe(true);
expect(validatePhoneNumber("00 41 79 123 45 67")).toBe(true);
expect(validatePhoneNumber("+41 (0)79 201 85 86")).toBe(true);
expect(validatePhoneNumber("+41 79 201 85 86")).toBe(true);
expect(validatePhoneNumber("026 418 01 31")).toBe(true);
expect(validatePhoneNumber("+41 79 201 85 86 8")).toBe(false);
expect(validatePhoneNumber("079 201 85 8")).toBe(false);
expect(validatePhoneNumber("aaa aaa aaa aaa")).toBe(false);
});
it("should validate a foreign phone number", () => {
expect(validatePhoneNumber("+49 30 12345678")).toBe(true);
expect(validatePhoneNumber("+49 30 1234567")).toBe(false);
expect(validatePhoneNumber("+49 30 123456789")).toBe(false);
expect(validatePhoneNumber("+33 1 23 45 67 89")).toBe(true);
expect(validatePhoneNumber("+33 1 23 45 67 89 9")).toBe(false);
expect(validatePhoneNumber("+39 06 12345678")).toBe(true);
expect(validatePhoneNumber("+39 06 1234567")).toBe(false);
expect(validatePhoneNumber("+43 1 2345678")).toBe(true);
expect(validatePhoneNumber("+43 1 23456789")).toBe(false);
expect(validatePhoneNumber("+423 235 09 09")).toBe(true);
expect(validatePhoneNumber("+423 235 09 09 8")).toBe(false);
expect(validatePhoneNumber("+354 123 4567")).toBe(true);
expect(validatePhoneNumber("+55 12 34567 8901")).toBe(true);
});
});

69
client/src/utils/phone.ts Normal file
View File

@ -0,0 +1,69 @@
export function normalizeSwissPhoneNumber(input: string) {
return input
.replace(/\s+/g, "")
.replace("(0)", "")
.replaceAll("-", "")
.replaceAll("/", "")
.replaceAll("(", "")
.replaceAll(")", "")
.replace(/^0041/, "+41")
.replace(/^\+410/, "+41")
.replace(/^0/, "+41");
}
export function validatePhoneNumber(input: string) {
const normalized = normalizeSwissPhoneNumber(input);
if (
!normalized.startsWith("+") ||
isNaN(Number(normalized.slice(1))) ||
normalized[1] === "0"
) {
// phone number can only start with a + and must be followed by numbers
return false;
}
if (["+41", "+43", "+49", "+39", "+33", "+42"].includes(normalized.slice(0, 3))) {
if (
// Swiss and French phone numbers
(normalized.startsWith("+41") || normalized.startsWith("+33")) &&
normalized.length === 12
) {
return true;
} else if (
// German and Italian phone numbers
(normalized.startsWith("+49") || normalized.startsWith("+39")) &&
normalized.length === 13
) {
return true;
} else if (
// Austrian and Liechtenstein phone numbers
(normalized.startsWith("+43") || normalized.startsWith("+423")) &&
normalized.length === 11
) {
return true;
}
return false;
}
// every other country
if (normalized.length >= 10 || normalized.length <= 13) {
return true;
}
return false;
}
export function displaySwissPhoneNumber(input: string) {
if (input && input.length === 12 && input.startsWith("+41")) {
input = input.replace("+41", "0");
let result = "";
result += input.substring(0, 3) + " ";
result += input.substring(3, 6) + " ";
result += input.substring(6, 8) + " ";
result += input.substring(8);
return result;
}
return input;
}

View File

@ -126,7 +126,7 @@ describe("checkout.cy.js", () => {
cy.get("#postal-code").type("1719"); cy.get("#postal-code").type("1719");
cy.get("#city").type("Zumholz"); cy.get("#city").type("Zumholz");
cy.get("#phone").type("+41 79 201 85 86"); cy.get("#phone").type("079 201 85 86");
cy.get("#birth-date").type("1982-06-09"); cy.get("#birth-date").type("1982-06-09");
cy.get('[data-cy="continue-pay"]').click(); cy.get('[data-cy="continue-pay"]').click();