@@ -109,3 +109,66 @@ export function isValidSwissIbanNumber(ibanNumber: string): boolean {
109109 // 8. IBAN is valid only if the remainder equals 1
110110 return restOfCalculation === 1 ;
111111}
112+
113+ /**
114+ * Validation of social insurance number with checking the checksum
115+ * Validation according to https://www.sozialversicherungsnummer.ch/aufbau-neu.htm
116+ * @param socialInsuranceNumber The social insurance number to check
117+ * Must be in one of the following formats:
118+ * - "756.XXXX.XXXX.XX" with dots as separators
119+ * - "756XXXXXXXXXX" with digits only
120+ * @returns The result if the social insurance number is valid or not
121+ */
122+ export function isValidSwissSocialSecurityNumber ( socialInsuranceNumber : string ) : boolean {
123+ // 1. Check if input is empty or only whitespace
124+ if ( isNullOrWhitespace ( socialInsuranceNumber ) ) {
125+ return false ;
126+ }
127+
128+ /**
129+ * 2. Check if input matches accepted formats:
130+ * - With dots: 756.XXXX.XXXX.XX
131+ * - Without dots: 756XXXXXXXXXX
132+ */
133+ const socialInsuranceNumberWithDots = new RegExp ( / ^ 7 5 6 \. \d { 4 } \. \d { 4 } \. \d { 2 } $ / ) ;
134+ const socialInsuranceNumberWithoutDots = new RegExp ( / ^ 7 5 6 \d { 10 } $ / ) ;
135+
136+ if ( ! socialInsuranceNumberWithDots . test ( socialInsuranceNumber ) && ! socialInsuranceNumberWithoutDots . test ( socialInsuranceNumber ) ) {
137+ return false ;
138+ }
139+
140+ // 3. Remove all dots → get a string of 13 digits
141+ const compactNumber = socialInsuranceNumber . replaceAll ( "." , "" ) ;
142+
143+ /**
144+ * 4. Separate digits for checksum calculation
145+ * - first 12 digits: used to calculate checksum
146+ * - last digit: actual check digit
147+ */
148+ const digits = compactNumber . slice ( 0 , - 1 ) ;
149+ const reversedDigits = [ ...digits ] . reverse ( ) . join ( "" ) ;
150+ const reversedDigitsArray = [ ...reversedDigits ] ;
151+
152+ /*
153+ * 5. Calculate weighted sum for checksum
154+ * - Even positions (after reversing) ×3
155+ * - Odd positions ×1
156+ */
157+ let sum = 0 ;
158+ for ( const [ i , element ] of reversedDigitsArray . entries ( ) ) {
159+ sum += i % 2 === 0 ? Number ( element ) * 3 : Number ( element ) * 1 ;
160+ }
161+
162+ /*
163+ * 6. Calculate expected check digit
164+ * - Check digit = value to reach next multiple of 10
165+ */
166+ const checksum = ( 10 - ( sum % 10 ) ) % 10 ;
167+ const checknumber = Number . parseInt ( compactNumber . slice ( - 1 ) ) ;
168+
169+ /*
170+ * 7. Compare calculated check digit with actual last digit
171+ * - If equal → valid AHV number
172+ */
173+ return checksum === checknumber ;
174+ }
0 commit comments