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