-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #62 from koblas/bitfinex_updates
feature: added Taiwan individual tax numbers
- Loading branch information
Showing
11 changed files
with
517 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { validate, format } from './ban'; | ||
import { InvalidLength, InvalidFormat } from '../exceptions'; | ||
|
||
describe('tw/ban', () => { | ||
it('format:0050150 3', () => { | ||
const result = format('0050150 3'); | ||
|
||
expect(result).toEqual('00501503'); | ||
}); | ||
|
||
it('validate:00501503', () => { | ||
const result = validate('00501503'); | ||
|
||
expect(result.isValid && result.compact).toEqual('00501503'); | ||
}); | ||
|
||
it('validate:12345', () => { | ||
const result = validate('12345'); | ||
|
||
expect(result.error).toBeInstanceOf(InvalidLength); | ||
}); | ||
|
||
it('validate:12345AAA', () => { | ||
const result = validate('12345AAA'); | ||
|
||
expect(result.error).toBeInstanceOf(InvalidFormat); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/** | ||
* BAN | ||
* | ||
* The BAN of a business entity is shown on the company registration certificate or | ||
* business registration certificate. For non-profit organizations, the BAN is shown | ||
* on the BAN-issuance notification. | ||
* | ||
* Sources: | ||
* https://www.mof.gov.tw/Eng/download/16968 | ||
* | ||
* ENTITY | ||
*/ | ||
|
||
import { ValidateReturn } from 'types'; | ||
import * as exceptions from '../exceptions'; | ||
import { strings } from '../util'; | ||
|
||
function clean(input: string) { | ||
return strings.cleanUnicode(input, ' -'); | ||
} | ||
|
||
const impl = { | ||
abbreviation: 'BAN', | ||
name: 'Taiwanese Unified Business Number', | ||
localName: '統一編號', | ||
|
||
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 !== 8) { | ||
return { isValid: false, error: new exceptions.InvalidLength() }; | ||
} | ||
if (!strings.isdigits(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; | ||
export default impl; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
export * as ubn from './ubn'; | ||
export * as ban from './ban'; | ||
export * as natid from './natid'; | ||
export * as ui from './ui'; | ||
export * as tax_code from './tax_code'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { validate, format } from './natid'; | ||
import { InvalidLength, InvalidComponent } from '../exceptions'; | ||
|
||
describe('tw/natid', () => { | ||
it('format:0050150 3', () => { | ||
const result = format('005-0150 3'); | ||
|
||
expect(result).toEqual('00501503'); | ||
}); | ||
|
||
it('validate:A200501503', () => { | ||
const result = validate('A200501503'); | ||
|
||
expect(result.isValid && result.compact).toEqual('A200501503'); | ||
}); | ||
|
||
it('validate:12345', () => { | ||
const result = validate('12345'); | ||
|
||
expect(result.error).toBeInstanceOf(InvalidLength); | ||
}); | ||
|
||
it('validate:12345AAAA', () => { | ||
const result = validate('12345AAA1A'); | ||
|
||
expect(result.error).toBeInstanceOf(InvalidComponent); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/** | ||
* National ID Card Number | ||
* | ||
* The National ID Card Number is shown on the ID card of the R.O.C., the | ||
* household certificate, and the passport. | ||
* | ||
* A ten-digit code with the first an alphabetic letter followed by a | ||
* nine-digit numeric string. The alphabetic letter is the area code | ||
* of the municipality/county/city in which the individual applies | ||
* for household registration. The leading number represents gender: | ||
* “1” for males and “2” for females. The last number is a check | ||
* digit. | ||
* | ||
* Note: NATID and UI only differ by gender coding | ||
* | ||
* Sources: | ||
* https://www.mof.gov.tw/Eng/download/16968 | ||
* https://en.wikipedia.org/wiki/National_identification_card_(Taiwan) | ||
* | ||
* PERSON | ||
*/ | ||
|
||
import { ValidateReturn } from 'types'; | ||
import * as exceptions from '../exceptions'; | ||
import { strings, weightedSum } from '../util'; | ||
|
||
function clean(input: string) { | ||
return strings.cleanUnicode(input, ' -'); | ||
} | ||
|
||
// ID Encoding alphabet | ||
// | ||
// A: 10 B: 11 C: 12 D: 13 E: 14 F: 15 G: 16 H: 17 I: 34 | ||
// J: 18 K: 19 L: 20 M: 21 N: 22 O: 35 P: 23 Q: 24 R: 25 | ||
// S: 26 T: 27 U: 28 V: 29 W: 32 X: 30 Y: 31 Z: 33 | ||
// | ||
export const ALPHABET = '0123456789ABCDEFGHJKLMNPQRSTUVXYWZIO'; | ||
|
||
const impl = { | ||
localName: '中華民國國民身分證', | ||
abbreviation: 'NATID', | ||
name: 'National ID Number', | ||
|
||
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 !== 10) { | ||
return { isValid: false, error: new exceptions.InvalidLength() }; | ||
} | ||
|
||
const [issuer, gender, code, check] = strings.splitAt(value, 1, 2, 9); | ||
|
||
if (!strings.isalpha(issuer)) { | ||
return { isValid: false, error: new exceptions.InvalidComponent() }; | ||
} | ||
if (!['1', '2'].includes(gender)) { | ||
return { isValid: false, error: new exceptions.InvalidComponent() }; | ||
} | ||
if (!strings.isdigits(code)) { | ||
return { isValid: false, error: new exceptions.InvalidComponent() }; | ||
} | ||
if (!strings.isdigits(check)) { | ||
return { isValid: false, error: new exceptions.InvalidComponent() }; | ||
} | ||
|
||
const sum = weightedSum(value, { | ||
weights: [1, 9, 8, 7, 6, 5, 4, 3, 2, 1], | ||
alphabet: ALPHABET, | ||
modulus: 10, | ||
}); | ||
if (sum % 10 !== 0) { | ||
return { isValid: false, error: new exceptions.InvalidChecksum() }; | ||
} | ||
|
||
return { | ||
isValid: true, | ||
compact: value, | ||
isIndividual: true, | ||
isCompany: false, | ||
}; | ||
}, | ||
}; | ||
|
||
export const { name, localName, abbreviation, validate, format, compact } = | ||
impl; | ||
export default impl; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { validate, format } from './tax_code'; | ||
import { InvalidLength, InvalidFormat } from '../exceptions'; | ||
|
||
describe('tw/tax_code', () => { | ||
it('format:0050150 3', () => { | ||
const result = format('0050150 3'); | ||
|
||
expect(result).toEqual('00501503'); | ||
}); | ||
|
||
test.each(['9000503', '2000 05 03 AA'])('validate:%s', value => { | ||
const result = validate(value); | ||
|
||
console.log(value, result); | ||
|
||
expect(result.isValid).toEqual(true); | ||
}); | ||
|
||
it('validate:9 00 05 15', () => { | ||
const result = validate('9 00 05 15'); | ||
|
||
expect(result.isValid && result.compact).toEqual('9000515'); | ||
}); | ||
|
||
it('validate:12345', () => { | ||
const result = validate('12345'); | ||
|
||
expect(result.error).toBeInstanceOf(InvalidLength); | ||
}); | ||
|
||
it('validate:12345AA', () => { | ||
const result = validate('12345AA'); | ||
|
||
expect(result.error).toBeInstanceOf(InvalidFormat); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/** | ||
* Tax Code | ||
* | ||
* Individual taxpayers who have neither National ID Card Number nor UI Number | ||
* (usually because they were not physically present or only stayed for a short | ||
* period of time in the R.O.C.) may produce Tax Codes themselves as their TINs | ||
* by reference to the coding princip | ||
* | ||
* Tax Code | ||
* (1) For individuals of Mainland China area: a seven-digit code that begins with 9 | ||
* followed by the last two numbers of the individuals’ birth year and the four | ||
* numbers of the individuals’ birth month and day (mmdd). | ||
* (2) For individuals other than the ones indicated in (1): a ten-digit code that | ||
* begins with the individuals’ date of birth (yyyymmdd), followed by the first | ||
* two alphabetic letters of the individuals’ name on the passport. | ||
* | ||
* Sources: | ||
* https://www.mof.gov.tw/Eng/download/16968 | ||
* | ||
* PERSON | ||
*/ | ||
|
||
import { ValidateReturn } from 'types'; | ||
import * as exceptions from '../exceptions'; | ||
import { | ||
isValidDateCompactYYMMDD, | ||
isValidDateCompactYYYYMMDD, | ||
strings, | ||
} from '../util'; | ||
|
||
function clean(input: string) { | ||
return strings.cleanUnicode(input, ' -'); | ||
} | ||
|
||
const impl = { | ||
abbreviation: '', | ||
localName: '', | ||
name: 'Tax Code', | ||
|
||
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 !== 10 && value.length !== 7) { | ||
return { isValid: false, error: new exceptions.InvalidLength() }; | ||
} | ||
if (value.length === 7) { | ||
const [lead, yymmdd] = strings.splitAt(value, 1); | ||
if (lead !== '9') { | ||
return { isValid: false, error: new exceptions.InvalidFormat() }; | ||
} | ||
if (!strings.isdigits(yymmdd)) { | ||
return { isValid: false, error: new exceptions.InvalidFormat() }; | ||
} | ||
if (!isValidDateCompactYYMMDD(yymmdd)) { | ||
return { isValid: false, error: new exceptions.InvalidComponent() }; | ||
} | ||
} else { | ||
const [yyyymmdd, name] = strings.splitAt(value, 8); | ||
if (!isValidDateCompactYYYYMMDD(yyyymmdd)) { | ||
return { isValid: false, error: new exceptions.InvalidComponent() }; | ||
} | ||
if (!/^[A-Z]+$/i.test(name)) { | ||
return { isValid: false, error: new exceptions.InvalidComponent() }; | ||
} | ||
} | ||
|
||
return { | ||
isValid: true, | ||
compact: value, | ||
isIndividual: true, | ||
isCompany: false, | ||
}; | ||
}, | ||
}; | ||
|
||
export const { name, localName, abbreviation, validate, format, compact } = | ||
impl; | ||
export default impl; |
Oops, something went wrong.