@@ -910,9 +910,61 @@ function plPlCheck(tin) {
910910* pt-BR validation function
911911* (Cadastro de Pessoas Físicas (CPF, persons)
912912* Cadastro Nacional de Pessoas Jurídicas (CNPJ, entities)
913- * Both inputs will be validated
913+ * Both inputs will be validated.
914+ * CNPJ supports both numeric (legacy) and alphanumeric format (starting July 2026).
914915*/
915916
917+ /**
918+ * Convert a CNPJ character to its numeric value for check digit calculation.
919+ * Numbers 0-9 map to values 0-9, letters A-Z map to values 17-42.
920+ * This is done by subtracting 48 from the ASCII code.
921+ */
922+ function cnpjCharToValue ( char ) {
923+ return char . charCodeAt ( 0 ) - 48 ;
924+ }
925+
926+ /**
927+ * Validate CNPJ (both numeric and alphanumeric formats).
928+ * Algorithm: module 11 with weights 2-9 from right to left.
929+ */
930+ function validateCnpj ( cnpj ) {
931+ // Get the 12 identifier characters and 2 check digits
932+ const identifiers = cnpj . substring ( 0 , 12 ) . toUpperCase ( ) ;
933+ const checkDigits = cnpj . substring ( 12 ) ;
934+
935+ // Reject CNPJs with all same characters (e.g., '00000000000000', 'AAAAAAAAAAAAAA')
936+ if ( / ^ ( .) \1+ $ / . test ( cnpj . toUpperCase ( ) ) ) {
937+ return false ;
938+ }
939+
940+ // Calculate first check digit
941+ let sum = 0 ;
942+ let weight = 5 ;
943+ for ( let i = 0 ; i < 12 ; i ++ ) {
944+ sum += cnpjCharToValue ( identifiers . charAt ( i ) ) * weight ;
945+ weight = weight === 2 ? 9 : weight - 1 ;
946+ }
947+ let remainder = sum % 11 ;
948+ let firstDV = remainder < 2 ? 0 : 11 - remainder ;
949+
950+ if ( firstDV !== parseInt ( checkDigits . charAt ( 0 ) , 10 ) ) {
951+ return false ;
952+ }
953+
954+ // Calculate second check digit (includes first check digit)
955+ sum = 0 ;
956+ weight = 6 ;
957+ for ( let i = 0 ; i < 12 ; i ++ ) {
958+ sum += cnpjCharToValue ( identifiers . charAt ( i ) ) * weight ;
959+ weight = weight === 2 ? 9 : weight - 1 ;
960+ }
961+ sum += firstDV * 2 ;
962+ remainder = sum % 11 ;
963+ let secondDV = remainder < 2 ? 0 : 11 - remainder ;
964+
965+ return secondDV === parseInt ( checkDigits . charAt ( 1 ) , 10 ) ;
966+ }
967+
916968function ptBrCheck ( tin ) {
917969 if ( tin . length === 11 ) {
918970 let sum ;
@@ -946,45 +998,8 @@ function ptBrCheck(tin) {
946998 return true ;
947999 }
9481000
949- if ( // Reject know invalid CNPJs
950- tin === '00000000000000' ||
951- tin === '11111111111111' ||
952- tin === '22222222222222' ||
953- tin === '33333333333333' ||
954- tin === '44444444444444' ||
955- tin === '55555555555555' ||
956- tin === '66666666666666' ||
957- tin === '77777777777777' ||
958- tin === '88888888888888' ||
959- tin === '99999999999999' ) { return false ; }
960-
961- let length = tin . length - 2 ;
962- let identifiers = tin . substring ( 0 , length ) ;
963- let verificators = tin . substring ( length ) ;
964- let sum = 0 ;
965- let pos = length - 7 ;
966-
967- for ( let i = length ; i >= 1 ; i -- ) {
968- sum += identifiers . charAt ( length - i ) * pos ;
969- pos -= 1 ;
970- if ( pos < 2 ) { pos = 9 ; }
971- }
972- let result = sum % 11 < 2 ? 0 : 11 - ( sum % 11 ) ;
973- if ( result !== parseInt ( verificators . charAt ( 0 ) , 10 ) ) { return false ; }
974-
975- length += 1 ;
976- identifiers = tin . substring ( 0 , length ) ;
977- sum = 0 ;
978- pos = length - 7 ;
979- for ( let i = length ; i >= 1 ; i -- ) {
980- sum += identifiers . charAt ( length - i ) * pos ;
981- pos -= 1 ;
982- if ( pos < 2 ) { pos = 9 ; }
983- }
984- result = sum % 11 < 2 ? 0 : 11 - ( sum % 11 ) ;
985- if ( result !== parseInt ( verificators . charAt ( 1 ) , 10 ) ) { return false ; }
986-
987- return true ;
1001+ // CNPJ validation (supports both numeric and alphanumeric formats)
1002+ return validateCnpj ( tin ) ;
9881003}
9891004
9901005/*
@@ -1190,7 +1205,7 @@ const taxIdFormat = {
11901205 'mt-MT' : / ^ \d { 3 , 7 } [ A P M G L H B Z ] $ | ^ ( [ 1 - 8 ] ) \1\d { 7 } $ / i,
11911206 'nl-NL' : / ^ \d { 9 } $ / ,
11921207 'pl-PL' : / ^ \d { 10 , 11 } $ / ,
1193- 'pt-BR' : / (?: ^ \d { 11 } $ ) | (?: ^ \d { 14 } $ ) / ,
1208+ 'pt-BR' : / (?: ^ \d { 11 } $ ) | (?: ^ [ A - Z 0 - 9 ] { 12 } \d { 2 } $ ) / i ,
11941209 'pt-PT' : / ^ \d { 9 } $ / ,
11951210 'ro-RO' : / ^ \d { 13 } $ / ,
11961211 'sk-SK' : / ^ \d { 6 } \/ { 0 , 1 } \d { 3 , 4 } $ / ,
0 commit comments