From 54f3a07e04e35b00f9e15190736b68e1dc0c61a8 Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Thu, 4 Sep 2025 15:29:16 +0200 Subject: [PATCH 1/5] Add string utility function formatSwissIbanNumber --- CHANGELOG.md | 4 + src/lib/string.spec.ts | 39 +-------- src/lib/string.ts | 108 ------------------------ src/lib/swissStandards.spec.ts | 49 +++++++++++ src/lib/swissStandards.ts | 149 +++++++++++++++++++++++++++++++++ 5 files changed, 203 insertions(+), 146 deletions(-) create mode 100644 src/lib/swissStandards.spec.ts create mode 100644 src/lib/swissStandards.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a710d1..34ec191 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `formatSwissIbanNumber` string utility function + ## [2.1.0] - 2025-09-03 ### Added diff --git a/src/lib/string.spec.ts b/src/lib/string.spec.ts index 2c0d975..94fc71e 100644 --- a/src/lib/string.spec.ts +++ b/src/lib/string.spec.ts @@ -1,12 +1,4 @@ -import { - isNullOrEmpty, - isNullOrWhitespace, - capitalize, - uncapitalize, - truncate, - isValidSwissIbanNumber, - isValidSwissSocialSecurityNumber, -} from "./string"; +import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate } from "./string"; describe("string tests", () => { test.each([ @@ -128,33 +120,4 @@ describe("string tests", () => { ])("truncate without suffix parameter", (value, maxLength, expected) => { expect(truncate(value, maxLength)).toBe(expected); }); - - test.each([ - [null as unknown as string, false], - [undefined as unknown as string, false], - ["CH9300762011623852957", true], - ["CH93 0076 2011 6238 5295 7", true], - ["CH930076 20116238 5295 7", false], - ["CH93-0076-2011-6238-5295-7", false], - ["CH93 0000 0000 0000 0000 1", false], - ["ch93 0076 2011 6238 5295 7", false], - ["DE93 0076 2011 6238 5295 7", false], - ])("check if this swiss IBAN is valid or not", (unformattedIbanNumber, expected) => { - expect(isValidSwissIbanNumber(unformattedIbanNumber)).toBe(expected); - }); - - test.each([ - [null as unknown as string, false], - [undefined as unknown as string, false], - ["7561234567891", false], - ["7569217076985", true], - ["756.92170769.85", false], - ["756.9217.0769.85", true], - ["756..9217.0769.85", false], - ["756.1234.5678.91", false], - ["test756.9217.0769.85", false], - ["7.56..9217...0769.85", false], - ])("check if the social insurance number is valid or not", (ahvNumber, expected) => { - expect(isValidSwissSocialSecurityNumber(ahvNumber)).toBe(expected); - }); }); diff --git a/src/lib/string.ts b/src/lib/string.ts index c8fbdcb..c0666b3 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -64,111 +64,3 @@ export function truncate(value: string | undefined, maxLength: number, suffix = return `${value.slice(0, maxLength)}${suffix}`; } - -/** - * Checks if the provided string is a valid swiss IBAN number - * @param ibanNumber The provided IBAN number to check - * Must be in one of the following formats: - * - "CHXX XXXX XXXX XXXX XXXX X" with whitespaces - * - "CHXXXXXXXXXXXXXXXXXXX" without whitespaces - * @returns The result of the IBAN number check - */ -export function isValidSwissIbanNumber(ibanNumber: string): boolean { - // 1. Reject null, undefined or whitespace-only strings - if (isNullOrWhitespace(ibanNumber)) { - return false; - } - - // 2. Define allowed strict formats - // - with spaces: "CHXX XXXX XXXX XXXX XXXX X" - const compactIbanNumberWithWhiteSpaces = new RegExp(/^CH\d{2}(?: \d{4}){4} \d{1}$/); - // - without spaces: "CHXXXXXXXXXXXXXXXXXXX" - const compactIbanNumberWithoutWhiteSpaces = new RegExp(/^CH\d{19}$/); - - // 3. Check if input matches one of the allowed formats - if (!compactIbanNumberWithWhiteSpaces.test(ibanNumber) && !compactIbanNumberWithoutWhiteSpaces.test(ibanNumber)) { - return false; - } - - // 4. Remove all spaces to get a compact IBAN string - const compactIbanNumber = ibanNumber.replaceAll(" ", ""); - - // 5. Rearrange IBAN for checksum calculation - // - move first 4 characters (CH + 2 check digits) to the end - const rearrangedIban = compactIbanNumber.slice(4) + compactIbanNumber.slice(0, 4); - - // 6. Replace letters with numbers (A=10, B=11, ..., Z=35) - const numericStr = rearrangedIban.replaceAll(/[A-Z]/g, (ch) => (ch.codePointAt(0)! - 55).toString()); - - // 7. Perform modulo 97 calculation to validate IBAN - let restOfCalculation = 0; - for (const digit of numericStr) { - restOfCalculation = (restOfCalculation * 10 + Number(digit)) % 97; - } - - // 8. IBAN is valid only if the remainder equals 1 - return restOfCalculation === 1; -} - -/** - * Validation of social insurance number with checking the checksum - * Validation according to https://www.sozialversicherungsnummer.ch/aufbau-neu.htm - * @param socialInsuranceNumber The social insurance number to check - * Must be in one of the following formats: - * - "756.XXXX.XXXX.XX" with dots as separators - * - "756XXXXXXXXXX" with digits only - * @returns The result if the social insurance number is valid or not - */ -export function isValidSwissSocialSecurityNumber(socialInsuranceNumber: string): boolean { - // 1. Check if input is empty or only whitespace - if (isNullOrWhitespace(socialInsuranceNumber)) { - return false; - } - - /** - * 2. Check if input matches accepted formats: - * - With dots: 756.XXXX.XXXX.XX - * - Without dots: 756XXXXXXXXXX - */ - const socialInsuranceNumberWithDots = new RegExp(/^756\.\d{4}\.\d{4}\.\d{2}$/); - const socialInsuranceNumberWithoutDots = new RegExp(/^756\d{10}$/); - - if (!socialInsuranceNumberWithDots.test(socialInsuranceNumber) && !socialInsuranceNumberWithoutDots.test(socialInsuranceNumber)) { - return false; - } - - // 3. Remove all dots → get a string of 13 digits - const compactNumber = socialInsuranceNumber.replaceAll(".", ""); - - /** - * 4. Separate digits for checksum calculation - * - first 12 digits: used to calculate checksum - * - last digit: actual check digit - */ - const digits = compactNumber.slice(0, -1); - const reversedDigits = [...digits].reverse().join(""); - const reversedDigitsArray = [...reversedDigits]; - - /* - * 5. Calculate weighted sum for checksum - * - Even positions (after reversing) ×3 - * - Odd positions ×1 - */ - let sum = 0; - for (const [i, element] of reversedDigitsArray.entries()) { - sum += i % 2 === 0 ? Number(element) * 3 : Number(element) * 1; - } - - /* - * 6. Calculate expected check digit - * - Check digit = value to reach next multiple of 10 - */ - const checksum = (10 - (sum % 10)) % 10; - const checknumber = Number.parseInt(compactNumber.slice(-1)); - - /* - * 7. Compare calculated check digit with actual last digit - * - If equal → valid AHV number - */ - return checksum === checknumber; -} diff --git a/src/lib/swissStandards.spec.ts b/src/lib/swissStandards.spec.ts new file mode 100644 index 0000000..3ef87ec --- /dev/null +++ b/src/lib/swissStandards.spec.ts @@ -0,0 +1,49 @@ +import { isValidSwissIbanNumber, isValidSwissSocialSecurityNumber, formatSwissIbanNumber } from "./swissStandards"; + +describe("Swiss standards test", () => { + test.each([ + [null as unknown as string, false], + [undefined as unknown as string, false], + ["CH9300762011623852957", true], + ["CH93 0076 2011 6238 5295 7", true], + ["CH930076 20116238 5295 7", false], + ["CH93-0076-2011-6238-5295-7", false], + ["CH93 0000 0000 0000 0000 1", false], + ["ch93 0076 2011 6238 5295 7", false], + ["c93 0076 2011 6238 5295 7", false], + ["DE93 0076 2011 6238 5295 7", false], + ])("check if this swiss IBAN is valid or not", (unformattedIbanNumber, expected) => { + expect(isValidSwissIbanNumber(unformattedIbanNumber)).toBe(expected); + }); + + test.each([ + [null as unknown as string, false], + [undefined as unknown as string, false], + ["7561234567891", false], + ["7569217076985", true], + ["756.92170769.85", false], + ["756.9217.0769.85", true], + ["756..9217.0769.85", false], + ["756.1234.5678.91", false], + ["test756.9217.0769.85", false], + ["7.56..9217...0769.85", false], + ])("check if the social insurance number is valid or not", (ahvNumber, expected) => { + expect(isValidSwissSocialSecurityNumber(ahvNumber)).toBe(expected); + }); + + test.each([ + [null as unknown as string, null, false], + [undefined as unknown as string, undefined, false], + ["CH9300762011623852957", "CH93 0076 2011 6238 5295 7", true], + ["ch9300762011623852957", "CH93 0076 2011 6238 5295 7", true], + ["ch9301234567891011127", "CH93 0123 4567 8910 1112 7", false], + ["DE93 00 76 2011 62385295 7", "DE93 00 76 2011 62385295 7", false], + ["D 93 00 76 2011 62385295 7", "D 93 00 76 2011 62385295 7", false], + ["Ch 93 0076 20 1 162385 295 7", "CH93 0076 2011 6238 5295 7", true], + ])("Check if the IBAN number gets formatted correctly", (unformattedIbanNumber, expectedIbanNumber, expectedIsValid) => { + const result = formatSwissIbanNumber(unformattedIbanNumber); + + expect(result.ibanNumber).toBe(expectedIbanNumber); + expect(result.isValidSwissIbanNumber).toBe(expectedIsValid); + }); +}); diff --git a/src/lib/swissStandards.ts b/src/lib/swissStandards.ts new file mode 100644 index 0000000..4c3586f --- /dev/null +++ b/src/lib/swissStandards.ts @@ -0,0 +1,149 @@ +import { isNullOrWhitespace } from "./string"; + +/** + * Checks if the provided string is a valid swiss IBAN number + * @param ibanNumber The provided IBAN number to check + * Must be in one of the following formats: + * - "CHXX XXXX XXXX XXXX XXXX X" with whitespaces + * - "CHXXXXXXXXXXXXXXXXXXX" without whitespaces + * @returns The result of the IBAN number check + */ +export function isValidSwissIbanNumber(ibanNumber: string): boolean { + // 1. Reject null, undefined or whitespace-only strings + if (isNullOrWhitespace(ibanNumber)) { + return false; + } + + // 2. Define allowed strict formats + // - with spaces: "CHXX XXXX XXXX XXXX XXXX X" + const compactIbanNumberWithWhiteSpaces = new RegExp(/^CH\d{2}(?: \d{4}){4} \d{1}$/); + // - without spaces: "CHXXXXXXXXXXXXXXXXXXX" + const compactIbanNumberWithoutWhiteSpaces = new RegExp(/^CH\d{19}$/); + + // 3. Check if input matches one of the allowed formats + if (!compactIbanNumberWithWhiteSpaces.test(ibanNumber) && !compactIbanNumberWithoutWhiteSpaces.test(ibanNumber)) { + return false; + } + + // 4. Remove all spaces to get a compact IBAN string + const compactIbanNumber = ibanNumber.replaceAll(" ", ""); + + // 5. Rearrange IBAN for checksum calculation + // - move first 4 characters (CH + 2 check digits) to the end + const rearrangedIban = compactIbanNumber.slice(4) + compactIbanNumber.slice(0, 4); + + // 6. Replace letters with numbers (A=10, B=11, ..., Z=35) + const numericStr = rearrangedIban.replaceAll(/[A-Z]/g, (ch) => (ch.codePointAt(0)! - 55).toString()); + + // 7. Perform modulo 97 calculation to validate IBAN + let restOfCalculation = 0; + for (const digit of numericStr) { + restOfCalculation = (restOfCalculation * 10 + Number(digit)) % 97; + } + + // 8. IBAN is valid only if the remainder equals 1 + return restOfCalculation === 1; +} + +/** + * Validation of social insurance number with checking the checksum + * Validation according to https://www.sozialversicherungsnummer.ch/aufbau-neu.htm + * @param socialInsuranceNumber The social insurance number to check + * Must be in one of the following formats: + * - "756.XXXX.XXXX.XX" with dots as separators + * - "756XXXXXXXXXX" with digits only + * @returns The result if the social insurance number is valid or not + */ +export function isValidSwissSocialSecurityNumber(socialInsuranceNumber: string): boolean { + // 1. Check if input is empty or only whitespace + if (isNullOrWhitespace(socialInsuranceNumber)) { + return false; + } + + /** + * 2. Check if input matches accepted formats: + * - With dots: 756.XXXX.XXXX.XX + * - Without dots: 756XXXXXXXXXX + */ + const socialInsuranceNumberWithDots = new RegExp(/^756\.\d{4}\.\d{4}\.\d{2}$/); + const socialInsuranceNumberWithoutDots = new RegExp(/^756\d{10}$/); + + if (!socialInsuranceNumberWithDots.test(socialInsuranceNumber) && !socialInsuranceNumberWithoutDots.test(socialInsuranceNumber)) { + return false; + } + + // 3. Remove all dots → get a string of 13 digits + const compactNumber = socialInsuranceNumber.replaceAll(".", ""); + + /** + * 4. Separate digits for checksum calculation + * - first 12 digits: used to calculate checksum + * - last digit: actual check digit + */ + const digits = compactNumber.slice(0, -1); + const reversedDigits = [...digits].reverse().join(""); + const reversedDigitsArray = [...reversedDigits]; + + /* + * 5. Calculate weighted sum for checksum + * - Even positions (after reversing) ×3 + * - Odd positions ×1 + */ + let sum = 0; + for (const [i, element] of reversedDigitsArray.entries()) { + sum += i % 2 === 0 ? Number(element) * 3 : Number(element) * 1; + } + + /* + * 6. Calculate expected check digit + * - Check digit = value to reach next multiple of 10 + */ + const checksum = (10 - (sum % 10)) % 10; + const checknumber = Number.parseInt(compactNumber.slice(-1)); + + /* + * 7. Compare calculated check digit with actual last digit + * - If equal → valid AHV number + */ + return checksum === checknumber; +} + +/** + * Formats a Swiss IBAN number to the standard of "CHXX XXXX XXXX XXXX XXXX X" + * @param unformattedIbanNumber the IBAN number to format + * @returns a object containing the formatted IBAN number and a boolean indicating if the IBAN number was valid or not + */ +export function formatSwissIbanNumber(unformattedIbanNumber: string): { + /** + * The formatted IBAN number or the original input if the unformatted IBAN number was invalid + */ + ibanNumber: string; + /** + * The result if the IBAN number is valid or not + */ + isValidSwissIbanNumber: boolean; +} { + // 1. Check if the unformatted IBAN number is empty or only a whitespace + if (isNullOrWhitespace(unformattedIbanNumber)) { + return { ibanNumber: unformattedIbanNumber, isValidSwissIbanNumber: false }; + } + + // 2. Remove all non-alphanumeric characters and convert letters to uppercase + const cleanedIbanNumber = unformattedIbanNumber.replaceAll(/[^A-Z0-9]/gi, "").toUpperCase(); + + // 3. Check if it is possible to format a new IBAN number + if (!/^CH\d{19}$/.test(cleanedIbanNumber)) { + return { ibanNumber: unformattedIbanNumber, isValidSwissIbanNumber: false }; + } + + // 4. Format the cleaned IBAN number into groups of 4 characters separated by spaces + const formattedIbanNumber = cleanedIbanNumber.replaceAll(/(.{4})/g, "$1 ").trim(); + + // 5. If the Swiss IBAN number is valid return the formatted IBAN number with the true status + if (isValidSwissIbanNumber(formattedIbanNumber)) { + return { ibanNumber: formattedIbanNumber, isValidSwissIbanNumber: true }; + } + + // 6. If the Swiss IBAN number is not valid return the formatted IBAN number with the false status + return { ibanNumber: formattedIbanNumber, isValidSwissIbanNumber: false }; +} From 9a01b0873ebd91adfabb714c4aa62c2ff8257d05 Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Mon, 8 Sep 2025 10:40:11 +0200 Subject: [PATCH 2/5] Formatted --- src/lib/swissStandards.spec.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/swissStandards.spec.ts b/src/lib/swissStandards.spec.ts index 61c31c6..202486f 100644 --- a/src/lib/swissStandards.spec.ts +++ b/src/lib/swissStandards.spec.ts @@ -27,8 +27,8 @@ describe("Swiss standards test", () => { ["756.1234.5678.91", false], ["test756.9217.0769.85", false], ["7.56..9217...0769.85", false], - ])("check if the social insurance number is valid or not", (ahvNumber, expected) => { - expect(isValidSwissSocialInsuranceNumber(ahvNumber)).toBe(expected); + ])("check if the social insurance number is valid or not", (swissSocialInsuranceNumber, expected) => { + expect(isValidSwissSocialInsuranceNumber(swissSocialInsuranceNumber)).toBe(expected); }); test.each([ @@ -45,6 +45,5 @@ describe("Swiss standards test", () => { expect(result.ibanNumber).toBe(expectedIbanNumber); expect(result.isValidSwissIbanNumber).toBe(expectedIsValid); - expect(isValidSwissSocialInsuranceNumber(ahvNumber)).toBe(expected); }); }); From 32c538abff91aa996e8337eedc457348f08edcfa Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Mon, 8 Sep 2025 16:40:06 +0200 Subject: [PATCH 3/5] Fixed changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad61e04..918b338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- `formatSwissIbanNumber` string utility function +- `formatSwissIbanNumber` swiss standard function ### Changed From fe089caca11c7de7f4cf1e2b5d49a8fc9052430f Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Wed, 1 Oct 2025 16:06:33 +0200 Subject: [PATCH 4/5] Added Swiss standards function tryParseSwissIbanNumber --- CHANGELOG.md | 2 +- src/lib/swissStandards.spec.ts | 58 ++++++++++++++++++++++++---------- src/lib/swissStandards.ts | 47 +++++++++------------------ 3 files changed, 58 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aa71cc..3a0517b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `splitLines` string utility function -- `formatSwissIbanNumber` swiss standard function +- `tryParseSwissIbanNumber` swiss standard function - `trimStart`, `trimEnd` and `trim` string type utility functions ### Changed diff --git a/src/lib/swissStandards.spec.ts b/src/lib/swissStandards.spec.ts index 4444a2b..2174416 100644 --- a/src/lib/swissStandards.spec.ts +++ b/src/lib/swissStandards.spec.ts @@ -1,4 +1,4 @@ -import { isValidSwissIbanNumber, isValidSwissSocialInsuranceNumber, formatSwissIbanNumber } from "./swissStandards"; +import { isValidSwissIbanNumber, isValidSwissSocialInsuranceNumber, tryParseSwissIbanNumber } from "./swissStandards"; describe("Swiss standards test", () => { test.each([ @@ -12,7 +12,7 @@ describe("Swiss standards test", () => { ["ch93 0076 2011 6238 5295 7", false], ["c93 0076 2011 6238 5295 7", false], ["DE93 0076 2011 6238 5295 7", false], - ])("check if the swiss IBAN number is valid or not", (iBanNumberToCheck, expected) => { + ])("check if the Swiss IBAN number is valid or not", (iBanNumberToCheck, expected) => { expect(isValidSwissIbanNumber(iBanNumberToCheck)).toBe(expected); }); @@ -25,7 +25,7 @@ describe("Swiss standards test", () => { ["DE34 0076 2ABC 123D EF45 3", false], ["CH34 0076 2ABC 123D EF45 \n6", false], ["CH34 0076 2ABC 123D EF45 !", false], - ])("check if the siwss IBAN number with letters is valid or not", (iBanNumberToCheck, expected) => { + ])("check if the Swiss IBAN number with letters is valid or not", (iBanNumberToCheck, expected) => { expect(isValidSwissIbanNumber(iBanNumberToCheck)).toBe(expected); }); @@ -45,18 +45,44 @@ describe("Swiss standards test", () => { }); test.each([ - [null as unknown as string, null, false], - [undefined as unknown as string, undefined, false], - ["CH9300762011623852957", "CH93 0076 2011 6238 5295 7", true], - ["ch9300762011623852957", "CH93 0076 2011 6238 5295 7", true], - ["ch9301234567891011127", "CH93 0123 4567 8910 1112 7", false], - ["DE93 00 76 2011 62385295 7", "DE93 00 76 2011 62385295 7", false], - ["D 93 00 76 2011 62385295 7", "D 93 00 76 2011 62385295 7", false], - ["Ch 93 0076 20 1 162385 295 7", "CH93 0076 2011 6238 5295 7", true], - ])("Check if the IBAN number gets formatted correctly", (unformattedIbanNumber, expectedIbanNumber, expectedIsValid) => { - const result = formatSwissIbanNumber(unformattedIbanNumber); - - expect(result.ibanNumber).toBe(expectedIbanNumber); - expect(result.isValidSwissIbanNumber).toBe(expectedIsValid); + [null as unknown as string, { isValid: false, iban: undefined, ibanFormatted: undefined }], + [undefined as unknown as string, { isValid: false, iban: undefined, ibanFormatted: undefined }], + ["", { isValid: false, iban: undefined, ibanFormatted: undefined }], + ["CH94 0076 2011 6238 5295 7", { isValid: false, iban: undefined, ibanFormatted: undefined }], + ["CH1000000ABC123DEF456", { isValid: false, iban: undefined, ibanFormatted: undefined }], + [ + "CH9300762011623852957", + { + isValid: true, + iban: "CH9300762011623852957", + ibanFormatted: "CH93 0076 2011 6238 5295 7", + }, + ], + [ + "CH3400762ABC123DEF456", + { + isValid: true, + iban: "CH3400762ABC123DEF456", + ibanFormatted: "CH34 0076 2ABC 123D EF45 6", + }, + ], + [ + "CH93 0076 2011 6238 5295 7", + { + isValid: true, + iban: "CH9300762011623852957", + ibanFormatted: "CH93 0076 2011 6238 5295 7", + }, + ], + [ + "CH34 0076 2ABC 123D EF45 6", + { + isValid: true, + iban: "CH3400762ABC123DEF456", + ibanFormatted: "CH34 0076 2ABC 123D EF45 6", + }, + ], + ])("check if the Swiss IBAN number gets parsed correctly", (unformattedSwissIbanNumber, expected) => { + expect(tryParseSwissIbanNumber(unformattedSwissIbanNumber)).toEqual(expected); }); }); diff --git a/src/lib/swissStandards.ts b/src/lib/swissStandards.ts index 3d05ae3..ac13650 100644 --- a/src/lib/swissStandards.ts +++ b/src/lib/swissStandards.ts @@ -109,41 +109,24 @@ export function isValidSwissSocialInsuranceNumber(socialInsuranceNumber: string) } /** - * Formats a Swiss IBAN number to the standard of "CHXX XXXX XXXX XXXX XXXX X" - * @param unformattedIbanNumber the IBAN number to format - * @returns a object containing the formatted IBAN number and a boolean indicating if the IBAN number was valid or not + * Attempts to parse and validate a Swiss IBAN. + * @param unformattedIbanNumber - The unformatted IBAN as a string + * @returns The result object with the following properties: + * @property {boolean} isValid - Indicates whether the IBAN is valid or not + * @property {string} iban - The cleaned IBAN, only present if valid + * @property {string} ibanFormatted - The formatted IBAN, only present if valid */ -export function formatSwissIbanNumber(unformattedIbanNumber: string): { - /** - * The formatted IBAN number or the original input if the unformatted IBAN number was invalid - */ - ibanNumber: string; - /** - * The result if the IBAN number is valid or not - */ - isValidSwissIbanNumber: boolean; -} { - // 1. Check if the unformatted IBAN number is empty or only a whitespace +export function tryParseSwissIbanNumber(unformattedIbanNumber?: string) { if (isNullOrWhitespace(unformattedIbanNumber)) { - return { ibanNumber: unformattedIbanNumber, isValidSwissIbanNumber: false }; + return { isValid: false }; } - // 2. Remove all non-alphanumeric characters and convert letters to uppercase - const cleanedIbanNumber = unformattedIbanNumber.replaceAll(/[^A-Z0-9]/gi, "").toUpperCase(); - - // 3. Check if it is possible to format a new IBAN number - if (!/^CH\d{19}$/.test(cleanedIbanNumber)) { - return { ibanNumber: unformattedIbanNumber, isValidSwissIbanNumber: false }; - } - - // 4. Format the cleaned IBAN number into groups of 4 characters separated by spaces - const formattedIbanNumber = cleanedIbanNumber.replaceAll(/(.{4})/g, "$1 ").trim(); - - // 5. If the Swiss IBAN number is valid return the formatted IBAN number with the true status - if (isValidSwissIbanNumber(formattedIbanNumber)) { - return { ibanNumber: formattedIbanNumber, isValidSwissIbanNumber: true }; - } + const iban = unformattedIbanNumber!.replaceAll(/[^A-Z0-9]/gi, "").toUpperCase(); + const isValid = isValidSwissIbanNumber(iban); - // 6. If the Swiss IBAN number is not valid return the formatted IBAN number with the false status - return { ibanNumber: formattedIbanNumber, isValidSwissIbanNumber: false }; + return { + isValid: isValid, + iban: isValid ? iban : undefined, + ibanFormatted: isValid ? iban.match(/.{1,4}/g)?.join(" ") : undefined, + }; } From 44bd2af8187de2b326894956a3011062310a08b1 Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Wed, 1 Oct 2025 16:24:33 +0200 Subject: [PATCH 5/5] Added another testcase --- src/lib/swissStandards.spec.ts | 37 +++++----------------------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/src/lib/swissStandards.spec.ts b/src/lib/swissStandards.spec.ts index 2174416..9708e93 100644 --- a/src/lib/swissStandards.spec.ts +++ b/src/lib/swissStandards.spec.ts @@ -50,38 +50,11 @@ describe("Swiss standards test", () => { ["", { isValid: false, iban: undefined, ibanFormatted: undefined }], ["CH94 0076 2011 6238 5295 7", { isValid: false, iban: undefined, ibanFormatted: undefined }], ["CH1000000ABC123DEF456", { isValid: false, iban: undefined, ibanFormatted: undefined }], - [ - "CH9300762011623852957", - { - isValid: true, - iban: "CH9300762011623852957", - ibanFormatted: "CH93 0076 2011 6238 5295 7", - }, - ], - [ - "CH3400762ABC123DEF456", - { - isValid: true, - iban: "CH3400762ABC123DEF456", - ibanFormatted: "CH34 0076 2ABC 123D EF45 6", - }, - ], - [ - "CH93 0076 2011 6238 5295 7", - { - isValid: true, - iban: "CH9300762011623852957", - ibanFormatted: "CH93 0076 2011 6238 5295 7", - }, - ], - [ - "CH34 0076 2ABC 123D EF45 6", - { - isValid: true, - iban: "CH3400762ABC123DEF456", - ibanFormatted: "CH34 0076 2ABC 123D EF45 6", - }, - ], + ["CH93-0076,2011.6238\n5295\n7", { isValid: true, iban: "CH9300762011623852957", ibanFormatted: "CH93 0076 2011 6238 5295 7" }], + ["CH9300762011623852957", { isValid: true, iban: "CH9300762011623852957", ibanFormatted: "CH93 0076 2011 6238 5295 7" }], + ["CH3400762ABC123DEF456", { isValid: true, iban: "CH3400762ABC123DEF456", ibanFormatted: "CH34 0076 2ABC 123D EF45 6" }], + ["CH93 0076 2011 6238 5295 7", { isValid: true, iban: "CH9300762011623852957", ibanFormatted: "CH93 0076 2011 6238 5295 7" }], + ["CH34 0076 2ABC 123D EF45 6", { isValid: true, iban: "CH3400762ABC123DEF456", ibanFormatted: "CH34 0076 2ABC 123D EF45 6" }], ])("check if the Swiss IBAN number gets parsed correctly", (unformattedSwissIbanNumber, expected) => { expect(tryParseSwissIbanNumber(unformattedSwissIbanNumber)).toEqual(expected); });