Add a random Dutch IBAN generator to the Generator page

Picks one of nine Dutch bank codes and a 9-digit account number that passes the elfproef, then computes the ISO 13616 MOD 97-10 check digits. mod97 walks the string digit by digit, since the rearranged IBAN is larger than Number.MAX_SAFE_INTEGER. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

author
Marijn Besseling <njirambem@gmail.com> · 2026-09-02 12:53 UTC
commit
cf1425bd88244faaa29cc220af83b8df68c31bd9
parent
e39ae7ad1b
tree
browse at this commit

2 files changed +71 -1

Blog/Components/Pages/Generator.razor +1 -1

@@ -4,7 +4,7 @@
4 4
5 5 <main>
6 6 <button id="bsn">Generate BSN</button>
7 - <!-- <button id="iban">Generate IBAN</button> -->
7 + <button id="iban">Generate IBAN</button>
8 8
9 9 <Log/>
10 10 </main>
No newline at end of file

Blog/Components/Pages/Generator.razor.js +70 -0

@@ -11,6 +11,9 @@ function writeInfo(label, msg) {
11 11 export function onLoad() {
12 12 const gen_bsn = getById("bsn");
13 13 gen_bsn.addEventListener("click", generateBsn);
14 +
15 + const gen_iban = getById("iban");
16 + gen_iban.addEventListener("click", generateIban);
14 17 }
15 18
16 19 function generateBsn() {
@@ -45,3 +48,70 @@ function generateBsn() {
45 48 const BSNString = "" + nr9 + nr8 + nr7 + nr6 + nr5 + nr4 + nr3 + nr2 + nr1;
46 49 writeInfo("BSN: ", BSNString);
47 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 +}