ex_iso20022
ISO 20022 message parsing for Elixir. Currently covers camt.053 (Bank to Customer Statement), the highest-demand message type. More message types are planned.
Installation
# mix.exs
def deps do
[
{:ex_iso20022, "~> 0.1"}
]
endQuick start
xml = File.read!("statement.xml")
case ISO20022.Camt053.parse(xml) do
{:ok, doc} ->
Enum.each(doc.statements, fn stmt ->
IO.puts("Account IBAN : #{stmt.account.iban}")
IO.puts("Currency : #{stmt.account.currency}")
closing = Enum.find(stmt.balances, &(&1.type == :closing_booked))
IO.puts("Closing bal : #{closing.amount} #{closing.currency} (#{closing.credit_debit})")
Enum.each(stmt.entries, fn entry ->
IO.puts(" #{entry.ref} #{entry.credit_debit} #{entry.amount} #{entry.currency}")
end)
end)
{:error, reason} ->
IO.inspect(reason, label: "parse error")
endUsing the bang variant when you are confident the input is valid:
doc = ISO20022.Camt053.parse!(xml)Supported message types
| Module | Message | Versions |
|---|---|---|
ISO20022.Camt053 | Bank to Customer Statement | camt.053.001.02 – 014 |
More message types (camt.052, camt.054, pain.001, pacs.008, …) will be added in
subsequent releases. The top-level ISO20022.parse/1 dispatcher is already in place
and will route to the right module automatically once each type is implemented.
Struct reference
ISO20022.Camt053.Document
The top-level struct returned by parse/1.
| Field | Type | Description |
|---|---|---|
group_header | GroupHeader | Message-level metadata |
statements | [Statement] | One entry per account per period |
ISO20022.Camt053.GroupHeader
| Field | Type | Description |
|---|---|---|
message_id | String | Unique message identifier (max 35 chars) |
created_at | DateTime | UTC creation timestamp |
pagination | map | nil | %{page_number: String, last_page: boolean} |
ISO20022.Camt053.Statement
| Field | Type | Description |
|---|---|---|
id | String | Statement identifier |
electronic_seq_number | integer | nil | Sequence number for gap detection |
created_at | DateTime | nil | Statement generation time |
from_to_date | map | nil | %{from: DateTime, to: DateTime} |
account | Account | Account identification |
balances | [Balance] | At least opening and closing booked balances |
entries | [Entry] | Booked transactions |
ISO20022.Camt053.Account
| Field | Type | Notes |
|---|---|---|
iban | String | nil | Present when account is IBAN-identified |
other_id | String | nil | Present when account uses a non-IBAN scheme |
other_scheme | String | nil |
Scheme code, e.g. "BBAN" |
currency | String | nil |
ISO 4217 alpha code, e.g. "EUR" |
servicer_bic | String | nil | BIC of the account-servicing institution |
name | String | nil | Account name |
ISO20022.Camt053.Balance
amount is always a positive Decimal. The sign is expressed through credit_debit.
| Field | Type | Description |
|---|---|---|
type | atom | See balance types below |
amount | Decimal | Positive amount |
currency | String | ISO 4217 alpha code |
credit_debit | :credit | :debit | :debit means overdraft |
date | Date | Balance reference date |
Balance type atoms:
| Atom | ISO code | Meaning |
|---|---|---|
:opening_booked | OPBD | Opening booked (mandatory) |
:closing_booked | CLBD | Closing booked (mandatory) |
:closing_available | CLAV | Closing available |
:interim_booked | ITBD | Interim booked |
:interim_available | ITAV | Interim available |
:forward_available | FWAV | Forward available |
{:other, code} | any | Unrecognised code |
ISO20022.Camt053.Entry
| Field | Type | Description |
|---|---|---|
ref | String | Bank-assigned entry reference |
amount | Decimal | Positive amount |
currency | String | ISO 4217 alpha code |
credit_debit | :credit | :debit | Direction |
reversal | boolean | true if this cancels a prior entry |
status | :booked |
Always :booked in camt.053 |
booking_date | Date | nil | Date posted to account |
value_date | Date | nil | Value date (may differ from booking date) |
account_servicer_ref | String | nil | Bank's own reference |
bank_transaction_code | BankTxCode | nil | ISO 20022 transaction classification |
additional_info | String | nil | Free-text entry description |
details | [EntryDetails] | Transaction-level detail blocks (batch entries) |
ISO20022.Camt053.BankTxCode
| Field | Type | Example |
|---|---|---|
domain | String | nil | "PMNT" |
family | String | nil | "RCDT", "ICDT", "RDDT" |
sub_family | String | nil | "XBCT", "ESCT", "SALA" |
proprietary_code | String | nil | Bank-specific code |
proprietary_issuer | String | nil | Issuer of the proprietary code |
ISO20022.Camt053.EntryDetails
Present when an entry groups multiple underlying transactions (batch payments).
| Field | Type | Description |
|---|---|---|
batch | map | nil | %{message_id, payment_info_id, number_of_transactions, total_amount} |
transaction_details | [TransactionDetails] | Individual transaction records |
ISO20022.Camt053.TransactionDetails
| Field | Type | Description |
|---|---|---|
refs | map | nil | %{message_id, end_to_end_id, uetr, …} |
amount | Decimal | nil | Individual transaction amount |
currency | String | nil | ISO 4217 |
credit_debit | :credit | :debit | nil | Direction |
related_parties | map | nil | %{debtor, creditor, ultimate_debtor, ultimate_creditor} |
related_agents | map | nil | %{debtor_agent, creditor_agent} |
remittance_info | tuple / nil | {:unstructured, text} or {:structured, %{ref, ref_type, …}} |
purpose | String | nil | ISO 20022 purpose code |
Error handling
{:ok, %ISO20022.Camt053.Document{}}
# Malformed XML
{:error, {:parse_error, reason}}
# Namespace not recognised as a camt.053 variant
{:error, {:unsupported_version, "urn:iso:std:iso:20022:tech:xsd:pain.001.001.09"}}
# Mandatory field absent from an otherwise valid document
{:error, {:missing_required_field, [:group_header, :message_id]}}
{:error, {:missing_required_field, [:statements, 0, :id]}}
# Field present but value could not be parsed
{:error, {:invalid_amount, "N/A", [:statements, 0, :entries, 1, :amount]}}
{:error, {:invalid_date, "32-01-2024"}}
The path in missing_required_field and invalid_* errors follows the struct
hierarchy so it is straightforward to pinpoint the problematic node.
Multi-version support
Real-world bank files use a wide range of schema versions:
urn:iso:std:iso:20022:tech:xsd:camt.053.001.02 # many European banks
urn:iso:std:iso:20022:tech:xsd:camt.053.001.04 # UK, SEPA migration era
urn:iso:std:iso:20022:tech:xsd:camt.053.001.08 # current SWIFT / TARGET2
urn:iso:std:iso:20022:tech:xsd:camt.053.001.11 # newer implementations
urn:iso:std:iso:20022:tech:xsd:camt.053.001.14 # latest ISO (2026)ex_iso20022 detects the version from the root element's xmlns attribute and
normalises to the same struct regardless of input version. All versions 02 – 14 are
accepted. Documents lacking a namespace (some older senders) are also accepted.
License
MIT