Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
042b2b9
feat(finance): add vatNumber()
rodrigobnogueira Aug 9, 2026
477f075
refactor(finance): address review feedback on vatNumber()
rodrigobnogueira Aug 9, 2026
9c0ea3f
refactor(finance): apply second review round on vatNumber()
rodrigobnogueira Aug 11, 2026
af4fc99
fix(finance): guard vatNumber() against inherited keys and even out t…
rodrigobnogueira Aug 11, 2026
d1a8555
fix(finance): stop vatNumber() emitting numbers that cannot be issued
rodrigobnogueira Aug 11, 2026
ff25c85
docs(finance): correct the vatNumber comments to match what it generates
rodrigobnogueira Aug 13, 2026
df6f7fe
Merge remote-tracking branch 'upstream/next' into feat/finance-vat-nu…
rodrigobnogueira Aug 23, 2026
bf19bac
Merge remote-tracking branch 'upstream/next' into feat/finance-vat-nu…
rodrigobnogueira Aug 25, 2026
619af0a
docs(finance): set vatNumber's @since to the release it will ship in
rodrigobnogueira Aug 25, 2026
cfa02b2
test(finance): cover both shapes of the two-pattern countries
rodrigobnogueira Aug 25, 2026
0de4b53
Merge remote-tracking branch 'upstream/next' into feat/finance-vat-nu…
rodrigobnogueira Sep 4, 2026
a7ac68b
refactor(finance): apply review feedback on vatNumber()
rodrigobnogueira Sep 5, 2026
737f5d4
docs(finance): describe the keying of vatNumberFormats as it is now
rodrigobnogueira Sep 5, 2026
f907215
test(finance): assert VatNumberCountryCode is accepted by isVAT
rodrigobnogueira Sep 6, 2026
e1bf4ae
test(finance): snapshot the VAT patterns instead of restating them
rodrigobnogueira Sep 11, 2026
e29dc2b
test(finance): snapshot a generated VAT number per country
rodrigobnogueira Sep 12, 2026
edfcba1
Merge branch 'next' into feat/finance-vat-number
ST-DDT Sep 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ export type { CompanyModule } from './modules/company';
export type { DatabaseModule } from './modules/database';
export type { DatatypeModule } from './modules/datatype';
export type { DateModule, SimpleDateModule } from './modules/date';
export type { Currency, FinanceModule } from './modules/finance';
export type {
Currency,
FinanceModule,
VatNumberCountryCode,
} from './modules/finance';
export {
BitcoinAddressFamily,
BitcoinNetwork,
Expand Down
115 changes: 115 additions & 0 deletions src/modules/finance/_vat-number.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* The VAT identification number patterns of the EU member states, keyed by
* ISO 3166-1 alpha-2 code, plus `EL`: the prefix Greek numbers carry in place
* of `GR`.
*
* Each pattern is written for `faker.helpers.fromRegExp()`.
* Currently, all values are generated randomly, so parts with intent such as check digits will likely produce invalid values.
*/
export const vatNumberFormats = {
/** UID-Nummer. */
AT: 'ATU[0-9]{8}',
Comment thread
ST-DDT marked this conversation as resolved.
/**
* BTW-nummer. Begins with 0 or 1: the 2005 ten-digit form zero-padded the
* older nine-digit numbers, and the 1 series was opened later, once the 0
* series neared exhaustion.
*/
BE: 'BE[01][0-9]{9}',
/** DDS nomer. Nine digits for legal entities, ten for individuals. */
BG: 'BG[0-9]{9,10}',
/**
* FPA. Numbers begin 0, 1, 3, 4, 5 or 9 under the legacy categories, or 6
* under the format introduced in March 2023; 2, 7 and 8 are not issued.
*/
CY: 'CY[0134569][0-9]{7}[A-Z]',
/** DIC. */
CZ: 'CZ[0-9]{8,10}',
/** Umsatzsteuer-Identifikationsnummer. */
DE: 'DE[0-9]{9}',
/** CVR-nummer. */
DK: 'DK[0-9]{8}',
/** KMKR number. */
EE: 'EE[0-9]{9}',
/** AFM. */
EL: 'EL[0-9]{9}',
/**
* NIF/CIF for entities. The leading character encodes the legal form, which
* constrains the control character: A, B, E and H always take a digit,
* C, D, F, G, J, U and V may take either, and foreign entities, public
* bodies, local corporations and religious congregations (N, P, Q, R, S, W)
* always take a letter. The two branches are separate patterns because one
* character class would pair the positions freely and emit combinations that
* are never issued; the may-take-either letters are generated with a digit,
* which under-generates rather than over-generates. The natural-person forms
* are out of scope.
*/
ES: ['ES[ABCDEFGHJUV][0-9]{7}[0-9]', 'ES[NPQRSW][0-9]{7}[A-J]'],
/** ALV-numero. */
FI: 'FI[0-9]{8}',
/**
* Numero de TVA intracommunautaire: a two-character key followed by the
* nine-digit SIREN. The letters I and O are not used in the key.
*/
FR: 'FR[0-9ABCDEFGHJKLMNPQRSTUVWXYZ]{2}[0-9]{9}',
/** AFM. Same as `EL`. */
GR: 'EL[0-9]{9}',
/** PDV ID. */
HR: 'HR[0-9]{11}',
/** Kozossegi adoszam. */
HU: 'HU[0-9]{8}',
/**
* VAT number: seven digits and a check letter, optionally followed by a
* `W` — the legacy marker for a married woman registered on her husband's
* number. The 2013 form, whose second letter runs A to I rather than being
* `W`, is not modelled, nor is the older form that carries a symbol.
*/
IE: 'IE[0-9]{7}[A-W]W{0,1}',
/** Partita IVA. */
IT: 'IT[0-9]{11}',
/**
* PVM kodas. Nine digits for legal entities, where the eighth is always 1
* and the ninth is a check digit. The twelve-digit form for temporarily
* registered taxpayers is rare and not modelled — note it could not simply
* widen this pattern to `[0-9]{9,12}`, since the lengths in between are
* never issued.
*/
LT: 'LT[0-9]{7}1[0-9]',
/** Numero de TVA. */
LU: 'LU[0-9]{8}',
/** PVN numurs. */
LV: 'LV[0-9]{11}',
/** VAT number. */
MT: 'MT[0-9]{8}',
/**
* Btw-identificatienummer. The two digits after the `B` are a company index
* running from 01 to 99, so `B00` is never issued — which takes two patterns
* to express, since a plain digit pair would include it.
*/
NL: ['NL[0-9]{9}B0[1-9]', 'NL[0-9]{9}B[1-9][0-9]'],
/** NIP. */
PL: 'PL[0-9]{10}',
/** Numero de identificacao fiscal. No taxpayer range begins with zero. */
PT: 'PT[1-9][0-9]{8}',
/** Cod de identificare fiscala. Between 2 and 10 digits, never leading zero. */
RO: 'RO[1-9][0-9]{1,9}',
/**
* Momsnummer: the ten-digit organisationsnummer followed by a two-digit
* establishment number. Only `01` is generated — it is the value for all but
* a handful of multi-establishment registrations.
*/
SE: 'SE[0-9]{10}01',
/** ID za DDV. Eight digits, never a leading zero. */
SI: 'SI[1-9][0-9]{7}',
/** IC DPH. */
SK: 'SK[0-9]{10}',
} as const satisfies Record<string, string | ReadonlyArray<string>>;

/** The codes to draw from, minus `GR`, which would give Greece double weight. */
export const vatNumberCountryCodes = Object.keys(vatNumberFormats).filter(
(code) => code !== 'GR'
) as VatNumberCountryCode[];

/**
* The country codes for which a VAT identification number can be generated.
*/
export type VatNumberCountryCode = keyof typeof vatNumberFormats;
1 change: 1 addition & 0 deletions src/modules/finance/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './module';
export type { VatNumberCountryCode } from './_vat-number';
58 changes: 58 additions & 0 deletions src/modules/finance/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
BitcoinNetwork,
} from './_bitcoin';
import iban from './_iban';
import type { VatNumberCountryCode } from './_vat-number';
import { vatNumberCountryCodes, vatNumberFormats } from './_vat-number';

/**
* The possible definitions related to currency entries.
Expand Down Expand Up @@ -58,6 +60,8 @@ export function prettyPrintIban(iban: string): string {
*
* For traditional bank accounts, use: [`accountNumber()`](https://fakerjs.dev/api/finance.html#accountnumber), [`accountName()`](https://fakerjs.dev/api/finance.html#accountname), [`bic()`](https://fakerjs.dev/api/finance.html#bic), [`iban()`](https://fakerjs.dev/api/finance.html#iban), [`pin()`](https://fakerjs.dev/api/finance.html#pin) and [`routingNumber()`](https://fakerjs.dev/api/finance.html#routingnumber).
*
* For tax identifiers, use [`vatNumber()`](https://fakerjs.dev/api/finance.html#vatnumber).
*
* For credit card related methods, use: [`creditCardNumber()`](https://fakerjs.dev/api/finance.html#creditcardnumber), [`creditCardCVV()`](https://fakerjs.dev/api/finance.html#creditcardcvv), [`creditCardIssuer()`](https://fakerjs.dev/api/finance.html#creditcardissuer), [`transactionDescription()`](https://fakerjs.dev/api/finance.html#transactiondescription) and [`transactionType()`](https://fakerjs.dev/api/finance.html#transactiontype).
*
* For blockchain related methods, use: [`bitcoinAddress()`](https://fakerjs.dev/api/finance.html#bitcoinaddress), [`ethereumAddress()`](https://fakerjs.dev/api/finance.html#ethereumaddress) and [`litecoinAddress()`](https://fakerjs.dev/api/finance.html#litecoinaddress).
Expand Down Expand Up @@ -849,6 +853,60 @@ export class FinanceModule extends ModuleBase {
return `${bankIdentifier}${countryCode}${locationCode}${branchCode}`;
}

/**
* Generates a random VAT identification number for one of the EU member states.
*
* The supported country codes are the EU member states, using the two-letter code each
* country's numbers carry:
* `AT`, `BE`, `BG`, `CY`, `CZ`, `DE`, `DK`, `EE`, `EL` (or `GR`), `ES`, `FI`, `FR`, `HR`, `HU`,
* `IE`, `IT`, `LT`, `LU`, `LV`, `MT`, `NL`, `PL`, `PT`, `RO`, `SE`, `SI` and `SK`.
*
* @remark Please note that this currently only generates the structure of the respective country's VAT identification.
* But it will return random values for digits with intent such as check digits, so the result is likely to be invalid.
*
* @param options An options object.
* @param options.countryCode The two-letter code of the country you want a VAT number for.
* Greece may be given as either `GR` or `EL`.
* Defaults to a random supported country.
*
* @throws {FakerError} Will throw an error if the passed country code is not supported.
*
* @example
* faker.finance.vatNumber() // 'SK4318759382'
* faker.finance.vatNumber({ countryCode: 'DE' }) // 'DE644073457'
* faker.finance.vatNumber({ countryCode: 'NL' }) // 'NL840351580B96'
* faker.finance.vatNumber({ countryCode: 'GR' }) // 'EL892156043'
*
* @since 11.0.0
*/
vatNumber(
options: {
/**
* The two-letter code of the country you want a VAT number for.
* Greece may be given as either `GR` or `EL`.
*
* @default faker.helpers.arrayElement(vatNumberCountryCodes)
*/
countryCode?: VatNumberCountryCode;
Comment thread
xDivisionByZerox marked this conversation as resolved.
} = {}
): string {
const {
countryCode = this.faker.helpers.arrayElement(vatNumberCountryCodes),
} = options;

if (!Object.hasOwn(vatNumberFormats, countryCode)) {
throw new FakerError(`Country code ${countryCode} not supported.`);
}

const pattern = vatNumberFormats[countryCode];

return this.faker.helpers.fromRegExp(
typeof pattern === 'string'
? pattern
: this.faker.helpers.arrayElement(pattern)
);
}

/**
* Generates a random transaction description.
*
Expand Down
51 changes: 51 additions & 0 deletions test/modules/__snapshots__/finance.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ exports[`finance > 42 > transactionDescription 1`] = `"You made a withdrawal of

exports[`finance > 42 > transactionType 1`] = `"invoice"`;

exports[`finance > 42 > vatNumber > noArgs 1`] = `"FI97511086"`;

exports[`finance > 42 > vatNumber > with a variable length countryCode option 1`] = `"RO4751108670"`;

exports[`finance > 42 > vatNumber > with countryCode option 1`] = `"DE397511086"`;

exports[`finance > 1211 > accountName 1`] = `"Savings Account"`;

exports[`finance > 1211 > accountNumber > noArgs 1`] = `"98296673"`;
Expand Down Expand Up @@ -162,6 +168,12 @@ exports[`finance > 1211 > transactionDescription 1`] = `"withdrawal transaction

exports[`finance > 1211 > transactionType 1`] = `"withdrawal"`;

exports[`finance > 1211 > vatNumber > noArgs 1`] = `"SI92966736"`;

exports[`finance > 1211 > vatNumber > with a variable length countryCode option 1`] = `"RO9296673687"`;

exports[`finance > 1211 > vatNumber > with countryCode option 1`] = `"DE982966736"`;

exports[`finance > 1337 > accountName 1`] = `"Credit Card Account"`;

exports[`finance > 1337 > accountNumber > noArgs 1`] = `"21243529"`;
Expand Down Expand Up @@ -242,3 +254,42 @@ exports[`finance > 1337 > routingNumber 1`] = `"051412430"`;
exports[`finance > 1337 > transactionDescription 1`] = `"Payment of CAD 278.12 for invoice at Wolf - Howe, processed with card ending ****6194 linked to account ***7734."`;

exports[`finance > 1337 > transactionType 1`] = `"invoice"`;

exports[`finance > 1337 > vatNumber > noArgs 1`] = `"EE124352971"`;

exports[`finance > 1337 > vatNumber > with a variable length countryCode option 1`] = `"RO324"`;

exports[`finance > 1337 > vatNumber > with countryCode option 1`] = `"DE212435297"`;

exports[`finance > should generate a stable VAT number per country 1`] = `
{
"AT": "ATU39751108",
"BE": "BE1709821135",
"BG": "BG261234715",
"CY": "CY50610998H",
"CZ": "CZ64140926",
"DE": "DE355197985",
"DK": "DK90103328",
"EE": "EE325180971",
"EL": "EL087770318",
"ES": "ESP0337684B",
"FI": "FI77574540",
"FR": "FR31635924720",
"GR": "EL219868818",
"HR": "HR58831248805",
"HU": "HU42139357",
"IE": "IE9924320O",
"IT": "IT50292149267",
"LT": "LT273665018",
"LU": "LU31056052",
"LV": "LV61639131982",
"MT": "MT68552089",
"NL": "NL337887601B96",
"PL": "PL0160156627",
"PT": "PT337686503",
"RO": "RO3938",
"SE": "SE675541720601",
"SI": "SI29993094",
"SK": "SK9982383159",
}
`;
12 changes: 12 additions & 0 deletions test/modules/finance.spec-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { VATCountryCode } from 'validator';
import { describe, expectTypeOf, it } from 'vitest';
import type { VatNumberCountryCode } from '../../src/modules/finance/_vat-number';

describe('finance', () => {
describe('vatNumber', () => {
it('VatNumberCountryCode', () => {
expectTypeOf<VatNumberCountryCode>().toExtend<VATCountryCode | 'GR'>();
// Currently the reverse is not true
});
});
});
Loading