Skip to content

Commit

Permalink
Merge pull request #92 from koblas/ca_sales_tax
Browse files Browse the repository at this point in the history
fix: added CA/QST,GST,PST numbers
  • Loading branch information
koblas committed Jul 30, 2023
2 parents 42ea640 + f7f91c1 commit 49f4217
Show file tree
Hide file tree
Showing 8 changed files with 344 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ How you can help! This library currently support about half the countries in the
| Brazil | BR | CNPJ | Company | Brazilian company number (Cadastro Nacional da Pessoa Jurídica) |
| Belarus | BY | UNP | Person/Company | Учетный номер плательщика, the Belarus VAT number |
| Canada | CA | BN | Company | Company Identifier (Canadian Business Number) |
| Canada | CA | GST | Company | Goods and service Tax Number |
| Canada | CA | PST | Company | Provincial Service Tax Number |
| Canada | CA | QST | Company | Quebec Service Tax Number |
| Canada | CA | SIN | Person | Person Identifier (Social Insurance Number) |
| Cuba | CU | NI | Person | Número de identidad, Cuban identity card numbers |
| Cyprus | CY | VAT | Company | Αριθμός Εγγραφής Φ.Π.Α. (Cypriot VAT number) |
Expand Down
34 changes: 34 additions & 0 deletions src/ca/gst.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { validate, format } from './gst';
import { InvalidLength, InvalidChecksum } from '../exceptions';

describe('ca/gst', () => {
it('format:754039725 RT 0001', () => {
const result = format('754039725 RT 0001');

expect(result).toEqual('754039725RT0001');
});

it('fvalidate:754039725 RT 0001', () => {
const result = validate('754039725 RT 0001');

expect(result.isValid && result.compact).toEqual('754039725RT0001');
});

test.each(['807138417RT0001', '754039725 RT 0001'])('validate:%s', value => {
const result = validate(value);

expect(result.isValid).toEqual(true);
});

it('validate:VALUE', () => {
const result = validate('VALUE');

expect(result.error).toBeInstanceOf(InvalidLength);
});

it('validate:754039729 RT 0001', () => {
const result = validate('754039729 RT 0001');

expect(result.error).toBeInstanceOf(InvalidChecksum);
});
});
70 changes: 70 additions & 0 deletions src/ca/gst.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* GST (Canadian Sales Tax Number)
*
* It is the BN in a extensive form.
* 9 digits - Business number to identify the business. It is the Bussines Number.
* 2 alpha - A reference number to identify each account a business may have within a program type.
* 4 digits - A reference number to identify each account a business may have within a program type.
*
* Source
* https://wiki.scn.sap.com/wiki/display/CRM/Canada
* https://www.canada.ca/en/revenue-agency/cra-canada.html
*
* ENTITY
*/

import * as exceptions from '../exceptions';
import { strings } from '../util';
import { Validator, ValidateReturn } from '../types';
import { validate as bnValidate } from './bn';

function clean(input: string): ReturnType<typeof strings.cleanUnicode> {
return strings.cleanUnicode(input, ' -');
}

const validRe = /^\d{9}[A-Z]{2}\d{4}$/i;

// const ALPHABET = '0123456789X';

const impl: Validator = {
name: 'Goods and service Tax Number',
localName: 'Goods and service Tax Number',
abbreviation: 'GST',

compact(input: string): string {
const [value, err] = clean(input);

if (err) {
throw err;
}

return value;
},

format(input: string): string {
const [value] = clean(input);

return value;
},

validate(input: string): ValidateReturn {
const [value, error] = clean(input);

if (error) {
return { isValid: false, error };
}
if (value.length !== 15) {
return { isValid: false, error: new exceptions.InvalidLength() };
}

if (!validRe.test(value)) {
return { isValid: false, error: new exceptions.InvalidFormat() };
}

// The BN15 is the same as a GST
return bnValidate(value);
},
};

export const { name, localName, abbreviation, validate, format, compact } =
impl;
3 changes: 3 additions & 0 deletions src/ca/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export * as bn from './bn';
export * as sin from './sin';
export * as gst from './gst';
export * as qst from './qst';
export * as pst from './pst';
28 changes: 28 additions & 0 deletions src/ca/pst.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { validate, format } from './pst';
import { InvalidLength } from '../exceptions';

describe('ca/pst', () => {
it('format:PST12345678', () => {
const result = format('PST12345678');

expect(result).toEqual('PST-1234-5678');
});

it('fvalidate:PST-1234-5678', () => {
const result = validate('PST-1234-5678');

expect(result.isValid && result.compact).toEqual('PST12345678');
});

test.each(['PST-1234-5678'])('validate:%s', value => {
const result = validate(value);

expect(result.isValid).toEqual(true);
});

it('validate:PST-1234-567', () => {
const result = validate('PST-1234-567');

expect(result.error).toBeInstanceOf(InvalidLength);
});
});
71 changes: 71 additions & 0 deletions src/ca/pst.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* PST (Canadian BC Provincial Sales Tax)
*
* 3 alphanumeric (PST)
* 8 digits
*
* Source
* https://wiki.scn.sap.com/wiki/display/CRM/Canada
* https://www2.gov.bc.ca/gov/content/taxes/sales-taxes/pst/register
*
* ENTITY
*/

import * as exceptions from '../exceptions';
import { strings } from '../util';
import { Validator, ValidateReturn } from '../types';

function clean(input: string): ReturnType<typeof strings.cleanUnicode> {
return strings.cleanUnicode(input, ' -');
}

const validRe = /^PST\d{8}$/;

// const ALPHABET = '0123456789X';

const impl: Validator = {
name: 'Provincial Sales Tax',
localName: '',
abbreviation: 'PST',

compact(input: string): string {
const [value, err] = clean(input);

if (err) {
throw err;
}

return value;
},

format(input: string): string {
const [value] = clean(input);

return strings.splitAt(value, 3, 7).join('-');
},

validate(input: string): ValidateReturn {
const [value, error] = clean(input);

if (error) {
return { isValid: false, error };
}
if (value.length !== 11) {
return { isValid: false, error: new exceptions.InvalidLength() };
}

if (!validRe.test(value)) {
return { isValid: false, error: new exceptions.InvalidFormat() };
}

return {
isValid: true,
compact: value,
isIndividual: false,
isCompany: true,
};
},
};

export const { name, localName, abbreviation, validate, format, compact } =
impl;
47 changes: 47 additions & 0 deletions src/ca/qst.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { validate, format } from './qst';
import {
InvalidLength,
InvalidChecksum,
InvalidComponent,
} from '../exceptions';

describe('ca/qst', () => {
it('format:1224002901 TQ 0001', () => {
const result = format('1224002901 TQ 0001');

expect(result).toEqual('1224002901TQ0001');
});

it('fvalidate:1224002901 TQ 0001', () => {
const result = validate('1224002901 TQ 0001');

expect(result.isValid && result.compact).toEqual('1224002901TQ0001');
});

test.each(['1224002901 TQ 0001', '1214326422TQ0001'])(
'validate:%s',
value => {
const result = validate(value);

expect(result.isValid).toEqual(true);
},
);

it('validate:1224002901 TQ 000', () => {
const result = validate('1224002901 TQ 000');

expect(result.error).toBeInstanceOf(InvalidLength);
});

it('validate:1224002902 TQ 0001', () => {
const result = validate('1224002902 TQ 0001');

expect(result.error).toBeInstanceOf(InvalidChecksum);
});

it('validate:1224002901 TQ 0000', () => {
const result = validate('1224002901 TQ 0000');

expect(result.error).toBeInstanceOf(InvalidComponent);
});
});
88 changes: 88 additions & 0 deletions src/ca/qst.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* QST (Quebec Sales Tax Number).
*
* DESCRIPTION
* 10 digits - Business number to identify the business. It is the Bussines Number.
* 2 alpha - QT / TQ (unclear)
* 4 digits - Serial number
*
* Source
* https://www5.services.mrq.gouv.qc.ca/mrqanonyme/g1/g1c/g1c1a_I_ValidationNoTVQ/PageIdentificationNoTVQ.aspx
* https://wiki.scn.sap.com/wiki/display/CRM/Canada
*
* 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<typeof strings.cleanUnicode> {
return strings.cleanUnicode(input, ' -');
}

const validRe = /^\d{10}[a-z]{2}\d{4}$/i;

// const ALPHABET = '0123456789X';

const impl: Validator = {
name: 'Quebec Sales Tax Number',
localName: '',
abbreviation: 'QST',

compact(input: string): string {
const [value, err] = clean(input);

if (err) {
throw err;
}

return value;
},

format(input: string): string {
const [value] = clean(input);

return value;
},

validate(input: string): ValidateReturn {
const [value, error] = clean(input);

if (error) {
return { isValid: false, error };
}
if (value.length !== 16) {
return { isValid: false, error: new exceptions.InvalidLength() };
}

if (!validRe.test(value)) {
return { isValid: false, error: new exceptions.InvalidFormat() };
}

const [front, check, , serial] = strings.splitAt(value, 9, 10, 12);
if (serial === '0000') {
return { isValid: false, error: new exceptions.InvalidComponent() };
}

const sum = weightedSum(front, {
weights: [4, 3, 2, 7, 6, 5, 4, 3, 2],
modulus: 11,
});

if (String((11 - sum) % 10) !== check) {
return { isValid: false, error: new exceptions.InvalidChecksum() };
}

return {
isValid: true,
compact: value,
isIndividual: false,
isCompany: true,
};
},
};

export const { name, localName, abbreviation, validate, format, compact } =
impl;

0 comments on commit 49f4217

Please sign in to comment.