|
| 1 | +<script setup lang="ts"> |
| 2 | +import { ref, watch } from 'vue'; |
| 3 | +import { random } from '@/utils/random'; |
| 4 | +
|
| 5 | +const countries = [ |
| 6 | + { code: 'AL', name: 'Albania', bankCodeLength: 8, accountNumberLength: 16, prefix: '' }, |
| 7 | + { code: 'AD', name: 'Andorra', bankCodeLength: 4, accountNumberLength: 12, prefix: '' }, |
| 8 | + { code: 'AT', name: 'Austria', bankCodeLength: 5, accountNumberLength: 11, prefix: '' }, |
| 9 | + { code: 'AZ', name: 'Azerbaijan', bankCodeLength: 4, accountNumberLength: 20, prefix: '' }, |
| 10 | + { code: 'BH', name: 'Bahrain', bankCodeLength: 4, accountNumberLength: 14, prefix: '' }, |
| 11 | + { code: 'BE', name: 'Belgium', bankCodeLength: 3, accountNumberLength: 9, prefix: '' }, |
| 12 | + { code: 'BA', name: 'Bosnia and Herzegovina', bankCodeLength: 3, accountNumberLength: 13, prefix: '' }, |
| 13 | + { code: 'BR', name: 'Brazil', bankCodeLength: 8, accountNumberLength: 13, prefix: '' }, |
| 14 | + { code: 'BG', name: 'Bulgaria', bankCodeLength: 4, accountNumberLength: 10, prefix: '' }, |
| 15 | + { code: 'CR', name: 'Costa Rica', bankCodeLength: 3, accountNumberLength: 14, prefix: '' }, |
| 16 | + { code: 'HR', name: 'Croatia', bankCodeLength: 7, accountNumberLength: 10, prefix: '' }, |
| 17 | + { code: 'CY', name: 'Cyprus', bankCodeLength: 3, accountNumberLength: 16, prefix: '' }, |
| 18 | + { code: 'CZ', name: 'Czech Republic', bankCodeLength: 4, accountNumberLength: 16, prefix: '' }, |
| 19 | + { code: 'DK', name: 'Denmark', bankCodeLength: 4, accountNumberLength: 10, prefix: '' }, |
| 20 | + { code: 'DO', name: 'Dominican Republic', bankCodeLength: 4, accountNumberLength: 20, prefix: '' }, |
| 21 | + { code: 'EE', name: 'Estonia', bankCodeLength: 2, accountNumberLength: 16, prefix: '' }, |
| 22 | + { code: 'FO', name: 'Faroe Islands', bankCodeLength: 4, accountNumberLength: 9, prefix: '' }, |
| 23 | + { code: 'FI', name: 'Finland', bankCodeLength: 6, accountNumberLength: 7, prefix: '' }, |
| 24 | + { code: 'FR', name: 'France', bankCodeLength: 5, accountNumberLength: 11, prefix: '' }, |
| 25 | + { code: 'GE', name: 'Georgia', bankCodeLength: 2, accountNumberLength: 16, prefix: '' }, |
| 26 | + { code: 'DE', name: 'Germany', bankCodeLength: 8, accountNumberLength: 10, prefix: '' }, |
| 27 | + { code: 'GI', name: 'Gibraltar', bankCodeLength: 4, accountNumberLength: 15, prefix: '' }, |
| 28 | + { code: 'GR', name: 'Greece', bankCodeLength: 3, accountNumberLength: 16, prefix: '' }, |
| 29 | + { code: 'GL', name: 'Greenland', bankCodeLength: 4, accountNumberLength: 9, prefix: '' }, |
| 30 | + { code: 'GT', name: 'Guatemala', bankCodeLength: 4, accountNumberLength: 20, prefix: '' }, |
| 31 | + { code: 'HU', name: 'Hungary', bankCodeLength: 3, accountNumberLength: 17, prefix: '' }, |
| 32 | + { code: 'IS', name: 'Iceland', bankCodeLength: 4, accountNumberLength: 18, prefix: '' }, |
| 33 | + { code: 'IE', name: 'Ireland', bankCodeLength: 4, accountNumberLength: 10, prefix: '' }, |
| 34 | + { code: 'IL', name: 'Israel', bankCodeLength: 3, accountNumberLength: 19, prefix: '' }, |
| 35 | + { code: 'IT', name: 'Italy', bankCodeLength: 5, accountNumberLength: 12, prefix: '' }, |
| 36 | + { code: 'JO', name: 'Jordan', bankCodeLength: 4, accountNumberLength: 18, prefix: '' }, |
| 37 | + { code: 'KZ', name: 'Kazakhstan', bankCodeLength: 3, accountNumberLength: 13, prefix: '' }, |
| 38 | + { code: 'XK', name: 'Kosovo', bankCodeLength: 4, accountNumberLength: 12, prefix: '' }, |
| 39 | + { code: 'KW', name: 'Kuwait', bankCodeLength: 4, accountNumberLength: 22, prefix: '' }, |
| 40 | + { code: 'LV', name: 'Latvia', bankCodeLength: 4, accountNumberLength: 13, prefix: '' }, |
| 41 | + { code: 'LB', name: 'Lebanon', bankCodeLength: 4, accountNumberLength: 20, prefix: '' }, |
| 42 | + { code: 'LI', name: 'Liechtenstein', bankCodeLength: 5, accountNumberLength: 12, prefix: '' }, |
| 43 | + { code: 'LT', name: 'Lithuania', bankCodeLength: 5, accountNumberLength: 11, prefix: '' }, |
| 44 | + { code: 'LU', name: 'Luxembourg', bankCodeLength: 3, accountNumberLength: 13, prefix: '' }, |
| 45 | + { code: 'MK', name: 'North Macedonia', bankCodeLength: 3, accountNumberLength: 10, prefix: '' }, |
| 46 | + { code: 'MT', name: 'Malta', bankCodeLength: 4, accountNumberLength: 18, prefix: '' }, |
| 47 | + { code: 'MR', name: 'Mauritania', bankCodeLength: 5, accountNumberLength: 15, prefix: '' }, |
| 48 | + { code: 'MU', name: 'Mauritius', bankCodeLength: 6, accountNumberLength: 19, prefix: '' }, |
| 49 | + { code: 'MC', name: 'Monaco', bankCodeLength: 5, accountNumberLength: 11, prefix: '' }, |
| 50 | + { code: 'MD', name: 'Moldova', bankCodeLength: 2, accountNumberLength: 18, prefix: '' }, |
| 51 | + { code: 'ME', name: 'Montenegro', bankCodeLength: 3, accountNumberLength: 13, prefix: '' }, |
| 52 | + { code: 'NL', name: 'Netherlands', bankCodeLength: 4, accountNumberLength: 10, prefix: '' }, |
| 53 | + { code: 'NO', name: 'Norway', bankCodeLength: 4, accountNumberLength: 7, prefix: '' }, |
| 54 | + { code: 'PK', name: 'Pakistan', bankCodeLength: 4, accountNumberLength: 16, prefix: '' }, |
| 55 | + { code: 'PS', name: 'Palestine', bankCodeLength: 4, accountNumberLength: 21, prefix: '' }, |
| 56 | + { code: 'PL', name: 'Poland', bankCodeLength: 8, accountNumberLength: 16, prefix: '' }, |
| 57 | + { code: 'PT', name: 'Portugal', bankCodeLength: 4, accountNumberLength: 15, prefix: '' }, |
| 58 | + { code: 'QA', name: 'Qatar', bankCodeLength: 4, accountNumberLength: 21, prefix: '' }, |
| 59 | + { code: 'RO', name: 'Romania', bankCodeLength: 4, accountNumberLength: 16, prefix: '' }, |
| 60 | + { code: 'LC', name: 'Saint Lucia', bankCodeLength: 4, accountNumberLength: 24, prefix: '' }, |
| 61 | + { code: 'SM', name: 'San Marino', bankCodeLength: 5, accountNumberLength: 12, prefix: '' }, |
| 62 | + { code: 'ST', name: 'São Tomé and Príncipe', bankCodeLength: 8, accountNumberLength: 16, prefix: '' }, |
| 63 | + { code: 'SA', name: 'Saudi Arabia', bankCodeLength: 2, accountNumberLength: 18, prefix: '' }, |
| 64 | + { code: 'RS', name: 'Serbia', bankCodeLength: 3, accountNumberLength: 15, prefix: '' }, |
| 65 | + { code: 'SC', name: 'Seychelles', bankCodeLength: 4, accountNumberLength: 16, prefix: '' }, |
| 66 | + { code: 'SK', name: 'Slovakia', bankCodeLength: 4, accountNumberLength: 16, prefix: '' }, |
| 67 | + { code: 'SI', name: 'Slovenia', bankCodeLength: 5, accountNumberLength: 8, prefix: '' }, |
| 68 | + { code: 'ES', name: 'Spain', bankCodeLength: 8, accountNumberLength: 12, prefix: '' }, |
| 69 | + { code: 'SE', name: 'Sweden', bankCodeLength: 3, accountNumberLength: 17, prefix: '' }, |
| 70 | + { code: 'CH', name: 'Switzerland', bankCodeLength: 5, accountNumberLength: 12, prefix: '' }, |
| 71 | + { code: 'TL', name: 'Timor-Leste', bankCodeLength: 3, accountNumberLength: 14, prefix: '' }, |
| 72 | + { code: 'TN', name: 'Tunisia', bankCodeLength: 5, accountNumberLength: 15, prefix: '' }, |
| 73 | + { code: 'TR', name: 'Turkey', bankCodeLength: 5, accountNumberLength: 16, prefix: '' }, |
| 74 | + { code: 'UA', name: 'Ukraine', bankCodeLength: 6, accountNumberLength: 19, prefix: '' }, |
| 75 | + { code: 'AE', name: 'United Arab Emirates', bankCodeLength: 3, accountNumberLength: 16, prefix: '' }, |
| 76 | + { code: 'GB', name: 'United Kingdom', bankCodeLength: 4, accountNumberLength: 14, prefix: '' }, |
| 77 | + { code: 'VG', name: 'British Virgin Islands', bankCodeLength: 4, accountNumberLength: 16, prefix: '' }, |
| 78 | +]; |
| 79 | +
|
| 80 | +const selectedCountry = ref('DE'); |
| 81 | +const bankCode = ref(''); |
| 82 | +const accountNumber = ref(''); |
| 83 | +const iban = ref(''); |
| 84 | +const error = ref(''); |
| 85 | +
|
| 86 | +function padLeft(str: string, length: number, char = '0') { |
| 87 | + return str.padStart(length, char); |
| 88 | +} |
| 89 | +
|
| 90 | +function randomDigits(length: number) { |
| 91 | + let digits = ''; |
| 92 | + for (let i = 0; i < length; i++) { |
| 93 | + digits += Math.floor(random() * 10).toString(); |
| 94 | + } |
| 95 | + return digits; |
| 96 | +} |
| 97 | +
|
| 98 | +function regenerate() { |
| 99 | + const country = countries.find(c => c.code === selectedCountry.value); |
| 100 | + if (!country) { |
| 101 | + return; |
| 102 | + } |
| 103 | + bankCode.value = randomDigits(country.bankCodeLength); |
| 104 | + accountNumber.value = randomDigits(country.accountNumberLength); |
| 105 | + iban.value = ''; |
| 106 | + error.value = ''; |
| 107 | +} |
| 108 | +
|
| 109 | +function generateIban() { |
| 110 | + error.value = ''; |
| 111 | + const country = countries.find(c => c.code === selectedCountry.value); |
| 112 | + if (!country) { |
| 113 | + return; |
| 114 | + } |
| 115 | + regenerate(); |
| 116 | +
|
| 117 | + const bCode = padLeft(bankCode.value, country.bankCodeLength); |
| 118 | + const aNum = padLeft(accountNumber.value, country.accountNumberLength); |
| 119 | + const bban = country.prefix + bCode + aNum; |
| 120 | + const check = calculateCheckDigits(country.code, bban); |
| 121 | + iban.value = `${country.code}${check}${bban}`; |
| 122 | +} |
| 123 | +
|
| 124 | +function calculateCheckDigits(countryCode: string, bban: string) { |
| 125 | + // IBAN check digits according to ISO 7064 |
| 126 | + let replaced = `${bban + countryCode}00`; |
| 127 | + replaced = replaced.replace(/[A-Z]/g, c => (c.codePointAt(0)! - 55).toString()); |
| 128 | +
|
| 129 | + const remainder = BigInt(replaced) % 97n; |
| 130 | + const check = 98n - remainder; |
| 131 | + return check.toString().padStart(2, '0'); |
| 132 | +} |
| 133 | +
|
| 134 | +// Generate Intial IBAN and listen for country changes |
| 135 | +generateIban(); |
| 136 | +
|
| 137 | +watch(selectedCountry, () => { |
| 138 | + generateIban(); |
| 139 | +}); |
| 140 | +</script> |
| 141 | + |
| 142 | +<template> |
| 143 | + <div> |
| 144 | + <div mb-3 flex items-center> |
| 145 | + <span w-100px>Country </span> |
| 146 | + <c-select |
| 147 | + v-model:value="selectedCountry" |
| 148 | + searchable |
| 149 | + flex-1 |
| 150 | + :options="countries.map(({ name, code }) => ({ label: name, value: code }))" |
| 151 | + data-test-id="iban-generator-country-select" |
| 152 | + /> |
| 153 | + </div> |
| 154 | + |
| 155 | + <c-card |
| 156 | + align-center |
| 157 | + mb-3 |
| 158 | + flex |
| 159 | + justify-center |
| 160 | + > |
| 161 | + <c-text-copyable |
| 162 | + style="text-align: center;font-family: monospace" |
| 163 | + :value="iban" |
| 164 | + placeholder="Your IBAN" |
| 165 | + /> |
| 166 | + </c-card> |
| 167 | + |
| 168 | + <div flex justify-center gap-3> |
| 169 | + <c-button @click="generateIban"> |
| 170 | + Refresh |
| 171 | + </c-button> |
| 172 | + </div> |
| 173 | + </div> |
| 174 | +</template> |
| 175 | + |
| 176 | +<style scoped lang="less"> |
| 177 | +::v-deep(.iban-display) { |
| 178 | + textarea { |
| 179 | + text-align: center; |
| 180 | + align-items: center; |
| 181 | + align-content: center; |
| 182 | + align-self: center; |
| 183 | + } |
| 184 | +} |
| 185 | +</style> |
0 commit comments