diff --git a/src/lib/isVAT.js b/src/lib/isVAT.js index 95593569d..ece7d8560 100644 --- a/src/lib/isVAT.js +++ b/src/lib/isVAT.js @@ -1,6 +1,21 @@ import assertString from './util/assertString'; import * as algorithms from './util/algorithms'; +const CH = (str) => { + // @see {@link https://www.ech.ch/de/ech/ech-0097/5.2.0} + const hasValidCheckNumber = (digits) => { + const lastDigit = digits.pop(); // used as check number + const weights = [5, 4, 3, 2, 7, 6, 5, 4]; + const calculatedCheckNumber = (11 - (digits.reduce((acc, el, idx) => + acc + (el * weights[idx]), 0) % 11)) % 11; + + return lastDigit === calculatedCheckNumber; + }; + + // @see {@link https://www.estv.admin.ch/estv/de/home/mehrwertsteuer/uid/mwst-uid-nummer.html} + return /^(CHE[- ]?)?(\d{9}|(\d{3}\.\d{3}\.\d{3})|(\d{3} \d{3} \d{3})) ?(TVA|MWST|IVA)?$/.test(str) && hasValidCheckNumber((str.match(/\d/g).map(el => +el))); +}; + const PT = (str) => { const match = str.match(/^(PT)?(\d{9})$/); if (!match) { @@ -69,7 +84,7 @@ export const vatMatchers = { SM: str => /^(SM)?\d{5}$/.test(str), SA: str => /^(SA)?\d{15}$/.test(str), RS: str => /^(RS)?\d{9}$/.test(str), - CH: str => /^(CH)?(\d{6}|\d{9}|(\d{3}.\d{3})|(\d{3}.\d{3}.\d{3}))(TVA|MWST|IVA)$/.test(str), + CH, TR: str => /^(TR)?\d{10}$/.test(str), UA: str => /^(UA)?\d{12}$/.test(str), GB: str => /^GB((\d{3} \d{4} ([0-8][0-9]|9[0-6]))|(\d{9} \d{3})|(((GD[0-4])|(HA[5-9]))[0-9]{2}))$/.test(str), diff --git a/test/validators.test.js b/test/validators.test.js index 6816a3bbb..90708637a 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -13865,18 +13865,30 @@ describe('Validators', () => { validator: 'isVAT', args: ['CH'], valid: [ - 'CH123456TVA', - '123456TVA', - 'CH123456789MWST', - '123456789MWST', - 'CH123.456IVA', - '123.456IVA', - 'CH123.456.789TVA', - '123.456.789TVA', - ], - invalid: [ - 'CH 123456', - '12345', + // strictly valid + 'CHE-116.281.710 MWST', + 'CHE-116.281.710 IVA', + 'CHE-116.281.710 TVA', + // loosely valid presentation variants + 'CHE 116 281 710 IVA', // all separators are spaces + 'CHE-191.398.369MWST', // no space before suffix + 'CHE-116281710 MWST', // no number separators + 'CHE-116281710MWST', // no number separators and no space before suffix + 'CHE105854263MWST', // no separators + 'CHE-116.285.524', // no suffix (vat abbreviation) + 'CHE116281710', // no suffix and separators + '116.281.710 TVA', // no prefix (CHE, ISO-3166-1 Alpha-3) + '116281710MWST', // no prefix and separators + '100.218.485', // no prefix and suffix + '123456788', // no prefix, separators and suffix + ], + invalid: [ + 'CH-116.281.710 MWST', // invalid prefix (should be CHE) + 'CHE-116.281 MWST', // invalid number of digits (should be 9) + 'CHE-123.456.789 MWST', // invalid last digit (should match the calculated check-number 8) + 'CHE-123.356.780 MWST', // invalid check-number (there are no swiss UIDs with the calculated check number 10) + 'CH-116.281.710 VAT', // invalid suffix (should be MWST, IVA or TVA) + 'CHE-116/281/710 IVA', // invalid number separators (should be all dots or all spaces) ], }); test({