# ex_iso20022
[](https://hex.pm/packages/ex_iso20022)
[](https://hexdocs.pm/ex_iso20022)
[](https://github.com/ARTARNA/ex_iso20022/actions/workflows/ci.yml)
[](LICENSE)
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
```elixir
# mix.exs
def deps do
[
{:ex_iso20022, "~> 0.1"}
]
end
```
## Quick start
```elixir
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")
end
```
Using the bang variant when you are confident the input is valid:
```elixir
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
```elixir
{: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