feat: validate IBAN in backend

This commit is contained in:
barredterra 2025-08-29 15:21:56 +02:00
parent 7b0067d0ad
commit b1c7821911
3 changed files with 63 additions and 0 deletions

View file

@ -993,6 +993,7 @@ class BaseDocument:
from frappe.utils import (
split_emails,
validate_email_address,
validate_iban,
validate_name,
validate_phone_number,
validate_phone_number_with_country_code,
@ -1031,6 +1032,9 @@ class BaseDocument:
if data_field_options == "URL":
validate_url(data, throw=True)
if data_field_options == "IBAN":
validate_iban(data, throw=True)
def _validate_constants(self):
if frappe.flags.in_import or self.is_new() or self.flags.ignore_validate_constants:
return

View file

@ -34,6 +34,7 @@ from frappe.utils import (
get_site_info,
get_sites,
get_url,
is_valid_iban,
money_in_words,
parse_and_map_trackers_from_url,
parse_timedelta,
@ -479,6 +480,26 @@ class TestValidationUtils(IntegrationTestCase):
for name in invalid_names:
self.assertRaises(frappe.InvalidNameError, validate_name, name, True)
def test_validate_iban(self):
valid_ibans = [
"GB82 WEST 1234 5698 7654 32",
"DE91 1000 0000 0123 4567 89",
"FR76 3000 6000 0112 3456 7890 189",
]
invalid_ibans = [
# wrong checksum (3rd place)
"GB72 WEST 1234 5698 7654 32",
"DE81 1000 0000 0123 4567 89",
"FR66 3000 6000 0112 3456 7890 189",
]
for iban in valid_ibans:
self.assertTrue(is_valid_iban(iban))
for not_iban in invalid_ibans:
self.assertFalse(is_valid_iban(not_iban))
class TestImage(IntegrationTestCase):
def test_strip_exif_data(self):

View file

@ -261,6 +261,44 @@ def validate_url(
return is_valid
def validate_iban(iban: str, throw: bool = False) -> bool:
from frappe import _
valid = is_valid_iban(iban)
if not valid and throw:
frappe.throw(frappe._("'{0}' is not a valid IBAN").format(frappe.bold(iban)))
return valid
def is_valid_iban(iban: str) -> bool:
"""
Algorithm: https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
"""
if not iban:
return False
def encode_char(c):
# Position in the alphabet (A=1, B=2, ...) plus nine
return str(9 + ord(c) - 64)
# remove whitespaces, upper case to get the right number from ord()
iban = iban.replace(" ", "").upper()
# Move country code and checksum from the start to the end
flipped = iban[4:] + iban[:4]
# Encode characters as numbers
encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped]
try:
to_check = int("".join(encoded))
except ValueError:
return False
return to_check % 97 == 1
def random_string(length: int) -> str:
"""generate a random string"""
import string