libeufin

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

commit fd6b16355c3c14111a0c23688ee0c6806e51c3a7
parent e5135f1d692db6556e7a7223e84d0ac981f8eb2c
Author: Antoine A <>
Date:   Fri, 31 Jan 2025 16:26:39 +0100

common: optimize IBAN checksum

Diffstat:
Mcommon/src/main/kotlin/TalerCommon.kt | 39+--------------------------------------
Mcommon/src/main/kotlin/iban.kt | 55++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 55 insertions(+), 39 deletions(-)

diff --git a/common/src/main/kotlin/TalerCommon.kt b/common/src/main/kotlin/TalerCommon.kt @@ -1,6 +1,6 @@ /* * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. + * Copyright (C) 2024-2025 Taler Systems S.A. * * LibEuFin is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -282,43 +282,6 @@ class TalerAmount { } } -@JvmInline -value class IBAN private constructor(val value: String) { - override fun toString(): String = value - - companion object { - private val SEPARATOR = Regex("[\\ \\-]") - - fun parse(raw: String): IBAN { - val iban: String = raw.uppercase().replace(SEPARATOR, "") - val builder = StringBuilder(iban.length + iban.asSequence().map { if (it.isDigit()) 1 else 2 }.sum()) - (iban.subSequence(4, iban.length).asSequence() + iban.subSequence(0, 4).asSequence()).forEach { - if (it.isDigit()) { - builder.append(it) - } else { - builder.append((it.code - 'A'.code) + 10) - } - } - val str = builder.toString() - val mod = str.toBigInteger().mod(97.toBigInteger()).toInt() - if (mod != 1) throw CommonError.Payto("Iban malformed, modulo is $mod expected 1") - return IBAN(iban) - } - - fun rand(): IBAN { - val ccNoCheck = "131400" // DE00 - val bban = (0..10).map { - (0..9).random() - }.joinToString("") // 10 digits account number - var checkDigits: String = "98".toBigInteger().minus("$bban$ccNoCheck".toBigInteger().mod("97".toBigInteger())).toString() - if (checkDigits.length == 1) { - checkDigits = "0${checkDigits}" - } - return IBAN("DE$checkDigits$bban") - } - } -} - @Serializable(with = Payto.Serializer::class) sealed class Payto { abstract val parsed: URI diff --git a/common/src/main/kotlin/iban.kt b/common/src/main/kotlin/iban.kt @@ -1,6 +1,6 @@ /* * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. + * Copyright (C) 2024-2025 Taler Systems S.A. * LibEuFin is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -31,4 +31,57 @@ private val ibanRegex = Regex("^[A-Z]{2}[0-9]{2}[a-zA-Z0-9]{1,30}$") fun validateIban(iban: String): Boolean { return ibanRegex.matches(iban) +} + + +@JvmInline +value class IBAN private constructor(val value: String) { + override fun toString(): String = value + + companion object { + private val SEPARATOR = Regex("[\\ \\-]") + + fun checksum(iban: String): Int = + (iban.substring(4 until iban.length).asSequence() + iban.substring(0 until 4).asSequence()) + .fold(0) { acc, char -> + var checksum = if (char.isDigit()) { + acc * 10 + char.code - '0'.code + } else { + acc * 100 + char.code - 'A'.code + 10 + } + if (checksum > 9_999_999) { + checksum %= 97 + } + checksum + } % 97 + + + fun parse(raw: String): IBAN { + val iban: String = raw.uppercase().replace(SEPARATOR, "") + if (iban.length < 5) { + throw CommonError.Payto("malformed IBAN, string is too small only ${iban.length} char") + } + for (c in iban.substring(0 until 2)) { + if (!c.isLetter()) throw CommonError.Payto("malformed IBAN, malformed country code") + } + for (c in iban.substring(2 until 4)) { + if (!c.isDigit()) throw CommonError.Payto("malformed IBAN, malformed check digit") + } + val checksum = checksum(iban) + if (checksum != 1) throw CommonError.Payto("malformed IBAN, modulo is $checksum expected 1") + return IBAN(iban) + } + + fun rand(): IBAN { + val bban = (0..10).map { + (0..9).random() + }.joinToString("") // 10 digits account number + val checkDigits = 98 - checksum("DE00$bban"); + return if (checkDigits < 10) { + IBAN("DE0$checkDigits$bban") + } else { + IBAN("DE$checkDigits$bban") + } + } + } } \ No newline at end of file