diff --git a/src/mx/rfc.spec.ts b/src/mx/rfc.spec.ts index dff5133b..f96b1a1d 100644 --- a/src/mx/rfc.spec.ts +++ b/src/mx/rfc.spec.ts @@ -37,11 +37,13 @@ describe('mx/rfc', () => { expect(result.isValid && result.compact).toEqual('SALC7304253S0'); }); - // it('validate:XEXX010101000', () => { - // const result = validate('XEXX010101000'); + it('validate:XEXX010101000', () => { + // Generic RFC for foreign residents; its serial/check digits do not + // follow the check digit algorithm but it is a real, valid RFC + const result = validate('XEXX010101000'); - // expect(result.isValid && result.compact).toEqual('XEXX010101000'); - // }); + expect(result.isValid && result.compact).toEqual('XEXX010101000'); + }); it('validate:RET130705MD5', () => { const result = validate('RET130705MD5'); @@ -56,9 +58,19 @@ describe('mx/rfc', () => { }); it('validate:VACE-460910-SX6', () => { + // python-stdnum docstring example: fails the check digit algorithm but + // is accepted because check digit validation is disabled by default const result = validate('VACE-460910-SX6'); - expect(result.isValid).toEqual(false); + expect(result.isValid && result.compact).toEqual('VACE460910SX6'); + }); + + it('validate:PAF-070213-GI1', () => { + // SAT-issued company RFC whose check digit does not match the published + // algorithm (expected 'A'); must validate like python-stdnum does + const result = validate('PAF-070213-GI1'); + + expect(result.isValid && result.compact).toEqual('PAF070213GI1'); }); it('validate:SOTO800101110', () => { diff --git a/src/mx/rfc.ts b/src/mx/rfc.ts index 2961ef5d..b4adee93 100644 --- a/src/mx/rfc.ts +++ b/src/mx/rfc.ts @@ -29,7 +29,7 @@ */ import * as exceptions from '../exceptions'; -import { isValidDateCompactYYMMDD, strings, weightedSum } from '../util'; +import { isValidDateCompactYYMMDD, strings } from '../util'; import { Validator, ValidateReturn } from '../types'; function clean(input: string): ReturnType { @@ -80,13 +80,6 @@ const nameBlacklist = new Set([ 'RUIN', ]); -// Official alphabet per SAT (Anexo 20). Includes '&' and 'Ñ'. -const checkAlphabet = '0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ Ñ'; - -// Legacy alphabet (Base 36) used in older systems. -// It excludes '&' and 'Ñ'. Space is added to support padding for companies. -const checkAlphabetLegacy = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ '; - const impl: Validator = { name: 'Mexican Tax Number', localName: 'Registro Federal de Contribuyentes', @@ -145,42 +138,6 @@ const impl: Validator = { return { isValid: false, error: new exceptions.InvalidLength() }; } - if (value.length >= 12) { - if (!/[1-9A-V][1-9A-Z][0-9A]$/.test(value)) { - return { isValid: false, error: new exceptions.InvalidComponent() }; - } - - const [front, check] = strings.splitAt(value, -1); - const paddedInput = front.padStart(12, ' '); - - const calculateChecksum = (alphabet: string) => { - const sum = weightedSum(paddedInput, { - modulus: 11, - alphabet: alphabet, - weights: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], - reverse: true, - }); - - const mod = 11 - (sum % 11); - if (mod === 11) return '0'; - if (mod === 10) return 'A'; - return String(mod); - }; - - // Try with official SAT alphabet first - const valOfficial = calculateChecksum(checkAlphabet); - - if (check !== valOfficial) { - // If it fails, try with Legacy alphabet (Base 36) - // This handles older RFCs generated without '&' or 'Ñ' support - const valLegacy = calculateChecksum(checkAlphabetLegacy); - - if (check !== valLegacy) { - return { isValid: false, error: new exceptions.InvalidChecksum() }; - } - } - } - return { isValid: true, compact: value, @@ -191,4 +148,4 @@ const impl: Validator = { }; export const { name, localName, abbreviation, validate, format, compact } = - impl; \ No newline at end of file + impl;