summaryrefslogtreecommitdiff
path: root/libeufin/iso20022.rst
blob: e45222a557836ea516e9397f23f5c25f7966d6c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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.


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


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.

.. code-block:: typescript

   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;
   }