libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

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 }