Skip to content

Instantly share code, notes, and snippets.

@miglen
Created September 5, 2025 12:38
Show Gist options
  • Save miglen/209d246fd6d0fa526087a7bb4fb91353 to your computer and use it in GitHub Desktop.
Save miglen/209d246fd6d0fa526087a7bb4fb91353 to your computer and use it in GitHub Desktop.
Bulgarian IBAN Generator & Validator
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