From 9955eb1019567e7d3f76dc7c8185aed0badb9c56 Mon Sep 17 00:00:00 2001 From: David Koblas Date: Wed, 15 Oct 2025 03:09:43 -0400 Subject: [PATCH 1/5] fix: add Anguilla tax id --- .github/workflows/release.yaml | 4 ++ README.md | 1 + src/ai/index.ts | 1 + src/ai/tin.spec.ts | 54 +++++++++++++++++++ src/ai/tin.ts | 96 ++++++++++++++++++++++++++++++++++ src/index.ts | 6 +++ 6 files changed, 162 insertions(+) create mode 100644 src/ai/index.ts create mode 100644 src/ai/tin.spec.ts create mode 100644 src/ai/tin.ts diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 15e2d420..553b1e71 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,5 +1,9 @@ name: Release +permissions: + id-token: write # Required for OIDC + contents: write + on: #push: # branches: diff --git a/README.md b/README.md index 822ebc50..bf85beb3 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ How you can help! This library currently support about half the countries in the | Country | Code | Name | Group | Meaning | | ---------------------- | ---- | --------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | | Andorra | AD | NRT | Tax | Tax Register Identifier (Número de Registre Tributari) | +| Anguilla | AI | TIN | Tax | Tax Identification Number | | Albania | AL | NIPT | Vat | Albanian Vat Identifier (Numri i Identifikimit për Personin e Tatueshëm) | | Argentina | AR | CBU | Bank | Single Banking Code (Clave Bancaria Uniforme) | | Argentina | AR | CUIT | Tax | Unique Tax Identification Code (Código Único de Identificación Tributaria) | diff --git a/src/ai/index.ts b/src/ai/index.ts new file mode 100644 index 00000000..110affee --- /dev/null +++ b/src/ai/index.ts @@ -0,0 +1 @@ +export * as tin from './tin'; diff --git a/src/ai/tin.spec.ts b/src/ai/tin.spec.ts new file mode 100644 index 00000000..0624b9ac --- /dev/null +++ b/src/ai/tin.spec.ts @@ -0,0 +1,54 @@ +import { validate, format } from './tin'; +import { InvalidLength, InvalidFormat } from '../exceptions'; + +describe('ai/tin', () => { + it('format:12345-6789', () => { + const result = format('123456789'); + + expect(result).toEqual('12345-6789'); + }); + + it('validate:12345-6789', () => { + const result = validate('12345-6789'); + + expect(result.isValid && result.compact).toEqual('123456789'); + }); + + test.each([ + '123456789', // Valid format + '12345-6789', // Valid format with hyphen + ])('validate:%s', value => { + const result = validate(value); + + expect(result.isValid).toEqual(true); + }); + + it('validate:12345678', () => { + const result = validate('12345678'); + + expect(result.error).toBeInstanceOf(InvalidLength); + }); + + it('validate:3234567890', () => { + const result = validate('3234567890'); + expect(result.isValid).toEqual(false); + }); + + it('validate:1234567890', () => { + const result = validate('1234567890'); + + expect(result.error).toBeInstanceOf(InvalidLength); + }); + + it('validate:12345A678', () => { + const result = validate('12345A678'); + + expect(result.error).toBeInstanceOf(InvalidFormat); + }); + + // it('validate:123456788', () => { + // const result = validate('123456788'); + + // expect(result.error).toBeInstanceOf(InvalidChecksum); + // }); +}); diff --git a/src/ai/tin.ts b/src/ai/tin.ts new file mode 100644 index 00000000..8cea6492 --- /dev/null +++ b/src/ai/tin.ts @@ -0,0 +1,96 @@ +/** + * TIN (Anguilla Tax Identification Number). + * + * The Anguilla Tax Identification Number is issued to individuals and entities + * for tax purposes. The number consists of 9 digits in the format XXXXX-XXXX + * where the first 5 digits represent the taxpayer's registration number and + * the last 4 digits are a sequence number. + * + * TINs are unique numbers automatically generated by the tax administration system + * and consists of 10 digits, including prefix digit and check digit + * - Individual TINs start with prefix 1 + * - Non-Individual (business) TINs start with prefix 2 + * + * Source + * https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/Anguilla-TIN.pdf + * + * PERSON/ENTITY + */ + +import * as exceptions from '../exceptions'; +import { strings } from '../util'; +import { Validator, ValidateReturn } from '../types'; +// import { weightedSum } from '../util/checksum'; + +function clean(input: string): ReturnType { + return strings.cleanUnicode(input, ' -'); +} + +const impl: Validator = { + name: 'Anguilla Tax Identification Number', + localName: 'Tax Identification Number', + abbreviation: 'TIN', + + compact(input: string): string { + const [value, err] = clean(input); + + if (err) { + throw err; + } + + return value; + }, + + format(input: string): string { + const [value] = clean(input); + + if (value.length !== 9) { + return value; + } + + return `${value.substring(0, 5)}-${value.substring(5)}`; + }, + + validate(input: string): ValidateReturn { + const [value, error] = clean(input); + + if (error) { + return { isValid: false, error }; + } + + if (value.length !== 9) { + return { isValid: false, error: new exceptions.InvalidLength() }; + } + + if (!strings.isdigits(value)) { + return { isValid: false, error: new exceptions.InvalidFormat() }; + } + + const prefix = value[0]; + + if (prefix !== '1' && prefix !== '2') { + return { isValid: false, error: new exceptions.InvalidComponent() }; + } + + // Validate checksum + // const [front, check] = strings.splitAt(value, 8); + // const sum = weightedSum(front, { + // weights: [7, 3, 1, 7, 3, 1, 7, 3], + // modulus: 10, + // }); + + // if (String(sum) !== check) { + // return { isValid: false, error: new exceptions.InvalidChecksum() }; + // } + + return { + isValid: true, + compact: value, + isIndividual: value == '1', + isCompany: value == '2', + }; + }, +}; + +export const { name, localName, abbreviation, validate, format, compact } = + impl; diff --git a/src/index.ts b/src/index.ts index e32a59c2..b52050b3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import * as AD from './ad'; +import * as AI from './ai'; import * as AL from './al'; import * as AR from './ar'; import * as AT from './at'; @@ -186,6 +187,7 @@ export const stdnum: Record> = { export const personValidators: Record = { AD: [AD.nrt], + AI: [AI.tin], AL: [AL.nipt], AR: [AR.cuit, AR.dni], AT: [AT.vnr], @@ -214,6 +216,7 @@ export const personValidators: Record = { FI: [FI.hetu], FR: [FR.nif, FR.nir], GB: [GB.nino, GB.utr], + GH: [GH.tin], GR: [GR.amka], GT: [GT.cui], HK: [HK.hkid], @@ -251,14 +254,17 @@ export const personValidators: Record = { SV: [SV.nit], TH: [TH.idnr], TR: [TR.tckimlik], + TW: [TW.ubn], UA: [UA.rntrc], US: [US.ssn], UY: [UY.nie, UY.cedula], + VN: [VN.mst], ZA: [ZA.tin, ZA.idnr], }; export const entityValidators: Record = { AD: [AD.nrt], + AI: [AI.tin], AL: [AL.nipt], AR: [AR.cuit], AT: [AT.businessid, AT.tin, AT.uid], From 52c87b624d7772e463e19af1db2c715ea3692ebd Mon Sep 17 00:00:00 2001 From: David Koblas Date: Wed, 15 Oct 2025 03:15:27 -0400 Subject: [PATCH 2/5] fix length --- src/ai/tin.spec.ts | 24 ++++++++++++------------ src/ai/tin.ts | 8 ++++---- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/ai/tin.spec.ts b/src/ai/tin.spec.ts index 0624b9ac..d0949c58 100644 --- a/src/ai/tin.spec.ts +++ b/src/ai/tin.spec.ts @@ -2,21 +2,21 @@ import { validate, format } from './tin'; import { InvalidLength, InvalidFormat } from '../exceptions'; describe('ai/tin', () => { - it('format:12345-6789', () => { - const result = format('123456789'); + it('format:12345-67890', () => { + const result = format('1234567890'); - expect(result).toEqual('12345-6789'); + expect(result).toEqual('12345-67890'); }); - it('validate:12345-6789', () => { - const result = validate('12345-6789'); + it('validate:12345-67890', () => { + const result = validate('12345-67890'); - expect(result.isValid && result.compact).toEqual('123456789'); + expect(result.isValid && result.compact).toEqual('1234567890'); }); test.each([ - '123456789', // Valid format - '12345-6789', // Valid format with hyphen + '1234567890', // Valid format + '12345-67890', // Valid format with hyphen ])('validate:%s', value => { const result = validate(value); @@ -34,14 +34,14 @@ describe('ai/tin', () => { expect(result.isValid).toEqual(false); }); - it('validate:1234567890', () => { - const result = validate('1234567890'); + it('validate:123456789', () => { + const result = validate('123456789'); expect(result.error).toBeInstanceOf(InvalidLength); }); - it('validate:12345A678', () => { - const result = validate('12345A678'); + it('validate:12345A6789', () => { + const result = validate('12345A6789'); expect(result.error).toBeInstanceOf(InvalidFormat); }); diff --git a/src/ai/tin.ts b/src/ai/tin.ts index 8cea6492..69b5d1b1 100644 --- a/src/ai/tin.ts +++ b/src/ai/tin.ts @@ -44,7 +44,7 @@ const impl: Validator = { format(input: string): string { const [value] = clean(input); - if (value.length !== 9) { + if (value.length !== 10) { return value; } @@ -58,7 +58,7 @@ const impl: Validator = { return { isValid: false, error }; } - if (value.length !== 9) { + if (value.length !== 10) { return { isValid: false, error: new exceptions.InvalidLength() }; } @@ -86,8 +86,8 @@ const impl: Validator = { return { isValid: true, compact: value, - isIndividual: value == '1', - isCompany: value == '2', + isIndividual: prefix == '1', + isCompany: prefix == '2', }; }, }; From 9fb9bc5f2104a296a61fd9dd0f3f63d5017201ed Mon Sep 17 00:00:00 2001 From: David Koblas Date: Wed, 15 Oct 2025 03:17:56 -0400 Subject: [PATCH 3/5] Update src/ai/tin.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/ai/tin.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ai/tin.ts b/src/ai/tin.ts index 69b5d1b1..13f1df5b 100644 --- a/src/ai/tin.ts +++ b/src/ai/tin.ts @@ -2,12 +2,12 @@ * TIN (Anguilla Tax Identification Number). * * The Anguilla Tax Identification Number is issued to individuals and entities - * for tax purposes. The number consists of 9 digits in the format XXXXX-XXXX + * for tax purposes. The number consists of 10 digits in the format XXXXX-XXXXX * where the first 5 digits represent the taxpayer's registration number and - * the last 4 digits are a sequence number. + * the last 5 digits are a sequence number (including prefix and check digit). * * TINs are unique numbers automatically generated by the tax administration system - * and consists of 10 digits, including prefix digit and check digit + * and consist of 10 digits, including prefix digit and check digit. * - Individual TINs start with prefix 1 * - Non-Individual (business) TINs start with prefix 2 * From f759be83f705dfb031d681a7b1971a06c6d32828 Mon Sep 17 00:00:00 2001 From: David Koblas Date: Wed, 15 Oct 2025 03:18:30 -0400 Subject: [PATCH 4/5] strict comparison --- src/ai/tin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ai/tin.ts b/src/ai/tin.ts index 13f1df5b..4d9ba525 100644 --- a/src/ai/tin.ts +++ b/src/ai/tin.ts @@ -86,8 +86,8 @@ const impl: Validator = { return { isValid: true, compact: value, - isIndividual: prefix == '1', - isCompany: prefix == '2', + isIndividual: prefix === '1', + isCompany: prefix === '2', }; }, }; From af312375bbe1e1e36ad744e143f4db7d93d8b6da Mon Sep 17 00:00:00 2001 From: David Koblas Date: Wed, 15 Oct 2025 03:19:58 -0400 Subject: [PATCH 5/5] order fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf85beb3..8277c575 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,8 @@ How you can help! This library currently support about half the countries in the | Country | Code | Name | Group | Meaning | | ---------------------- | ---- | --------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | | Andorra | AD | NRT | Tax | Tax Register Identifier (Número de Registre Tributari) | -| Anguilla | AI | TIN | Tax | Tax Identification Number | | Albania | AL | NIPT | Vat | Albanian Vat Identifier (Numri i Identifikimit për Personin e Tatueshëm) | +| Anguilla | AI | TIN | Tax | Tax Identification Number | | Argentina | AR | CBU | Bank | Single Banking Code (Clave Bancaria Uniforme) | | Argentina | AR | CUIT | Tax | Unique Tax Identification Code (Código Único de Identificación Tributaria) | | Argentina | AR | DNI | Person | National Identity Document (Documento Nacional de Identidad) |