diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 15e2d42..553b1e7 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 822ebc5..8277c57 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ How you can help! This library currently support about half the countries in the | ---------------------- | ---- | --------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | | Andorra | AD | NRT | Tax | Tax Register Identifier (Número de Registre Tributari) | | 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) | diff --git a/src/ai/index.ts b/src/ai/index.ts new file mode 100644 index 0000000..110affe --- /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 0000000..d0949c5 --- /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-67890', () => { + const result = format('1234567890'); + + expect(result).toEqual('12345-67890'); + }); + + it('validate:12345-67890', () => { + const result = validate('12345-67890'); + + expect(result.isValid && result.compact).toEqual('1234567890'); + }); + + test.each([ + '1234567890', // Valid format + '12345-67890', // 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:123456789', () => { + const result = validate('123456789'); + + expect(result.error).toBeInstanceOf(InvalidLength); + }); + + it('validate:12345A6789', () => { + const result = validate('12345A6789'); + + 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 0000000..4d9ba52 --- /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 10 digits in the format XXXXX-XXXXX + * where the first 5 digits represent the taxpayer's registration number and + * 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 consist 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 !== 10) { + 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 !== 10) { + 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: prefix === '1', + isCompany: prefix === '2', + }; + }, +}; + +export const { name, localName, abbreviation, validate, format, compact } = + impl; diff --git a/src/index.ts b/src/index.ts index e32a59c..b52050b 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],