Skip to content

fix(bg/vat): check only the length of 10-digit numbers - #173

Open
kallehauge wants to merge 1 commit into
koblas:mainfrom
kallehauge:fix/bg-vat-ten-digit-length
Open

kallehauge wants to merge 1 commit into
koblas:mainfrom
kallehauge:fix/bg-vat-ten-digit-length

Conversation

@kallehauge

Copy link
Copy Markdown

Problem

For 10-digit Bulgarian VAT numbers that are neither a valid EGN nor a valid PNF, bg/vat falls back to checkOther(), and that comparison is inverted:

return String(sum % 10) !== check; // true when the check digit does NOT match

validate() rejects the number when checkOther() is false, so a number is rejected exactly when its last digit matches the computed one, and accepted for almost any other last digit:

stdnum.BG.vat.validate('BG1234567890'); // valid
stdnum.BG.vat.validate('BG1234567892'); // InvalidChecksum: the digit matches the computed one

Why this PR checks only the length

Simply flipping the comparison would make the result trustworthy only if the algorithm itself were, and there is no official source for it:

  • The EU Commission does not publish the algorithms. VIES FAQ, Q11: "Is it possible to know the algorithms used by Member States in the construction of their VAT identification numbers? The European Commission cannot divulge these algorithms." It gives only the structure: BG + 9 or 10 digits (archived FAQ).
  • Since 1 January 2024 a 10-digit Bulgarian VAT number is an official number issued by the National Revenue Agency (NRA). Before that, an individual's VAT number was BG + EGN or personal number of a foreigner; the NRA replaced it with BG + an official number it generates, following an amendment of the rules implementing the VAT Act (State Gazette no. 55/2023) made to protect personal data (NRA announcement). So the EGN and PNF checks no longer describe the numbers in use.
  • No check digit rule is published for that official number. The EU's TIN country sheet for Bulgaria lists the "Official number from NRA Registry" as 10 digits and notes that it "cannot be validated in the on-line check module"; the OECD TIN sheet for Bulgaria describes it as "a block of 10 digits".
  • python-stdnum's rule has no documented basis either. I looked at python-stdnum to see what the check was meant to be. It accepts a 10-digit number when it is a valid EGN, a valid PNF, or its last digit matches calc_check_digit_other (weights 4, 3, 2, 7, 6, 5, 4, 3, 2). The module cites no source for that algorithm, and I could not find it in any Bulgarian or EU documentation. Copying it would replace one unsourced check with another.

With no published algorithm, the defensible check for a 10-digit number is the published structure: 10 digits. 9-digit numbers (legal entities, the ЕИК) keep their check digit.

Fix

  • 10-digit numbers: only the length and that every character is a digit are checked.
  • checkOther() and the EGN/PNF fallbacks in bg/vat are removed; bg/egn and bg/pnf themselves are unchanged.
  • The module docblock says why.
Input Before After
BG1234567890 valid valid
BG1234567892 InvalidChecksum valid
BG12345678901 InvalidLength InvalidLength
BG123456789A InvalidFormat InvalidFormat
BG175074752 (9 digits) valid valid
BG175074754 (9 digits, wrong check digit) InvalidChecksum InvalidChecksum

This touches the same lines as #172 (the first-digit rule for 9-digit numbers); whichever merges second needs a small conflict resolution.

Tested

  • src/bg/vat.spec.ts: 6 new cases. BG1234567892 fails before the change and passes after it; the other valid examples pass both before and after.
  • Full suite: TZ=UTC npx jest --runInBand passes, 200 suites / 1641 tests.
  • npm run lint and npm run build pass.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant