Reputation: 24853
The German Tax Id (Steueridentifikationsnummer) has the following properties:
The third bulletpoint is a little difficult for me to solve in an elegant way. I already have the code for the other three bulletpoints, but would love to get input for the last one, so that this could be a small little reference for other people.
# validate tax number
$taxNumber = $_POST['taxNumber'];
echo preg_replace("/[^0-9]/", "", $taxNumber);
if (strlen($taxNumber != 11)) {
# 11 digits
$taxNumberValid = false;
} else if ($taxNumber[0] == "0") {
# first digit != 0
$taxNumberValid = false;
} else {
# one digit two times, one digit zero times
# checksum
$numbers = str_split($taxNumber);
$sum = 0;
$product = 10;
for ($i = 0; $i <= 9; $i++) {
$sum = ($numbers[$i] + $product) % 10;
if ($sum == 0) {
$sum = 10;
}
$product = ($sum * 2) % 11;
}
$checksum = 11 - $product;
if ($checksum == 10) {
$checksum = 0;
}
if ($taxNumber[10] != $checksum) {
$taxNumberValid = false;
}
}
Upvotes: 4
Views: 4922
Reputation: 3804
Here's a solution (in Javascript) that takes all the rules in consideration.
function validateTin(tin) {
// Allow space and slash (/) as number separators
tin = tin.replace(/ |\//g, "");
// 11 digits, the first is not allowed to be 0
if (!/^[1-9][0-9]{10}$/.test(tin)) {
return false;
}
const firstTen = tin.slice(0, 10);
// Count the number of occurrences of each digit
const occurrences = firstTen
.split("")
.reduce((acc, d) => acc.set(d, (acc.get(d) || 0) + 1), new Map());
const keys = [...occurrences.keys()];
const values = [...occurrences.values()];
// If one digit occurs twice, the length of keys will be 9
// If one digit occurs three times OR if two different digits occurs twice
// each, the length will be 8
if (keys.length !== 9 && keys.length !== 8) {
return false;
}
if (keys.length === 8) {
// Check how many times the digit that occurred the most times occurred
const maxOccurrences = values.reduce((max, num) => Math.max(max, num));
// If maxOccurrences is 2, we know two different numbers occurred twice
// each. This is not a valid tin.
// A final restriction is that a number can not occur three times in a row
if (maxOccurrences === 2 || /(\d)\1\1/.test(firstTen)) {
return false;
}
}
// Calculate the checksum digit
let m11 = 10;
let m10 = 0;
for (let i = 0; i < 10; i++) {
m10 = (parseInt(tin[i], 10) + m11) % 10;
if (m10 === 0) {
m10 = 10;
}
m11 = (2 * m10) % 11;
}
let digit = 11 - m11;
if (digit === 10) {
digit = 0;
}
return digit === parseInt(tin[10], 10);
}
Upvotes: 1
Reputation: 24853
This code solves the problem:
// remove whitespaces, slashes & other unnecessary characters
$taxNumber = preg_replace("/[^0-9]/", "", $taxNumber);
// by default the taxnumber is correct
$taxNumberValid = true;
// taxnumber has to have exactly 11 digits
if (strlen($taxNumber) != 11) {
$taxNumberValid = false;
}
// first digit cannot be 0
if ($taxNumber[0] == "0") {
$taxNumberValid = false;
}
/*
make sure that within the first ten digits:
1.) one digit appears exactly twice or thrice
2.) one or two digits appear zero times
3.) and oll other digits appear exactly once once
*/
$digits = str_split($taxNumber);
$first10Digits = $digits;
array_pop($first10Digits);
$countDigits = array_count_values ($first10Digits);
if (count($countDigits) != 9 && count($countDigits) != 8) {
$taxNumberValid = false;
}
// last check: 11th digit has to be the correct checkums
// see http://de.wikipedia.org/wiki/Steueridentifikationsnummer#Aufbau_der_Identifikationsnummer
$sum = 0;
$product = 10;
for($i = 0; $i <= 9; $i++) {
$sum = ($digits[$i] + $product) % 10;
if ($sum == 0) {
$sum = 10;
}
$product = ($sum * 2) % 11;
}
$checksum = 11 - $product;
if ($checksum == 10) {
$checksum = 0;
}
if ($taxNumber[10] != $checksum) {
$taxNumberValid = false;
}
Update in 2017
Until 2016, the rule was, that within the first ten digits one number had to appear exactly twice.
Starting at 2017, the rule is, that within the first ten digits one number has to appear exactly twice or thrice.
Upvotes: 2
Reputation: 66315
And here is how you would write it in JS, based on @Pascal Klein's answer:
function countValues(arr) {
return arr.reduce((obj, item) => {
obj[item] = obj[item] ? ++obj[item] : 1;
return obj;
}, {});
}
function validateTIN(tin) {
const tinLength = 11;
// Taxnumber has to have exactly 11 digits.
if (tin.length !== tinLength) {
return false;
}
// First digit cannot be 0.
if (tin[0] === '0') {
return false;
}
/*
make sure that within the first ten digits:
1.) one digit appears exactly twice or thrice
2.) one or two digits appear zero times
3.) and all other digits appear exactly once
*/
const tinArray = tin.split('').slice(0, -1);
const valueCount = countValues(tinArray);
const valueCountLength = Object.keys(valueCount).length;
if (valueCountLength !== 8 && valueCountLength !== 9) {
return false;
}
// 11th digit has to match the checkum.
let sum = 0;
let product = 10;
for(let i = 0; i < tinLength - 1; i++) {
sum = (+tinArray[i] + product) % 10;
if (sum === 0) {
sum = 10;
}
product = (sum * 2) % 11;
}
let checksum = 11 - product;
if (checksum === 10) {
checksum = 0;
}
if (+tin[tinLength - 1] !== checksum) {
return false;
}
return true;
}
const tin1 = 'gbg';
const tin2 = '42344677908';
const tin3 = '12005078909';
const tin4 = '36574261809'; // valid
const tin5 = '10863924976'; // valid
console.log(tin1, validateTIN(tin1));
console.log(tin2, validateTIN(tin2));
console.log(tin3, validateTIN(tin3));
console.log(tin4, validateTIN(tin4));
console.log(tin5, validateTIN(tin5));
Upvotes: 1