# Copyright (C) 2023-2026 Pain001. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
# Licensed under either of the Apache License, Version 2.0 or the MIT
# License, at your option. You may not use this file except in
# compliance with one of those licences. Copies are provided in
# LICENSE-APACHE and LICENSE-MIT.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Licences is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the applicable Licence for the specific language
# governing permissions and limitations.
"""BIC (Business Identifier Code) / SWIFT Code validator.
This module implements ISO 9362 format validation for BIC/SWIFT codes.
It validates format structure to catch typos before XML generation.
Example:
>>> from pain001.validation.bic_validator import validate_bic
>>>
>>> # Valid BIC (8 characters)
>>> is_valid, error = validate_bic("DEUTDEFF")
>>> assert is_valid
>>>
>>> # Valid BIC (11 characters with branch code)
>>> is_valid, error = validate_bic("DEUTDEFF500")
>>> assert is_valid
>>>
>>> # Invalid BIC (wrong length)
>>> is_valid, error = validate_bic("DEUTDE")
>>> assert not is_valid
Standards:
- ISO 9362:2022 - Banking — Banking telecommunication messages — Business identifier code (BIC)
"""
import functools
try: # pragma: no cover - presence depends on the install
import pain001_fast as _fast # type: ignore[import-not-found]
_FAST_AVAILABLE = True
except ImportError: # pragma: no cover - presence depends on the install
_fast = None
_FAST_AVAILABLE = False
from pain001.exceptions import InvalidBICError
# ISO 3166-1 alpha-2 country codes (SEPA + major financial centers)
VALID_COUNTRY_CODES = {
"AD",
"AE",
"AL",
"AR",
"AT",
"AU",
"AZ",
"BA",
"BE",
"BG",
"BH",
"BR",
"BY",
"CA",
"CH",
"CL",
"CN",
"CO",
"CR",
"CY",
"CZ",
"DE",
"DK",
"DO",
"EE",
"EG",
"ES",
"FI",
"FO",
"FR",
"GB",
"GE",
"GI",
"GL",
"GR",
"GT",
"HK",
"HR",
"HU",
"ID",
"IE",
"IL",
"IN",
"IQ",
"IS",
"IT",
"JO",
"JP",
"KR",
"KW",
"KZ",
"LB",
"LC",
"LI",
"LT",
"LU",
"LV",
"MC",
"MD",
"ME",
"MK",
"MR",
"MT",
"MU",
"MX",
"MY",
"NL",
"NO",
"NZ",
"PH",
"PK",
"PL",
"PS",
"PT",
"QA",
"RO",
"RS",
"RU",
"SA",
"SE",
"SG",
"SI",
"SK",
"SM",
"TH",
"TN",
"TR",
"TW",
"UA",
"US",
"VA",
"VG",
"XK",
"ZA",
}
[docs]
def validate_bic(
bic: str, field: str | None = None, strict: bool = True
) -> tuple[bool, str]:
"""Validate BIC/SWIFT code format.
This is the main entry point for BIC validation. It performs ISO 9362
format validation.
Args:
bic: BIC/SWIFT code to validate.
field: Optional field name for error reporting.
strict: If True, raise InvalidBICError on failure. If False, return tuple.
Returns:
Tuple of (is_valid, error_message). If strict=True and invalid, raises exception.
Raises:
InvalidBICError: If strict=True and BIC is invalid.
Example:
>>> # Non-strict mode (returns tuple)
>>> is_valid, error = validate_bic("DEUTDEFF", strict=False)
>>> assert is_valid
>>>
>>> # Strict mode (raises exception on error)
>>> try:
... validate_bic("INVALID", field="debtor_agent")
... except InvalidBICError as e:
... print(f"Invalid: {e}")
"""
is_valid, error = validate_bic_format(bic)
if not is_valid:
if strict:
raise InvalidBICError(
message=error,
bic=bic,
field=field,
reason="Invalid BIC format (ISO 9362)",
)
return False, error
return True, ""
[docs]
def validate_bic_safe(bic: str, field: str | None = None) -> bool:
"""Validate BIC and return True/False (never raises exceptions).
This is a convenience wrapper for validate_bic with strict=False.
Useful when you only need a boolean result without error details.
When ``pain001-fast`` is available, delegates to fast structural native check.
Args:
bic: BIC/SWIFT code to validate.
field: Optional field name (unused, for API compatibility).
Returns:
True if BIC is valid, False otherwise.
Example:
>>> if validate_bic_safe("DEUTDEFF"):
... print("Valid BIC")
"""
if _FAST_AVAILABLE and _fast is not None:
return bool(_fast.validate_bic(bic))
is_valid, _ = validate_bic(bic, field=field, strict=False)
return is_valid