15.8. ISO 20022

ISO 20022 is the standard that defines many XML messages for FinTech. It is very general, and often countries/orgs define subsets (TVS, technical validation subset) of the schema.

Documentation for message fields can be viewed at https://www.iso20022.org/standardsrepository.

The primary syntax for ISO 20022 messages is XML. To avoid LibEuFin clients having to deal with XML, we define a mapping from ISO 20022 messages into JSON.

The specifics of this mapping are:

  • The JSON should be “idiomatic” and easy to process.
  • When possible, the highly nested structures of ISO 20022 should be flattened.
  • It must be possible round-trip between the LibEuFin JSON and ISO 20022 XML messages. The mapping of message types is not 1-to-1, as some ISO 20022 messages are very similar and can be mapped to the same JSON structure.
  • JSON-mapped messages are not explicitly versioned. Instead, changes are made in a backwards-compatible way, possibly preserving old versions of message elements in the same schema.

15.8.1. Why does LibEuFin not use ISO 20022?

While LibEuFin can ingest ISO 20022 messages (camt.05x, pain.002) and generate them (pain.001), it does not use ISO 20022 in its API and internal data model.

Reasons for not using ISO 20022 directly are:

  1. Impedance mismatch. ISO 20022 messages do not map well to query/response APIs.
  2. Cumbersome to use. Even when ISO 20022 messages are directly mapped to JSON, they are difficult to use due to their verbosity.
  3. Underspecification. Messages like camt.05x leave many “degrees of freedom” when translating the underlying data into a message.
  4. No interoperability. As a result of underspecification, different countries/organizations define their own subset and interpretation rules for ISO 20022 messages. This can lead to even contradictory usage rules. An example for this is how the Swiss and EPC interpretations handle transaction amounts in the presence of multiple currencies.
  5. Redundancy. ISO 20022 are redundant, and often mix aspects of the “presentation logic” with the underlying data model. An example of this is the optional “summary” information in camt.05x messages.

Instead of using ISO 20022 messages directly, LibEuFin leverages the standard in other ways:

  • As the data exchange format with banks
  • As a guideline for naming in data formats
  • As a guideline for which concepts need to be understood by LibEuFin

15.8.2. Implementation notes for camt.05x

Types of amounts in camt messages

  • Entry amount
  • ISO 20022: Mandatory, with debit/credit indicator. Indicates money moving in/out of the account in the account’s currency.
  • LibEuFin: Same.
  • Entry transaction amount
  • ISO 20022: Optional, direction-less. Amount of money moving between the debtor and creditor bank, may not be in the account’s currency (but the “native” currency between the two banks).
  • LibEuFin: Same.
  • Entry instructed amount
  • ISO 20022: Optional, direction-less. Amount of money specified in the payment initiation message. Usually only specified when the amount is in a different currency than the account currency.
  • LibEuFin: Same.
  • Entry counter value amount
    • ISO 20022: Optional, direction-less. Amount in the account’s currency before charges.
    • LibEuFin: Same.
  • Entry announced posting amount
    • (not used by LibEuFin)
  • EntryDetails amount
    • ISO 20022: Optional, with debit-credit indicator. Same as “Entry amount” but for EntryDetails (= logical transactions).
    • LibEuFin: Always present. In Swiss camt messages, the element is always present. In German/Swedish/… camt, we take the “Entry amount” for non-batch entries, whereas in batch entries we use the “EntryDetails transaction amount” with the same debit credit indicator as the whole entry, which by local rules is always in the bank account currency.
  • EntryDetails (transaction / instructed / counter value) amount
    • ISO 20022: Optional, direction-less. Same as “Entry … amount” but for EntryDetails (= logical transactions).
    • Same.
  • EntryDetails announced posting amount
    • (not used by LibEuFin)

LibEuFin schema for account history

FIXME: This is not complete yet.

interface NexusTransactionsReponse {
  entries: NexusAccountEntryItem[];
}

interface NexusAccountEntryItem {
  nexusEntryId: string;

  // Serial number that will increase with each update
  // to the entry.
  nexusStatusSequenceId: number;

  entry: AccountEntryItem;
}

interface AccountEntryItem {

  // At least one of entryId or accountServicerRef
  // must be non-null
  entryId?: string;
  accountServicerRef?: string;

  creditDebitIndicator: "credit" | "debit";
  amount: string;
  currency: string;
  instructedAmountDetails?: {
    amount: string;
    currency: string;
    currencyExchange?: {
      sourceCurrency: string;
      targetCurrency: string;
      unitCurrency: string;
      exchangeRate: string;
      contractId: string;
      quotationDate: string;
    };
  };
  status: "booked" | "pending" | "info";
  valueDate?: string;
  bookingDate?: string;
  mandateId?: string;
  endToEndId?: string;
  messageId?: string;

  creditor?: Party
  creditorAgent?: FinancialInstitution;
  creditorAccount?: FinancialInstitution;

  debtor?: Party
  debtorAgent?: FinancialInstitution;
  debtorAccount?: FinancialInstitution;

  unstructuredRemittanceInformation?: string;
  bankTransactionCode: BankTransactionCode;
}

interface Party {
  name?: string;
  partyType: "private" | "organization";
  otherId?: {
    id: string;
    schemeName?: string;
    issuer?: string;
  };
}

interface Account {
  name?: string;
  currency?: string;
  otherId?: {
    id: string;
    schemeName?: string;
    issuer?: string;
  };
}

interface FinancialInstitution {
  type: "financial-institution";
  name?: string;
  bic?: string;
  otherId?: {
    id: string;
    schemeName?: string;
    issuer?: string;
  };
}

interface BankTransactionCode {
  domain?: string;
  family?: string;
  subfamily?: string;
  proprietaryIssuer?: string;
  proprietaryCode?: string;
}