summaryrefslogtreecommitdiff
path: root/design-documents/050-libeufin-nexus.rst
blob: 6049bda6972c09a3c37af51530c17f95783d9b59 (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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
DD 50: Libeufin-Nexus
#####################

Summary
=======

This document proposes a new design for the libeufin Nexus component,
focusing on the user-interaction and where what state is to be stored.


Motivation
==========

The existing Nexus design is overly complex to configure, develop and
maintain.  It supports EBICS features we do not need, and lacks key features
(like long-polling) that are absolutely needed.

..
  long-polling at the TWG is NOT NetzBon-critical, as the TWG is only offered
  by the Bank.

We also have several implementations with Nexus, Bank and Depolymerization
subsystems, and it would be good to combine some of them.

Requirements
============

  * Easy to use, high-level abstraction over EBICS details
  * Reduce complexity, no multi-account, multi-connection support
  * No general EBICS client, Taler-specific logic
  * Support for Taler facade, including bouncing of transactions with malformed subject
  * Clear separation between configuration and runtime, minimal runtime footprint
  * No built-in cron-jobs, background tasks runnable via systemd (one-shot and persistent mode)
  * Configuration style same as other GNUnet/Taler components
  * No private keys in database, as in other Taler components
  * Enable future unified implementation with Depolymerization to share database and REST API logic

Proposed Solution
=================

Split up Nexus into four components:

  * nexus-ebics-setup: register account with EBICS server and perform key generation and exchange
  * nexus-ebics-fetch: obtain wire transfers and payment status from EBICS server
  * nexus-ebics-submit: send payment initiation messages to EBICS server
  * nexus-httpd: serve Taler REST APIs (wire gateway API, revenue API) to Taler clients and implement facade logic

All four components should read a simple INI-style configuration file,
possibly with component-specific sections.

Configuration file
------------------

.. code-block:: shell-session

  [nexus-ebics]
  CURRENCY = EUR
  HOST_BASE_URL = http://ebics.bank.com/
  HOST_ID = mybank
  USER_ID = myuser
  PARTNER_ID = myorg
  IBAN = MY-IBAN
  BIC = MY-BIC
  NAME = MY NAME
  BANK_PUBLIC_KEYS_FILE = enc-auth-keys.json
  CLIENT_PRIVATE_KEYS_FILE = my-private-keys.json
  BANK_DIALECT = postfinance # EBICS+ISO20022 style used by the bank.
  
  [nexus-postgres]
  CONFIG = postgres:///libeufin-nexus
  
  [nexus-ebics-fetch]
  FREQUENCY = 30s # used when long-polling is not supported
  STATEMENT_LOG_DIRECTORY = /tmp/ebics-messages/
  
  [nexus-ebics-submit]
  FREQUENCY = 30s # use 0 to always submit immediately (via LISTEN trigger)
  
  [nexus-httpd]
  PORT = 8080
  UNIXPATH =
  SERVE = tcp | unix
  
  [nexus-httpd-wire-gateway-facade]
  ENABLED = YES
  AUTH_METHOD = token
  AUTH_TOKEN = "secret-token:foo"
  
  [nexus-httpd-revenue-facade]
  ENABLED = YES
  AUTH_METHOD = token
  AUTH_TOKEN = "secret-token:foo"


File contents: BANK_PUBLIC_KEYS_FILE
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

JSON with 3 fields:

  * bank_encryption_public_key (base32)
  * bank_authentication_public_key (base32)
  * accepted (boolean)

File contents: CLIENT_PRIVATE_KEYS_FILE
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

JSON with:

  * signature_private_key (base32)
  * encryption_private_key (base32)
  * authentication_private_key (base32)
  * submitted_ini (boolean)
  * submitted_hia (boolean)

Database schema
---------------

.. code-block:: shell-session

  CREATE TABLE incoming_transactions
    (incoming_transaction_id INT8 GENERATED BY DEFAULT AS IDENTITY
    ,amount taler_amount NOT NULL
    ,wire_transfer_subject TEXT
    ,execution_time INT8 NOT NULL
    ,debit_payto_uri TEXT NOT NULL
    ,bank_transfer_id TEXT NOT NULL -- EBICS or Depolymerizer (generic)
    ,bounced BOOL DEFAULT FALSE -- to track if we bounced it
    );
  
  CREATE TABLE outgoing_transactions
    (outgoing_transaction_id INT8 GENERATED BY DEFAULT AS IDENTITY
    ,amount taler_amount NOT NULL
    ,wire_transfer_subject TEXT
    ,execution_time INT8 NOT NULL
    ,credit_payto_uri TEXT NOT NULL
    ,bank_transfer_id TEXT NOT NULL
    );
  
  CREATE TABLE initiated_outgoing_transactions
    (initiated_outgoing_transaction_id INT8 GENERATED BY DEFAULT AS IDENTITY -- used as our ID in PAIN
    ,amount taler_amount NOT NULL
    ,wire_transfer_subject TEXT
    ,execution_time INT8 NOT NULL
    ,credit_payto_uri TEXT NOT NULL
    ,out_transaction_id INT8 REFERENCES outgoing_transactions (out_transaction_id)
    ,submitted BOOL DEFAULT FALSE 
    ,hidden BOOL DEFAULT FALSE -- FIXME: exaplain this.
    ,client_request_uuid TEXT NOT NULL UNIQUE
    ,failure_message TEXT -- NOTE: that may mix soon failures (those found at initiation time), or late failures (those found out along a fetch operation)
    );

  COMMENT ON COLUMN initiated_outgoing_transactions.out_transaction_id
    IS 'Points to the bank transaction that was found via nexus-fetch.  If "submitted" is false or nexus-fetch could not download this initiation, this column is expected to be NULL.'

nexus-ebics-setup
-----------------

The ebics-setup tool performs the following:

  * Checks if the require configuration options are present and well-formed
    (like the file names), if not exits with an error message. Given
    --check-full-config, also sanity-check the configuration options of the
    other subsystems.

  * Checks if the private keys file exists, if not creates new private keys
    with flags "not submitted".

  * If any private key flags are set to "not submitted" or a command-line
    override is given (--force-keys-resubmission), attempt to submit the
    corresponding client public keys to the bank. If the bank accepts the
    client public key, update the flags to "submitted".

  * If a public key was submitted or if a command-line override
    (--generate-registration-pdf) is given, generate a PDF with the public key
    for the user to print and send to the bank.

  * Checks if the public keys of the bank already exist on disk, if not try to
    download them with a flag "not accepted". If downloading fails, display a
    message asking the user to register the private keys (and give override
    options for re-submission of private keys or re-generation of the
    registration PDF).

  * If we just downloaded public keys, display the corresponding public keys
    (or fingerprints) to the user and ask the user to interactively confirm to
    accept them (or auto-accept via --auto-accept-keys). If the user accepted
    the public key, update the flag to "accepted".

nexus-ebics-fetch
-----------------

  * Fetches by default all incoming and outgoing bank transactions and error
    messages and inserts them into the Postgres database tables (including
    updating the initiated outgoing transaction table). First, considers the
    last transactions in the database and fetches statements from that day
    forward (inclusive). Afterwards, fetches reports and when the day rolls
    over (before committing any transactions with the next day!) also fetches a
    final statement of the previous day, thus ensuring we get a statement
    every day plus intra-day reports.
    
    .. note::
      
      (1) "from that day forward (inclusive)" must **not** rely on EBICS returning
      the unseen messages: that's because they might **already** be downloaded but
      never made it to the database.
      
      (2) "and when the day rolls over".  When does a day roll over? => A day rolls
      over when the current time is at least on the day after the transaction with the
      most recent timestamp that's stored in the database.

      (3) "Afterwards, fetches reports".  This must happen **only after** any possible
      previous statement got downloaded.

      To summarize: at any point in time the database must contain (the content of) any
      possible statement up to the current time, plus any possible report up to the current
      time (in case that's not covered by any statement so far).

  * Bounces transactions with mal-formed wire transfer subjects.

  * Optionally logs EBICS messages to disk, one per file, based on
    configuration.  Filenames must include the timestamp of the download.  The
    date must be in the path and the time of day at the beginning of the
    filename. This will facilitate easy deletion of logs.

  * Optionally only fetches reports (--only-reports) or statements (--only-statements)
    or only error messages (--only-failures).

  * Optionally terminates after one fetch (--transient) or re-fetches based
    on the configured frequency.

  * Terminates hard (with error code) if incoming transactions are not in the
    expected (configured) currency.


nexus-ebics-submit
------------------

  * Generates a payment initiation message for all client-initiated outgoing
    transactions that have not yet been initiated.  If the server accepts the
    message, sets the initiated flag in the table to true. The EBICS order ID
    is set to the lowest initiated_outgoing_transaction_id in the transaction
    set modulo 2^20 encoded in BASE36.  The payment information ID is set to
    the initiated_outgoing_transaction_id of each transaction as a text
    string.  The message identification is set to the lowest
    initiated_outgoing_transaction_id plus ("-") the highest
    initiated_outgoing_transaction_id as a text string.

  * Optionally terminates after one fetch (--transient) or re-submits based
    on the configured frequency.

  * If configured frequency is zero (default), listens to notifications from
    nexus-httpd for insertions of outgoing payment initiation records.


nexus-httpd
-----------

  * Offers REST APIs as per configuration.

  * Listens to notifications from nexus-ebics-fetch to run facade-logic and
    wake-up long pollers.

  * Offers a *new* REST API to list failed (initiated outgoing) transactions
    and allows the user to re-initiate those transactions (by creating new
    records in the initiated outgoing transactions table). Also allows the
    user to set the "hidden" flag on failed transactions to not show them
    anymore.


Definition of Done
==================

  * Code implemented
  * Testcases migrated (including exchange, merchant, etc.)
  * Man pages updated
  * Manual updated
  * Tested with actual banks (especially error handling and idempotency)
  * Tested against server-side EBICS mock (to be resurrected)
  * Tested against various ISO 20022 messages


Alternatives
============

  * Only run Taler on top of Bitcoin.

Drawbacks
=========

  * Uses EBICS.

Discussion / Q&A
================
(This should be filled in with results from discussions on mailing lists / personal communication.)

* From private discussion: bouncing goes inside nexus-fetch as
  it saves one database event, makes the HTTPd simpler, and lets
  the bouncing happen even when no HTTPd runs.

* Sign-up PDF is ever only generated if *both* INI & HIA have the "submitted" state.

* What is 'override option for re-submission of private keys?'.
  --force-keys-submission already re-submits the keys but it does not
  override them.  If the user wants new keys, they can easily remove
  the keys file on disk.  That makes the CLI shorter.

* Implementation sticks to the IBAN found in the configuration, **if** the bank
  does not show any IBAN related to the EBICS subscriber.

* from nexus-ebics-submit: "if the server accepts the request, sets the
  initiated flag in the table to true".  May there be a case where the
  server accepted the request, but the client never got any response (some
  network issue..), and therefore didn't set the submitted flag, ending
  up in submitting the payment twice?  Also: flagging the payment _after_
  the bank response, may lead double-submission even if the HTTP talk ended
  well: it suffices to crash after having received a "200 OK" response but
  before setting the submitted flag to the database.

* the ebics-submit section mentions the EBICS order ID.  The following excerpt
  was found however at page 88 of the EBICS 3 specifications: 

  ``OrderID is only present if a file is transmitted to the bank relating to an order with an
  already existing order number (only allowed for AdminOrderType = HVE or HVS)``

  Nexus does not support HVE or HVS.

* As of private communication, the responsibility of submitting idempotent payments
  relies on the use of ``request_uid`` (a database column of the initiated payment)
  as the ``MsgId`` value of the corresponding pain.001 document.

* ``submitted`` column of an initiated payment evolved into the following enum:
  
  .. code-block:: shell-session
    
    CREATE TYPE submission_state AS ENUM (
      'unsubmitted'
      ,'transient_failure'
      ,'permanent_failure'
      ,'success'
      ,'never_heard_back'
    );

  * ``unsubmitted``: default state when a payment is initiated
  * ``transient_failure``: submission failed but can be retried, for example after a network issue.
  * ``permanent_failure``: EBICS- or bank-technical error codes were not EBICS_OK (nor any tolerated EBICS code like EBICS_NO_DOWNLOAD_DATA_AVAILABLE), never retry.  
  * ``never_heard_back``: the payment initiation submission has **been** ``success`` but it was never confirmed by any outgoing transaction (from a camt.5x document) or any pain.002 report.  It is responsability of a garbage collector to set this state after a particular time period.

* the initiated_outgoing_transactions table takes two more columns:
  ``last_submission_date``, a timestamp in microseconds, and a
  ``submission_counter``.  Both of them would serve to decide retry
  policies.

* the ``failure_text`` column at the initiated_outgoing_transactions table
  should contain a JSON object that contains any useful detail about the problem.
  That *could* be modeled after the Taler `ErrorDetail <https://docs.taler.net/core/api-common.html#tsref-type-ErrorDetail>`_, where at least the error code and the hint fields are provided.