Created
September 5, 2025 12:38
-
-
Save miglen/209d246fd6d0fa526087a7bb4fb91353 to your computer and use it in GitHub Desktop.
Bulgarian IBAN Generator & Validator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import random | |
import string | |
from typing import List, Tuple, Dict | |
class BulgarianIBANGenerator: | |
""" | |
Generator and validator for Bulgarian IBANs. | |
Bulgarian IBAN format: BGkk BBBB SSSS SSSS SSSS SS (22 characters) | |
- BG: Country code | |
- kk: Check digits (calculated using MOD-97) | |
- BBBB: 4-letter bank code | |
- SSSS SSSS SSSS SS: 14-digit account number | |
""" | |
# Major Bulgarian banks with their SWIFT/BIC codes (first 4 letters used for IBAN) | |
BULGARIAN_BANKS = { | |
'UNCR': 'UniCredit Bulbank', | |
'STSA': 'DSK Bank (formerly State Savings Bank)', | |
'BNBG': 'Bulgarian National Bank', | |
'RZBB': 'United Bulgarian Bank (UBB)', | |
'FINV': 'First Investment Bank (FIB)', | |
'FIBB': 'First Investment Bank Bulgaria', | |
'SOMB': 'Societe Generale Expressbank', | |
'BUIN': 'Bulgarian-American Credit Bank', | |
'CECB': 'Central Cooperative Bank', | |
'BICC': 'Bank Credit Consortium', | |
'TZBB': 'TBI Bank', | |
'PRCB': 'ProCredit Bank Bulgaria', | |
'IORT': 'Investbank', | |
'BPBI': 'Bulgarian Post Bank', | |
'EAIB': 'Eurobank Bulgaria', | |
'PIRB': 'Piraeus Bank Bulgaria', | |
'CREX': 'Credit Europe Bank Bulgaria', | |
'DOMF': 'D Commercial Bank', | |
'ALBB': 'Allianz Bank Bulgaria', | |
'TEXI': 'Texim Bank', | |
'CITI': 'Citibank Europe Bulgaria', | |
'RAIF': 'Raiffeisenbank Bulgaria', | |
'INGB': 'ING Bank N.V. Bulgaria Branch', | |
} | |
@staticmethod | |
def calculate_iban_check_digits(country_code: str, bank_code: str, account_number: str) -> str: | |
"""Calculate the check digits for an IBAN using the MOD-97 algorithm.""" | |
# Create IBAN with 00 as temporary check digits | |
temp_iban = country_code + '00' + bank_code + account_number | |
# Rearrange: move first 4 characters to end | |
rearranged = temp_iban[4:] + temp_iban[:4] | |
# Convert letters to numbers (A=10, B=11, ..., Z=35) | |
numeric_string = '' | |
for char in rearranged: | |
if char.isalpha(): | |
numeric_string += str(ord(char.upper()) - 55) | |
else: | |
numeric_string += char | |
# Calculate MOD 97 | |
remainder = 0 | |
for digit in numeric_string: | |
remainder = (remainder * 10 + int(digit)) % 97 | |
# Check digits = 98 - remainder | |
check_digits = 98 - remainder | |
return f"{check_digits:02d}" | |
@staticmethod | |
def validate_iban(iban: str) -> bool: | |
"""Validate an IBAN using the MOD-97 algorithm.""" | |
# Remove spaces and convert to uppercase | |
iban = iban.replace(' ', '').upper() | |
# Check length for Bulgarian IBAN | |
if len(iban) != 22 or not iban.startswith('BG'): | |
return False | |
# Rearrange: move first 4 characters to end | |
rearranged = iban[4:] + iban[:4] | |
# Convert letters to numbers | |
numeric_string = '' | |
for char in rearranged: | |
if char.isalpha(): | |
numeric_string += str(ord(char.upper()) - 55) | |
else: | |
numeric_string += char | |
# Calculate MOD 97 | |
remainder = 0 | |
for digit in numeric_string: | |
remainder = (remainder * 10 + int(digit)) % 97 | |
return remainder == 1 | |
@classmethod | |
def generate_random_account_number(cls) -> str: | |
"""Generate a random 14-digit account number.""" | |
return ''.join([str(random.randint(0, 9)) for _ in range(14)]) | |
@classmethod | |
def generate_iban(cls, bank_code: str = None, account_number: str = None) -> str: | |
"""Generate a valid Bulgarian IBAN.""" | |
if bank_code is None: | |
bank_code = random.choice(list(cls.BULGARIAN_BANKS.keys())) | |
if account_number is None: | |
account_number = cls.generate_random_account_number() | |
# Ensure bank code is 4 characters and uppercase | |
bank_code = bank_code.upper()[:4].ljust(4, 'X') | |
# Ensure account number is exactly 14 digits | |
if len(account_number) > 14: | |
account_number = account_number[:14] | |
elif len(account_number) < 14: | |
account_number = account_number.ljust(14, '0') | |
# Calculate check digits | |
check_digits = cls.calculate_iban_check_digits('BG', bank_code, account_number) | |
# Construct IBAN | |
iban = f"BG{check_digits}{bank_code}{account_number}" | |
return iban | |
@classmethod | |
def generate_ibans_for_all_banks(cls, count_per_bank: int = 1) -> List[Dict[str, str]]: | |
"""Generate IBANs for all Bulgarian banks.""" | |
ibans = [] | |
for bank_code, bank_name in cls.BULGARIAN_BANKS.items(): | |
for _ in range(count_per_bank): | |
iban = cls.generate_iban(bank_code) | |
ibans.append({ | |
'iban': iban, | |
'bank_code': bank_code, | |
'bank_name': bank_name, | |
'formatted_iban': f"{iban[:4]} {iban[4:8]} {iban[8:12]} {iban[12:16]} {iban[16:20]} {iban[20:]}" | |
}) | |
return ibans | |
@classmethod | |
def list_all_banks(cls) -> None: | |
"""Print all Bulgarian banks with their codes.""" | |
print("Bulgarian Banks and their SWIFT/BIC codes:") | |
print("-" * 60) | |
for code, name in cls.BULGARIAN_BANKS.items(): | |
print(f"{code}: {name}") | |
print(f"\nTotal banks: {len(cls.BULGARIAN_BANKS)}") | |
def main(): | |
"""Main function with CLI interface for bank selection.""" | |
import sys | |
generator = BulgarianIBANGenerator() | |
print("=== Bulgarian IBAN Generator and Validator ===\n") | |
# Check if bank code provided as command line argument | |
if len(sys.argv) > 1: | |
bank_code = sys.argv[1].upper() | |
account_number = sys.argv[2] if len(sys.argv) > 2 else None | |
if bank_code in generator.BULGARIAN_BANKS: | |
bank_name = generator.BULGARIAN_BANKS[bank_code] | |
iban = generator.generate_iban(bank_code, account_number) | |
print(f"Selected Bank: {bank_name}") | |
print(f"Bank Code: {bank_code}") | |
print(f"Generated IBAN: {iban}") | |
print(f"Formatted: {iban[:4]} {iban[4:8]} {iban[8:12]} {iban[12:16]} {iban[16:20]} {iban[20:]}") | |
print(f"Is Valid: {generator.validate_iban(iban)}") | |
return | |
else: | |
print(f"Error: Bank code '{bank_code}' not found!") | |
print("Available bank codes:") | |
for code, name in generator.BULGARIAN_BANKS.items(): | |
print(f" {code} - {name}") | |
return | |
# Interactive mode - show menu | |
while True: | |
print("\nChoose an option:") | |
print("1. List all banks") | |
print("2. Select a bank by code") | |
print("3. Generate random IBAN") | |
print("4. Validate an IBAN") | |
print("5. Generate IBANs for all banks") | |
print("6. Exit") | |
choice = input("\nEnter your choice (1-6): ").strip() | |
if choice == '1': | |
print("\n" + "="*60) | |
generator.list_all_banks() | |
print("="*60) | |
elif choice == '2': | |
print("\nAvailable bank codes:") | |
sorted_banks = sorted(generator.BULGARIAN_BANKS.items()) | |
for i, (code, name) in enumerate(sorted_banks, 1): | |
print(f"{i:2d}. {code} - {name}") | |
bank_input = input("\nEnter bank code or number: ").strip().upper() | |
# Check if input is a number (bank selection by index) | |
if bank_input.isdigit(): | |
bank_index = int(bank_input) - 1 | |
if 0 <= bank_index < len(sorted_banks): | |
bank_code, bank_name = sorted_banks[bank_index] | |
else: | |
print("Invalid bank number!") | |
continue | |
else: | |
# Input is bank code | |
bank_code = bank_input | |
if bank_code not in generator.BULGARIAN_BANKS: | |
print(f"Bank code '{bank_code}' not found!") | |
continue | |
bank_name = generator.BULGARIAN_BANKS[bank_code] | |
# Ask for account number (optional) | |
account_number = input("Enter account number (14 digits, or press Enter for random): ").strip() | |
if not account_number: | |
account_number = None | |
elif len(account_number) != 14 or not account_number.isdigit(): | |
print("Warning: Account number should be 14 digits. Using as provided...") | |
# Generate IBAN | |
iban = generator.generate_iban(bank_code, account_number) | |
print(f"\n--- Generated IBAN ---") | |
print(f"Bank: {bank_name}") | |
print(f"Code: {bank_code}") | |
print(f"IBAN: {iban}") | |
print(f"Formatted: {iban[:4]} {iban[4:8]} {iban[8:12]} {iban[12:16]} {iban[16:20]} {iban[20:]}") | |
print(f"Valid: {generator.validate_iban(iban)}") | |
elif choice == '3': | |
iban = generator.generate_iban() | |
bank_code = iban[4:8] | |
bank_name = generator.BULGARIAN_BANKS[bank_code] | |
print(f"\n--- Random IBAN ---") | |
print(f"Bank: {bank_name}") | |
print(f"Code: {bank_code}") | |
print(f"IBAN: {iban}") | |
print(f"Formatted: {iban[:4]} {iban[4:8]} {iban[8:12]} {iban[12:16]} {iban[16:20]} {iban[20:]}") | |
elif choice == '4': | |
iban_input = input("Enter IBAN to validate: ").strip().replace(' ', '').upper() | |
is_valid = generator.validate_iban(iban_input) | |
print(f"\nIBAN: {iban_input}") | |
print(f"Valid: {is_valid}") | |
if len(iban_input) >= 8 and iban_input.startswith('BG'): | |
bank_code = iban_input[4:8] | |
bank_name = generator.BULGARIAN_BANKS.get(bank_code, 'Unknown Bank') | |
print(f"Bank: {bank_name} ({bank_code})") | |
elif choice == '5': | |
print("\n--- Sample IBANs for all Bulgarian banks ---") | |
print("-" * 80) | |
sample_ibans = generator.generate_ibans_for_all_banks(1) | |
for iban_info in sample_ibans: | |
print(f"{iban_info['bank_code']} ({iban_info['bank_name'][:25]:<25}): {iban_info['formatted_iban']}") | |
elif choice == '6': | |
print("Goodbye!") | |
break | |
else: | |
print("Invalid choice! Please enter 1-6.") | |
def print_usage(): | |
"""Print usage instructions.""" | |
print("Usage:") | |
print(" python iban_generator.py [BANK_CODE] [ACCOUNT_NUMBER]") | |
print("\nExamples:") | |
print(" python iban_generator.py FINV") | |
print(" python iban_generator.py UNCR 12345678901234") | |
print(" python iban_generator.py") | |
print("\nAvailable bank codes:") | |
for code, name in BulgarianIBANGenerator.BULGARIAN_BANKS.items(): | |
print(f" {code} - {name}") | |
if __name__ == "__main__": | |
import sys | |
if len(sys.argv) > 1 and sys.argv[1] in ['-h', '--help', 'help']: | |
print_usage() | |
else: | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment