@@ -64,3 +64,66 @@ export function truncate(value: string | undefined, maxLength: number, suffix =
6464
6565 return `${ value . slice ( 0 , maxLength ) } ${ suffix } ` ;
6666}
67+
68+ /**
69+ * Validation of social insurance number with checking the checksum
70+ * Validation according to https://www.sozialversicherungsnummer.ch/aufbau-neu.htm
71+ * @param socialInsuranceNumber The social insurance number to check
72+ * Must be in one of the following formats:
73+ * - "756.XXXX.XXXX.XX" with dots as separators
74+ * - "756XXXXXXXXXX" with digits only
75+ * @returns The result if the social insurance number is valid or not
76+ */
77+ export function isValidSwissSocialSecurityNumber ( socialInsuranceNumber : string ) : boolean {
78+ // 1. Check if input is empty or only whitespace
79+ if ( isNullOrWhitespace ( socialInsuranceNumber ) ) {
80+ return false ;
81+ }
82+
83+ /**
84+ * 2. Check if input matches accepted formats:
85+ * - With dots: 756.XXXX.XXXX.XX
86+ * - Without dots: 756XXXXXXXXXX
87+ */
88+ const socialInsuranceNumberWithDots = new RegExp ( / ^ 7 5 6 \. \d { 4 } \. \d { 4 } \. \d { 2 } $ / ) ;
89+ const socialInsuranceNumberWithoutDots = new RegExp ( / ^ 7 5 6 \d { 10 } $ / ) ;
90+
91+ if ( ! socialInsuranceNumberWithDots . test ( socialInsuranceNumber ) && ! socialInsuranceNumberWithoutDots . test ( socialInsuranceNumber ) ) {
92+ return false ;
93+ }
94+
95+ // 3. Remove all dots → get a string of 13 digits
96+ const compactNumber = socialInsuranceNumber . replaceAll ( "." , "" ) ;
97+
98+ /**
99+ * 4. Separate digits for checksum calculation
100+ * - first 12 digits: used to calculate checksum
101+ * - last digit: actual check digit
102+ */
103+ const digits = compactNumber . slice ( 0 , - 1 ) ;
104+ const reversedDigits = [ ...digits ] . reverse ( ) . join ( "" ) ;
105+ const reversedDigitsArray = [ ...reversedDigits ] ;
106+
107+ /*
108+ * 5. Calculate weighted sum for checksum
109+ * - Even positions (after reversing) ×3
110+ * - Odd positions ×1
111+ */
112+ let sum = 0 ;
113+ for ( const [ i , element ] of reversedDigitsArray . entries ( ) ) {
114+ sum += i % 2 === 0 ? Number ( element ) * 3 : Number ( element ) * 1 ;
115+ }
116+
117+ /*
118+ * 6. Calculate expected check digit
119+ * - Check digit = value to reach next multiple of 10
120+ */
121+ const checksum = ( 10 - ( sum % 10 ) ) % 10 ;
122+ const checknumber = Number . parseInt ( compactNumber . slice ( - 1 ) ) ;
123+
124+ /*
125+ * 7. Compare calculated check digit with actual last digit
126+ * - If equal → valid AHV number
127+ */
128+ return checksum === checknumber ;
129+ }
0 commit comments