iban.kt (3596B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2024-2025 Taler Systems S.A. 4 5 * LibEuFin is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation; either version 3, or 8 * (at your option) any later version. 9 10 * LibEuFin is distributed in the hope that it will be useful, but 11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 13 * Public License for more details. 14 15 * You should have received a copy of the GNU Affero General Public 16 * License along with LibEuFin; see the file COPYING. If not, see 17 * <http://www.gnu.org/licenses/> 18 */ 19 20 package tech.libeufin.common 21 22 @JvmInline 23 value class IBAN private constructor(val value: String) { 24 override fun toString(): String = value 25 26 companion object { 27 private val SEPARATOR = Regex("[\\ \\-]") 28 29 fun checksum(iban: String): Int = 30 (iban.substring(4 until iban.length).asSequence() + iban.substring(0 until 4).asSequence()) 31 .fold(0) { acc, char -> 32 if (char.isDigit()) { 33 (acc * 10 + char.code - '0'.code) % 97 34 } else { 35 (acc * 100 + char.code - 'A'.code + 10) % 97 36 } 37 } 38 39 40 fun parse(raw: String): IBAN { 41 val iban: String = raw.uppercase().replace(SEPARATOR, "") 42 if (iban.length < 5) { 43 throw CommonError.Payto("malformed IBAN, string is too small only ${iban.length} char") 44 } 45 val countryCode = iban.substring(0 until 2) 46 for (c in countryCode) { 47 if (!c.isLetter()) throw CommonError.Payto("malformed IBAN, malformed country code") 48 } 49 for (c in iban.substring(2 until 4)) { 50 if (!c.isDigit()) throw CommonError.Payto("malformed IBAN, malformed check digit") 51 } 52 val country = try { 53 Country.valueOf(countryCode) 54 } catch (e: IllegalArgumentException) { 55 throw CommonError.Payto("malformed IBAN, unknown country $countryCode") 56 } 57 if (country == Country.DE && iban.length != 22) { 58 // This is allowed for retrocompatibility with libeufin-bank malformed DE IBAN 59 } else if (!country.bbanRegex.matches(iban.substring(4))) { 60 throw CommonError.Payto("malformed IBAN, invalid char") 61 } 62 val checksum = checksum(iban) 63 if (checksum != 1) throw CommonError.Payto("malformed IBAN, modulo is $checksum expected 1") 64 return IBAN(iban) 65 } 66 67 fun rand(country: Country): IBAN { 68 val bban = buildString { 69 for ((repetition, c) in country.rules) { 70 val alphabet = when (c) { 71 IbanC.n -> "0123456789" 72 IbanC.a -> "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 73 IbanC.c -> "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 74 } 75 repeat(repetition) { 76 append(alphabet.random()) 77 } 78 } 79 } 80 val checkDigits = 98 - checksum("${country.name}00$bban"); 81 return if (checkDigits < 10) { 82 IBAN("${country.name}0$checkDigits$bban") 83 } else { 84 IBAN("${country.name}$checkDigits$bban") 85 } 86 } 87 } 88 }