Skip to content

Commit 89e87c1

Browse files
dom-baurneoscieCopilot
authored
Add utility function isValidSwissIbanNumber (#79)
Add utility function isValidSwissIbanNumber for issue #77 --------- Co-authored-by: Sandro C. <sc@neolution.ch> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 18d0936 commit 89e87c1

File tree

3 files changed

+69
-1
lines changed

3 files changed

+69
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- `isValidSwissIbanNumber` string utility function
1213
- `isValidSwissSocialSecurityNumber` string utility function
1314

1415
## [2.0.0] - 2025-07-29

src/lib/string.spec.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate, isValidSwissSocialSecurityNumber } from "./string";
1+
import {
2+
isNullOrEmpty,
3+
isNullOrWhitespace,
4+
capitalize,
5+
uncapitalize,
6+
truncate,
7+
isValidSwissIbanNumber,
8+
isValidSwissSocialSecurityNumber,
9+
} from "./string";
210

311
describe("string tests", () => {
412
test.each([
@@ -121,6 +129,20 @@ describe("string tests", () => {
121129
expect(truncate(value, maxLength)).toBe(expected);
122130
});
123131

132+
test.each([
133+
[null as unknown as string, false],
134+
[undefined as unknown as string, false],
135+
["CH9300762011623852957", true],
136+
["CH93 0076 2011 6238 5295 7", true],
137+
["CH930076 20116238 5295 7", false],
138+
["CH93-0076-2011-6238-5295-7", false],
139+
["CH93 0000 0000 0000 0000 1", false],
140+
["ch93 0076 2011 6238 5295 7", false],
141+
["DE93 0076 2011 6238 5295 7", false],
142+
])("check if this swiss IBAN is valid or not", (unformattedIbanNumber, expected) => {
143+
expect(isValidSwissIbanNumber(unformattedIbanNumber)).toBe(expected);
144+
});
145+
124146
test.each([
125147
[null as unknown as string, false],
126148
[undefined as unknown as string, false],

src/lib/string.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,51 @@ export function truncate(value: string | undefined, maxLength: number, suffix =
6565
return `${value.slice(0, maxLength)}${suffix}`;
6666
}
6767

68+
/**
69+
* Checks if the provided string is a valid swiss IBAN number
70+
* @param ibanNumber The provided IBAN number to check
71+
* Must be in one of the following formats:
72+
* - "CHXX XXXX XXXX XXXX XXXX X" with whitespaces
73+
* - "CHXXXXXXXXXXXXXXXXXXX" without whitespaces
74+
* @returns The result of the IBAN number check
75+
*/
76+
export function isValidSwissIbanNumber(ibanNumber: string): boolean {
77+
// 1. Reject null, undefined or whitespace-only strings
78+
if (isNullOrWhitespace(ibanNumber)) {
79+
return false;
80+
}
81+
82+
// 2. Define allowed strict formats
83+
// - with spaces: "CHXX XXXX XXXX XXXX XXXX X"
84+
const compactIbanNumberWithWhiteSpaces = new RegExp(/^CH\d{2}(?: \d{4}){4} \d{1}$/);
85+
// - without spaces: "CHXXXXXXXXXXXXXXXXXXX"
86+
const compactIbanNumberWithoutWhiteSpaces = new RegExp(/^CH\d{19}$/);
87+
88+
// 3. Check if input matches one of the allowed formats
89+
if (!compactIbanNumberWithWhiteSpaces.test(ibanNumber) && !compactIbanNumberWithoutWhiteSpaces.test(ibanNumber)) {
90+
return false;
91+
}
92+
93+
// 4. Remove all spaces to get a compact IBAN string
94+
const compactIbanNumber = ibanNumber.replaceAll(" ", "");
95+
96+
// 5. Rearrange IBAN for checksum calculation
97+
// - move first 4 characters (CH + 2 check digits) to the end
98+
const rearrangedIban = compactIbanNumber.slice(4) + compactIbanNumber.slice(0, 4);
99+
100+
// 6. Replace letters with numbers (A=10, B=11, ..., Z=35)
101+
const numericStr = rearrangedIban.replaceAll(/[A-Z]/g, (ch) => (ch.codePointAt(0)! - 55).toString());
102+
103+
// 7. Perform modulo 97 calculation to validate IBAN
104+
let restOfCalculation = 0;
105+
for (const digit of numericStr) {
106+
restOfCalculation = (restOfCalculation * 10 + Number(digit)) % 97;
107+
}
108+
109+
// 8. IBAN is valid only if the remainder equals 1
110+
return restOfCalculation === 1;
111+
}
112+
68113
/**
69114
* Validation of social insurance number with checking the checksum
70115
* Validation according to https://www.sozialversicherungsnummer.ch/aufbau-neu.htm

0 commit comments

Comments
 (0)