From 6b179cbd995fe58b6e9cf367dabcef99cb1c4b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Kallehauge?= <3846700+kallehauge@users.noreply.github.com> Date: Mon, 12 Oct 2026 14:46:50 +0200 Subject: [PATCH] fix(bg/vat): check only the length of 10-digit numbers A 10-digit Bulgarian VAT number belongs to an individual, and since 1 January 2024 it is an official number issued by the National Revenue Agency, for which no check digit algorithm is published. The previous check for these numbers compared the computed digit with != instead of ==, so it accepted almost every wrong check digit and rejected the matching one. --- src/bg/vat.spec.ts | 26 +++++++++++++++++++++++++- src/bg/vat.ts | 29 ++++------------------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/bg/vat.spec.ts b/src/bg/vat.spec.ts index 4d5d34fd..a42aa3ed 100644 --- a/src/bg/vat.spec.ts +++ b/src/bg/vat.spec.ts @@ -1,5 +1,5 @@ import { validate, format } from './vat'; -import { InvalidLength, InvalidChecksum } from '../exceptions'; +import { InvalidLength, InvalidChecksum, InvalidFormat } from '../exceptions'; describe('bg/vat', () => { it('format:BG 175 074 752', () => { @@ -31,4 +31,28 @@ describe('bg/vat', () => { expect(result.error).toBeInstanceOf(InvalidChecksum); }); + + // 10-digit VAT numbers belong to individuals. Since 2024 they are an + // official number issued by the National Revenue Agency, for which no check + // digit algorithm is published, so only the length and the digits are checked. + test.each(['BG1234567890', 'BG1234567892', '9067903772', '9067903779'])( + 'validate:%s', + value => { + const result = validate(value); + + expect(result.isValid && result.isIndividual).toEqual(true); + }, + ); + + it('validate:BG12345678901', () => { + const result = validate('BG12345678901'); + + expect(result.error).toBeInstanceOf(InvalidLength); + }); + + it('validate:BG123456789A', () => { + const result = validate('BG123456789A'); + + expect(result.error).toBeInstanceOf(InvalidFormat); + }); }); diff --git a/src/bg/vat.ts b/src/bg/vat.ts index 0da3c8f3..da8d72bf 100644 --- a/src/bg/vat.ts +++ b/src/bg/vat.ts @@ -3,14 +3,14 @@ * * The Bulgarian VAT (Данък върху добавената стойност) number is either 9 * (for legal entities) or 10 digits (for physical persons, foreigners and - * others) long. Each type of number has its own check digit algorithm. + * others) long. A 9-digit number carries a check digit. A 10-digit number is, + * since 2024, an official number issued by the National Revenue Agency, which + * publishes no check digit algorithm for it, so only its length is checked. * * PERSON/ENTITY */ import * as exceptions from '../exceptions'; -import * as egn from './egn'; -import * as pnf from './pnf'; import { strings } from '../util'; import { Validator, ValidateReturn } from '../types'; import { weightedSum } from '../util/checksum'; @@ -45,19 +45,6 @@ function checkLegal(value: string): boolean { return String(sum % 10) === check; } -function checkOther(value: string): boolean { - const [front, check] = strings.splitAt(value, -1); - - const sum = - 11 - - weightedSum(front, { - modulus: 11, - weights: [4, 3, 2, 7, 6, 5, 4, 3, 2], - }); - - return String(sum % 10) !== check; -} - const impl: Validator = { name: 'Bulgarian VAT Number', localName: 'Идентификационен номер по ДДС', @@ -95,15 +82,7 @@ const impl: Validator = { return { isValid: false, error: new exceptions.InvalidFormat() }; } - if (value.length === 9) { - if (!checkLegal(value)) { - return { isValid: false, error: new exceptions.InvalidChecksum() }; - } - } else if ( - !egn.validate(value).isValid && - !pnf.validate(value).isValid && - !checkOther(value) - ) { + if (value.length === 9 && !checkLegal(value)) { return { isValid: false, error: new exceptions.InvalidChecksum() }; }