Blog/Components/Pages/Generator.razor.js 3.3 K · 115 lines · raw · history

1 import { getById, h } from "/common.module.js";
2
3 function writeInfo(label, msg) {
4 log.appendChild(
5 h("div",
6 [h("div", {class: "info"}, [h("span", {class: "noselect"}, label), h("span", msg)])]
7 ),
8 );
9 }
10
11 const gen_bsn = getById("bsn");
12 gen_bsn.addEventListener("click", generateBsn);
13
14 const gen_iban = getById("iban");
15 gen_iban.addEventListener("click", generateIban);
16
17 function generateBsn() {
18 const nr9 = Math.floor(Math.random() * 7);
19 const nr8 = Math.floor(Math.random() * 10);
20 const nr7 = Math.floor(Math.random() * 10);
21 const nr6 = Math.floor(Math.random() * 10);
22 const nr5 = Math.floor(Math.random() * 10);
23 const nr4 = Math.floor(Math.random() * 10);
24 const nr3 = Math.floor(Math.random() * 10);
25 let nr2 = Math.floor(Math.random() * 10);
26 const bsnNumber =
27 9 * nr9 +
28 8 * nr8 +
29 7 * nr7 +
30 6 * nr6 +
31 5 * nr5 +
32 4 * nr4 +
33 3 * nr3 +
34 2 * nr2;
35 let nr1 = Math.floor(bsnNumber - Math.floor(bsnNumber / 11) * 11);
36 if (nr1 > 9) {
37 if (nr2 > 0) {
38 nr2 -= 1;
39 nr1 = 8;
40 } else {
41 nr2 += 1;
42 nr1 = 1;
43 }
44 }
45
46 const BSNString = "" + nr9 + nr8 + nr7 + nr6 + nr5 + nr4 + nr3 + nr2 + nr1;
47 writeInfo("BSN: ", BSNString);
48 }
49
50 // Bank identifiers of a few Dutch banks, as they appear in an IBAN.
51 const BANK_CODES = ["ABNA", "ASNB", "BUNQ", "INGB", "KNAB", "RABO", "RBRB", "SNSB", "TRIO"];
52
53 /** @param {number} count @returns {string} */
54 function randomDigits(count) {
55 let digits = "";
56 for (let i = 0; i < count; i++) {
57 digits += Math.floor(Math.random() * 10);
58 }
59 return digits;
60 }
61
62 /**
63 * An old-style Dutch account number: 9 digits that pass the "elfproef", where
64 * the digits weighted 9, 8, ... 1 add up to a multiple of 11. IBANs pad those
65 * to 10 digits with a leading zero.
66 * @returns {string}
67 */
68 function randomAccountNumber() {
69 while (true) {
70 const digits = randomDigits(8);
71 let sum = 0;
72 for (let i = 0; i < 8; i++) {
73 sum += (9 - i) * Number(digits[i]);
74 }
75 // The ninth digit weighs 1, so it has to make up the rest on its own.
76 const last = (11 - (sum % 11)) % 11;
77 if (last < 10) {
78 return "0" + digits + last;
79 }
80 // It would have to be a 10 to balance out; draw eight new digits.
81 }
82 }
83
84 /**
85 * Letters count as A=10 .. Z=35 in the ISO 7064 MOD 97-10 check.
86 * @param {string} letters
87 * @returns {string}
88 */
89 function lettersToDigits(letters) {
90 return [...letters].map((c) => c.charCodeAt(0) - 55).join("");
91 }
92
93 /**
94 * Remainder of a number too large for a JS number, taken digit by digit.
95 * @param {string} digits
96 * @returns {number}
97 */
98 function mod97(digits) {
99 let rest = 0;
100 for (const digit of digits) {
101 rest = (rest * 10 + Number(digit)) % 97;
102 }
103 return rest;
104 }
105
106 function generateIban() {
107 const bank = BANK_CODES[Math.floor(Math.random() * BANK_CODES.length)];
108 const account = randomAccountNumber();
109 // ISO 13616: move "NL" + the check digits to the back, then pick the check
110 // digits that make the whole thing leave a remainder of 1 modulo 97.
111 const rest = mod97(lettersToDigits(bank) + account + lettersToDigits("NL") + "00");
112 const check = String(98 - rest).padStart(2, "0");
113
114 writeInfo("IBAN: ", "NL" + check + bank + account);
115 }