Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add structure to allow guessing of BIC based on IBAN #8

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions lib/bankster.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,17 @@ defmodule Bankster do
"""
@spec iban_validate(String.t()) :: {:ok, String.t()} | {:error, Atom.t()}
defdelegate iban_validate(iban), to: Bankster.Iban, as: :validate

@doc """
Attempts to find the BIC associated with a valid IBAN. Returns an error tuple on invalid data.
Does not support all countries nor all banks in supported countries. Patches are welcome..
## Example
iex(1)> Bankster.bic_calculate("NL91ABNA0417164300")
{:ok, "ABNANL2A XXX"}

iex(2)> Bankster.bic_calculate("AL86751639367318444714198669")
{:error, :unsupported_country}
"""
@spec bic_calculate(String.t()) :: {:ok, String.t()} | {:error, Atom.t()}
defdelegate bic_calculate(iban), to: Bankster.Bic, as: :calculate_bic_from_iban
end
63 changes: 62 additions & 1 deletion lib/bankster/bic.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,65 @@ defmodule Bankster.Bic do
do: Regex.match?(@bic_validation_regex, String.replace(bic, ~r/\s*/, ""))

defp is_valid?(_), do: false
end

@doc """
Attempts to calculate the BIC given a valid IBAN.
"""
@spec calculate_bic_from_iban(String.t()) :: {:ok, String.t()} | {:error, Atom.t()}
def calculate_bic_from_iban(iban) do
case Bankster.Iban.validate(iban) do
{:ok, canon_iban} ->
country = Bankster.Iban.country_code(canon_iban)
bban = Bankster.Iban.bban(canon_iban)
regexes = Bankster.Iban.iban_rules()
matches = Regex.run(regexes[country][:rule], bban, [capture: :all_but_first])
Comment on lines +27 to +32
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think, could with not be "clearer" here?

with {:ok, canon_iban} <- Bankster.Iban.validate(iban),
     {:ok, country} <- {:ok, Bankster.Iban.country_code(canon_iban)},
     {:ok, bban} <- {:ok, Bankster.Iban.country_code(canon_iban)},
     {:ok, regexes} <- {:ok, Bankster.Iban.iban_rules()},
     {:ok, matches} <- {:ok, Regex.run(regexes[country][:rule], bban, capture: :all_but_first)} do
  calculate_bic(country, matches)
else
  {:error, message} ->
    {:error, message}

  _calculation_failed ->
    {:error, :bic_calculation_failed}
end

calculate_bic(country, matches)
{:error, msg} ->
{:error, msg}
end
end

# SPECIFIC BIC MAPPINGS

@doc """
Does the BIC calculation. Should be specified per-country. Done this way instead of a map
to allow more dynamic calculation of the BIC for countries that need it.
"""
@spec calculate_bic(String.t(), [String.t()]) :: {:ok, String.t()} | {:error, :unsupported_country | :unsupported_bank}
defp calculate_bic("NL", matches) do
mapping = %{
"INGB" => "INGBNL2A XXX",
"ABNA" => "ABNANL2A XXX",
"RABO" => "RABONL2U XXX",
"KNAB" => "KNABNL2H XXX",
"BUNQ" => "BUNQNL2A XXX",
"SNSB" => "SNSBNL2A XXX",
"TRIO" => "TRIONL2U XXX",
"ASNB" => "ASNBNL21 XXX",
"RBRB" => "RBRBNL21 XXX"
}
basic_mapping(mapping, List.first(matches))
end

defp calculate_bic(_, _), do: {:error, :unsupported_country}

# BIC MAPPING HELPERS

@spec basic_mapping(%{required(String.t()) => String.t()}, String.t()) :: {:ok, String.t()} | {:error, :unsupported_bank}
defp basic_mapping(mapping, bank) do
if Map.has_key?(mapping, bank) do
{:ok, mapping[bank]}
else
{:error, :unsupported_bank}
end
end

@spec basic_mapping_nested(%{required(String.t()) => %{required(String.t()) => String.t()}}, String.t(), String.t()) :: {:ok, String.t()} | {:error, :unsupported_bank}
defp basic_mapping_nested(mapping, bank, branch) do
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compiler complains "function basic_mapping_nested/3 is unused". Is this just for demonstration purpouses?

if Map.has_key?(mapping, bank) and Map.has_key?(mapping[bank], branch) do
{:ok, mapping[bank][branch]}
else
{:error, :unsupported_bank}
end
end
end
237 changes: 121 additions & 116 deletions lib/bankster/iban.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,122 +4,124 @@ defmodule Bankster.Iban do
"""

@iban_rules %{
"AA" => %{length: 16, rule: ~r/^[0-9A-Z]{12}$/i},
"AD" => %{length: 24, rule: ~r/^[0-9]{8}[0-9A-Z]{12}$/i},
"AE" => %{length: 23, rule: ~r/^[0-9]{19}$/i},
"AL" => %{length: 28, rule: ~r/^[0-9]{8}[0-9A-Z]{16}$/i},
"AO" => %{length: 25, rule: ~r/^[0-9]{21}$/i},
"AT" => %{length: 20, rule: ~r/^[0-9]{16}$/i},
"AX" => %{length: 18, rule: ~r/^[0-9]{14}$/i},
"AZ" => %{length: 28, rule: ~r/^[A-Z]{4}[0-9A-Z]{20}$/i},
"BA" => %{length: 20, rule: ~r/^[0-9]{16}$/i},
"BE" => %{length: 16, rule: ~r/^[0-9]{12}$/i},
"BF" => %{length: 27, rule: ~r/^[0-9]{23}$/i},
"BG" => %{length: 22, rule: ~r/^[A-Z]{4}[0-9]{6}[0-9A-Z]{8}$/i},
"BH" => %{length: 22, rule: ~r/^[A-Z]{4}[0-9]{14}$/i},
"BI" => %{length: 16, rule: ~r/^[0-9]{12}$/i},
"BJ" => %{length: 28, rule: ~r/^[A-Z]{1}[0-9]{23}$/i},
"BL" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i},
"BR" => %{length: 29, rule: ~r/^[0-9]{23}[A-Z]{1}[0-9A-Z]{1}$/i},
"BY" => %{length: 28, rule: ~r/^[0-9A-Z]{4}[0-9]{4}[0-9A-Z]{16}$/i},
"CF" => %{length: 27, rule: ~r/^[0-9]{23}$/i},
"CG" => %{length: 27, rule: ~r/^[0-9]{23}$/i},
"CH" => %{length: 21, rule: ~r/^[0-9]{5}[0-9A-Z]{12}$/i},
"CI" => %{length: 28, rule: ~r/^[A-Z]{1}[0-9]{23}$/i},
"CM" => %{length: 27, rule: ~r/^[0-9]{23}$/i},
"CR" => %{length: 22, rule: ~r/^[0-9]{18}$/i},
"CV" => %{length: 25, rule: ~r/^[0-9]{21}$/i},
"CY" => %{length: 28, rule: ~r/^[0-9]{8}[0-9A-Z]{16}$/i},
"CZ" => %{length: 24, rule: ~r/^[0-9]{20}$/i},
"DE" => %{length: 22, rule: ~r/^[0-9]{18}$/i},
"DJ" => %{length: 27, rule: ~r/^[0-9]{23}$/i},
"DK" => %{length: 18, rule: ~r/^[0-9]{14}$/i},
"DO" => %{length: 28, rule: ~r/^[0-9A-Z]{4}[0-9]{20}$/i},
"DZ" => %{length: 24, rule: ~r/^[0-9]{20}$/i},
"EE" => %{length: 20, rule: ~r/^[0-9]{16}$/i},
"EG" => %{length: 27, rule: ~r/^[0-9]{23}$/i},
"ES" => %{length: 24, rule: ~r/^[0-9]{20}$/i},
"FI" => %{length: 18, rule: ~r/^[0-9]{14}$/i},
"FO" => %{length: 18, rule: ~r/^[0-9]{14}$/i},
"FR" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i},
"GA" => %{length: 27, rule: ~r/^[0-9]{23}$/i},
"GB" => %{length: 22, rule: ~r/^[A-Z]{4}[0-9]{14}$/i},
"GE" => %{length: 22, rule: ~r/^[A-Z]{2}[0-9]{16}$/i},
"GF" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i},
"GI" => %{length: 23, rule: ~r/^[A-Z]{4}[0-9A-Z]{15}$/i},
"GL" => %{length: 18, rule: ~r/^[0-9]{14}$/i},
"GP" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i},
"GQ" => %{length: 27, rule: ~r/^[0-9]{23}$/i},
"GR" => %{length: 27, rule: ~r/^[0-9]{7}[0-9A-Z]{16}$/i},
"GT" => %{length: 28, rule: ~r/^[0-9A-Z]{24}$/i},
"GW" => %{length: 25, rule: ~r/^[0-9A-Z]{2}[0-9]{19}$/i},
"HN" => %{length: 28, rule: ~r/^[A-Z]{4}[0-9]{20}$/i},
"HR" => %{length: 21, rule: ~r/^[0-9]{17}$/i},
"HU" => %{length: 28, rule: ~r/^[0-9]{24}$/i},
"IE" => %{length: 22, rule: ~r/^[A-Z]{4}[0-9]{14}$/i},
"IL" => %{length: 23, rule: ~r/^[0-9]{19}$/i},
"IQ" => %{length: 23, rule: ~r/^[0-9A-Z]{4}[0-9]{15}$/i},
"IR" => %{length: 26, rule: ~r/^[0-9]{22}$/i},
"IS" => %{length: 26, rule: ~r/^[0-9]{22}$/i},
"IT" => %{length: 27, rule: ~r/^[A-Z]{1}[0-9]{10}[0-9A-Z]{12}$/i},
"JO" => %{length: 30, rule: ~r/^[A-Z]{4}[0-9]{4}[0-9A-Z]{18}$/i},
"KM" => %{length: 27, rule: ~r/^[0-9]{23}$/i},
"KW" => %{length: 30, rule: ~r/^[A-Z]{4}[0-9]{22}$/i},
"KZ" => %{length: 20, rule: ~r/^[0-9]{3}[0-9A-Z]{13}$/i},
"LB" => %{length: 28, rule: ~r/^[0-9]{4}[0-9A-Z]{20}$/i},
"LC" => %{length: 32, rule: ~r/^[A-Z]{4}[0-9A-Z]{24}$/i},
"LI" => %{length: 21, rule: ~r/^[0-9]{5}[0-9A-Z]{12}$/i},
"LT" => %{length: 20, rule: ~r/^[0-9]{16}$/i},
"LU" => %{length: 20, rule: ~r/^[0-9]{3}[0-9A-Z]{13}$/i},
"LV" => %{length: 21, rule: ~r/^[A-Z]{4}[0-9A-Z]{13}$/i},
"MA" => %{length: 28, rule: ~r/^[0-9]{24}$/i},
"MC" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i},
"MD" => %{length: 24, rule: ~r/^[0-9A-Z]{20}$/i},
"ME" => %{length: 22, rule: ~r/^[0-9]{18}$/i},
"MF" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i},
"MG" => %{length: 27, rule: ~r/^[0-9]{23}$/i},
"MK" => %{length: 19, rule: ~r/^[0-9]{3}[0-9A-Z]{10}[0-9]{2}$/i},
"ML" => %{length: 28, rule: ~r/^[A-Z]{1}[0-9]{23}$/i},
"MQ" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i},
"MR" => %{length: 27, rule: ~r/^[0-9]{23}$/i},
"MT" => %{length: 31, rule: ~r/^[A-Z]{4}[0-9]{5}[0-9A-Z]{18}$/i},
"MU" => %{length: 30, rule: ~r/^[A-Z]{4}[0-9]{19}[A-Z]{3}$/i},
"MZ" => %{length: 25, rule: ~r/^[0-9]{21}$/i},
"NC" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i},
"NE" => %{length: 28, rule: ~r/^[A-Z]{2}[0-9]{22}$/i},
"NI" => %{length: 32, rule: ~r/^[A-Z]{4}[0-9]{24}$/i},
"NL" => %{length: 18, rule: ~r/^[A-Z]{4}[0-9]{10}$/i},
"NO" => %{length: 15, rule: ~r/^[0-9]{11}$/i},
"PF" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i},
"PK" => %{length: 24, rule: ~r/^[A-Z]{4}[0-9A-Z]{16}$/i},
"PL" => %{length: 28, rule: ~r/^[0-9]{24}$/i},
"PM" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i},
"PS" => %{length: 29, rule: ~r/^[A-Z]{4}[0-9A-Z]{21}$/i},
"PT" => %{length: 25, rule: ~r/^[0-9]{21}$/i},
"QA" => %{length: 29, rule: ~r/^[A-Z]{4}[0-9]{4}[0-9A-Z]{17}$/i},
"RE" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i},
"RO" => %{length: 24, rule: ~r/^[A-Z]{4}[0-9A-Z]{16}$/i},
"RS" => %{length: 22, rule: ~r/^[0-9]{18}$/i},
"SA" => %{length: 24, rule: ~r/^[0-9]{2}[0-9A-Z]{18}$/i},
"SC" => %{length: 31, rule: ~r/^[A-Z]{4}[0-9]{20}[A-Z]{3}$/i},
"SE" => %{length: 24, rule: ~r/^[0-9]{20}$/i},
"SI" => %{length: 19, rule: ~r/^[0-9]{15}$/i},
"SK" => %{length: 24, rule: ~r/^[0-9]{20}$/i},
"SM" => %{length: 27, rule: ~r/^[A-Z]{1}[0-9]{10}[0-9A-Z]{12}$/i},
"SN" => %{length: 28, rule: ~r/^[A-Z]{1}[0-9]{23}$/i},
"ST" => %{length: 25, rule: ~r/^[0-9]{8}[0-9]{13}$/i},
"SV" => %{length: 28, rule: ~r/^[0-9A-Z]{4}[0-9]{20}$/i},
"TD" => %{length: 27, rule: ~r/^[0-9]{23}$/i},
"TF" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i},
"TG" => %{length: 28, rule: ~r/^[A-Z]{2}[0-9]{22}$/i},
"TL" => %{length: 23, rule: ~r/^[0-9]{19}$/i},
"TN" => %{length: 24, rule: ~r/^[0-9]{20}$/i},
"TR" => %{length: 26, rule: ~r/^[0-9]{6}[0-9A-Z]{16}$/i},
"UA" => %{length: 29, rule: ~r/^[0-9]{6}[0-9A-Z]{19}$/i},
"VG" => %{length: 24, rule: ~r/^[A-Z]{4}[0-9]{16}$/i},
"WF" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i},
"XK" => %{length: 20, rule: ~r/^[0-9]{16}$/i},
"YT" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i}
"AA" => %{length: 16, rule: ~r/^[0-9A-Z]{12}$/i}, # No auto-BIC
"AD" => %{length: 24, rule: ~r/^([0-9]{4})([0-9]{4})[0-9A-Z]{12}$/i},
"AE" => %{length: 23, rule: ~r/^([0-9]{3})[0-9]{16}$/i},
"AL" => %{length: 28, rule: ~r/^([0-9]{3})([0-9]{4})[0-9][0-9A-Z]{16}$/i},
"AO" => %{length: 25, rule: ~r/^([0-9]{4})[0-9]{17}$/i},
"AT" => %{length: 20, rule: ~r/^([0-9]{5})[0-9]{11}$/i},
"AX" => %{length: 18, rule: ~r/^([0-9]{6})[0-9]{8}$/i}, # Same as FI
"AZ" => %{length: 28, rule: ~r/^([A-Z]{4})[0-9A-Z]{20}$/i},
"BA" => %{length: 20, rule: ~r/^([0-9]{3})([0-9]{3})[0-9]{10}$/i},
"BE" => %{length: 16, rule: ~r/^([0-9]{3})[0-9]{9}$/i},
"BF" => %{length: 27, rule: ~r/^[0-9]{23}$/i}, # No auto-BIC
"BG" => %{length: 22, rule: ~r/^([A-Z]{4})([0-9]{4})[0-9]{2}[0-9A-Z]{8}$/i},
"BH" => %{length: 22, rule: ~r/^([A-Z]{4})[0-9]{14}$/i},
"BI" => %{length: 16, rule: ~r/^[0-9]{12}$/i}, # No auto-BIC
"BJ" => %{length: 28, rule: ~r/^[A-Z]{1}[0-9]{23}$/i}, # No auto-BIC
"BL" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i}, # No auto-BIC
"BR" => %{length: 29, rule: ~r/^([0-9]{8})([0-9]{5})[0-9]{10}[A-Z]{1}[0-9A-Z]{1}$/i},
"BY" => %{length: 28, rule: ~r/^([0-9A-Z]{4})[0-9]{4}[0-9A-Z]{16}$/i},
"CF" => %{length: 27, rule: ~r/^[0-9]{23}$/i}, # No auto-BIC
"CG" => %{length: 27, rule: ~r/^[0-9]{23}$/i}, # No auto-BIC
"CH" => %{length: 21, rule: ~r/^([0-9]{5})[0-9A-Z]{12}$/i},
"CI" => %{length: 28, rule: ~r/^[A-Z]{1}[0-9]{23}$/i}, # No auto-BIC
"CM" => %{length: 27, rule: ~r/^[0-9]{23}$/i}, # No auto-BIC
"CR" => %{length: 22, rule: ~r/^0([0-9]{3})[0-9]{14}$/i},
"CV" => %{length: 25, rule: ~r/^[0-9]{21}$/i}, # No auto-BIC
"CY" => %{length: 28, rule: ~r/^([0-9]{3})([0-9]{5})[0-9A-Z]{16}$/i},
"CZ" => %{length: 24, rule: ~r/^([0-9]{4})[0-9]{16}$/i},
"DE" => %{length: 22, rule: ~r/^([0-9]{8})[0-9]{10}$/i},
"DJ" => %{length: 27, rule: ~r/^[0-9]{23}$/i}, # No auto-BIC
"DK" => %{length: 18, rule: ~r/^([0-9]{4})[0-9]{10}$/i},
"DO" => %{length: 28, rule: ~r/^([0-9A-Z]{4})[0-9]{20}$/i},
"DZ" => %{length: 24, rule: ~r/^[0-9]{20}$/i}, # No auto-BIC
"EE" => %{length: 20, rule: ~r/^([0-9]{2})([0-9]{2})[0-9]{12}$/i},
"EG" => %{length: 27, rule: ~r/^([0-9]{4})([0-9]{4})[0-9]{15}$/i},
"ES" => %{length: 24, rule: ~r/^([0-9]{4})([0-9]{4})[0-9]{12}$/i},
"FI" => %{length: 18, rule: ~r/^([0-9]{6})[0-9]{8}$/i},
"FO" => %{length: 18, rule: ~r/^([0-9]{4})[0-9]{10}$/i},
"FR" => %{length: 27, rule: ~r/^([0-9]{5})([0-9]{5})[0-9A-Z]{11}[0-9]{2}$/i},
"GA" => %{length: 27, rule: ~r/^[0-9]{23}$/i}, # No auto-BIC
"GB" => %{length: 22, rule: ~r/^([A-Z]{4})([0-9]{6})[0-9]{8}$/i},
"GE" => %{length: 22, rule: ~r/^([A-Z]{2})[0-9]{16}$/i},
"GF" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i}, # No auto-BIC
"GI" => %{length: 23, rule: ~r/^([A-Z]{4})[0-9A-Z]{15}$/i},
"GL" => %{length: 18, rule: ~r/^([0-9]{4})[0-9]{10}$/i},
"GP" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i}, # No auto-BIC
"GQ" => %{length: 27, rule: ~r/^[0-9]{23}$/i}, # No auto-BIC
"GR" => %{length: 27, rule: ~r/^([0-9]{3})([0-9]{4})[0-9A-Z]{16}$/i},
"GT" => %{length: 28, rule: ~r/^([0-9A-Z]{4})[0-9A-Z]{20}$/i},
"GW" => %{length: 25, rule: ~r/^[0-9A-Z]{2}[0-9]{19}$/i}, # No auto-BIC
"HN" => %{length: 28, rule: ~r/^[A-Z]{4}[0-9]{20}$/i}, # No auto-BIC
"HR" => %{length: 21, rule: ~r/^([0-9]{7})[0-9]{10}$/i},
"HU" => %{length: 28, rule: ~r/^([0-9]{3})([0-9]{4})[0-9]{17}$/i},
"IE" => %{length: 22, rule: ~r/^([A-Z]{4})([0-9]{6})[0-9]{8}$/i},
"IL" => %{length: 23, rule: ~r/^([0-9]{3})([0-9]{3})[0-9]{13}$/i},
"IQ" => %{length: 23, rule: ~r/^([0-9A-Z]{4})([0-9]{3})[0-9]{12}$/i},
"IR" => %{length: 26, rule: ~r/^[0-9]{22}$/i}, # No auto-BIC
"IS" => %{length: 26, rule: ~r/^([0-9]{2})([0-9]{2})[0-9]{18}$/i},
"IT" => %{length: 27, rule: ~r/^[A-Z]{1}([0-9]{5})([0-9]{5})[0-9A-Z]{12}$/i},
"JO" => %{length: 30, rule: ~r/^([A-Z]{4})([0-9]{4})[0-9A-Z]{18}$/i},
"KM" => %{length: 27, rule: ~r/^[0-9]{23}$/i}, # No auto-BIC
"KW" => %{length: 30, rule: ~r/^([A-Z]{4})[0-9]{22}$/i},
"KZ" => %{length: 20, rule: ~r/^([0-9]{3})[0-9A-Z]{13}$/i},
"LB" => %{length: 28, rule: ~r/^([0-9]{4})[0-9A-Z]{20}$/i},
"LC" => %{length: 32, rule: ~r/^([A-Z]{4})[0-9A-Z]{24}$/i},
"LI" => %{length: 21, rule: ~r/^([0-9]{5})[0-9A-Z]{12}$/i},
"LT" => %{length: 20, rule: ~r/^([0-9]{5})[0-9]{11}$/i},
"LU" => %{length: 20, rule: ~r/^([0-9]{3})[0-9A-Z]{13}$/i},
"LV" => %{length: 21, rule: ~r/^([A-Z]{4})[0-9A-Z]{13}$/i},
"LY" => %{length: 25, rule: ~r/^([0-9]{3})([0-9]{3})[0-9]{15}$/i},
"MA" => %{length: 28, rule: ~r/^[0-9]{24}$/i}, # No auto-BIC
"MC" => %{length: 27, rule: ~r/^([0-9]{5})([0-9]{5})[0-9A-Z]{11}[0-9]{2}$/i},
"MD" => %{length: 24, rule: ~r/^([0-9A-Z]{2})[0-9A-Z]{18}$/i},
"ME" => %{length: 22, rule: ~r/^([0-9]{3})[0-9]{15}$/i},
"MF" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i}, # No auto-BIC
"MG" => %{length: 27, rule: ~r/^[0-9]{23}$/i}, # No auto-BIC
"MK" => %{length: 19, rule: ~r/^([0-9]{3})[0-9A-Z]{10}[0-9]{2}$/i},
"ML" => %{length: 28, rule: ~r/^[A-Z]{1}[0-9]{23}$/i}, # No auto-BIC
"MQ" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i}, # No auto-BIC
"MR" => %{length: 27, rule: ~r/^([0-9]{5})([0-9]{5})[0-9]{13}$/i},
"MT" => %{length: 31, rule: ~r/^([A-Z]{4})([0-9]{5})[0-9A-Z]{18}$/i},
"MU" => %{length: 30, rule: ~r/^([A-Z]{4}[0-9]{2})([0-9]{2})[0-9]{15}[A-Z]{3}$/i},
"MZ" => %{length: 25, rule: ~r/^[0-9]{21}$/i}, # No auto-BIC
"NC" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i}, # No auto-BIC
"NE" => %{length: 28, rule: ~r/^[A-Z]{2}[0-9]{22}$/i}, # No auto-BIC
"NI" => %{length: 32, rule: ~r/^[A-Z]{4}[0-9]{24}$/i}, # No auto-BIC
"NL" => %{length: 18, rule: ~r/^([A-Z]{4})[0-9]{10}$/i},
"NO" => %{length: 15, rule: ~r/^([0-9]{4})[0-9]{7}$/i},
"PF" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i}, # No auto-BIC
"PK" => %{length: 24, rule: ~r/^([A-Z]{4})[0-9A-Z]{16}$/i},
"PL" => %{length: 28, rule: ~r/^([0-9]{3})([0-9]{4})[0-9]{17}$/i},
"PM" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i}, # No auto-BIC
"PS" => %{length: 29, rule: ~r/^([A-Z]{4})[0-9A-Z]{21}$/i},
"PT" => %{length: 25, rule: ~r/^([0-9]{4})([0-9]{4})[0-9]{13}$/i},
"QA" => %{length: 29, rule: ~r/^([A-Z]{4})[0-9]{4}[0-9A-Z]{17}$/i},
"RE" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i}, # No auto-BIC
"RO" => %{length: 24, rule: ~r/^([A-Z]{4})[0-9A-Z]{16}$/i},
"RS" => %{length: 22, rule: ~r/^([0-9]{3})[0-9]{15}$/i},
"SA" => %{length: 24, rule: ~r/^([0-9]{2})[0-9A-Z]{18}$/i},
"SC" => %{length: 31, rule: ~r/^([A-Z]{4}[0-9]{2})([0-9]{2})[0-9]{16}[A-Z]{3}$/i},
"SD" => %{length: 18, rule: ~r/^([0-9]{2})[0-9]{12}$/i},
"SE" => %{length: 24, rule: ~r/^([0-9]{3})[0-9]{17}$/i},
"SI" => %{length: 19, rule: ~r/^([0-9]{2})([0-9]{3})[0-9]{10}$/i},
"SK" => %{length: 24, rule: ~r/^([0-9]{4})[0-9]{16}$/i},
"SM" => %{length: 27, rule: ~r/^[A-Z]{1}([0-9]{5})([0-9]{5})[0-9A-Z]{12}$/i},
"SN" => %{length: 28, rule: ~r/^[A-Z]{1}[0-9]{23}$/i}, # No auto-BIC
"ST" => %{length: 25, rule: ~r/^([0-9]{4})([0-9]{4})[0-9]{13}$/i},
"SV" => %{length: 28, rule: ~r/^([0-9A-Z]{4})[0-9]{20}$/i},
"TD" => %{length: 27, rule: ~r/^[0-9]{23}$/i}, # No auto-BIC
"TF" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i}, # No auto-BIC
"TG" => %{length: 28, rule: ~r/^[A-Z]{2}[0-9]{22}$/i}, # No auto-BIC
"TL" => %{length: 23, rule: ~r/^([0-9]{3})[0-9]{16}$/i},
"TN" => %{length: 24, rule: ~r/^([0-9]{2})([0-9]{3})[0-9]{13}$/i},
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the entry at https://de.wikipedia.org/wiki/Internationale_Bankkontonummer the regex should be ~r/^([0-9]{2})([0-9]{3})[0-9]{15}$/i

"TR" => %{length: 26, rule: ~r/^([0-9]{5})0[0-9A-Z]{16}$/i},
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rule should be ~r/^([0-9]{5})[0-9][0-9A-Z]{16}$/i

"UA" => %{length: 29, rule: ~r/^([0-9]{6})[0-9A-Z]{19}$/i},
"VG" => %{length: 24, rule: ~r/^([A-Z]{4})[0-9]{16}$/i},
"WF" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i}, # No auto-BIC
"XK" => %{length: 20, rule: ~r/^([0-9]{4})[0-9]{12}$/i},
"YT" => %{length: 27, rule: ~r/^[0-9]{10}[0-9A-Z]{11}[0-9]{2}$/i} # No auto-BIC
}

@replacements %{
Expand Down Expand Up @@ -309,6 +311,9 @@ defmodule Bankster.Iban do
@spec valid?(String.t()) :: boolean
def valid?(iban), do: match?({:ok, _}, validate(iban))

def iban_rules(), do: @iban_rules
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this only used as a private function? What is the reason for this function?



##################################################
## HELPERS

Expand Down