summaryrefslogtreecommitdiff
path: root/design-documents/023-taler-kyc.rst
blob: 51d0427b23cb550872fa8320588c4bba961d4e76 (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
DD 023: Taler KYC
#################

Summary
=======

This document discusses the Know-your-customer (KYC) processes supported by Taler.


Motivation
==========

To legally operate, Taler has to comply with KYC regulation that requires
banks to identify parties involved in transactions at certain points.


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

Taler needs to run KYC checks in the following circumstances:

* Customer withdraws money over a monthly threshold

  * exchange triggers KYC
  * key: IBAN (encoded as payto:// URI)

* Wallet receives (via refunds) money resulting in a balance over a threshold

  * this is a client-side restriction
  * key: reserve (=KYC account) long term public key per wallet (encoded as payto:// URI)

* Wallet receives money via P2P payments

  * key: reserve (=KYC account) long term public key per wallet (encoded as payto:// URI)

* Merchant receives money (Q: any money, or above a monthly threshold?)

  * key: IBAN (encoded as payto:// URI)



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

Exchange modifications
^^^^^^^^^^^^^^^^^^^^^^

We introduce a new ``wire_targets`` table into the exchange database. This
table is referenced as the source or destination of payments (regular deposits
and also P2P payments).  A positive side-effect is that we reduce duplication
in the ``reserves_in``, ``wire_out`` and ``deposits`` tables as they can
reference this table.  In this table, we additionally store information
related to the KYC status of the underlying payto://-URI.

A new ``/kyc/`` endpoint is based on the ``wire_targets`` serial
number. Access is ``authenticated`` by also passing the hash of the
payto://-URI (weak authentication is acceptable, as the KYC status or the
ability to initiate a KYC process are not very sensitive).  Given this pair,
the ``/kyc/`` endpoint returns either the (positive) KYC status or redirects
the client (302) to the current stage of the KYC process.  The redirection is
offered using an HTTP-redirect for Web-based clients and a JSON body with
information for triggering a browser-based KYC process using OAuth 2.0.

When withdrawing, the exchange checks if the KYC status is acceptable.  If no
KYC was done and if either the amount withdrawn over the last X days exceeds
the threshold or the reserve received received a P2P transfer, then a ``202
Accepted`` is returned which redirects the consumer to the new ``/kyc/``
handler.

When depositing, the exchange checks the KYC status and if negative, returns an
additional information field that tells the merchant the ``wire_target_serial``
number needed to begin the KYC process (this is independent of the amount)
at the new ``/kyc/`` handler.

When tracking deposits, the exchange also adds the ``wire_target_serial`` to
the reply if the KYC status is negative.

The aggregator is modified to only SELECT deposits where the ``wire_target``
has the KYC status set to positive (unless KYC is disabled in the exchange
configuration).


  ..note::

    Unrelated: We may want to consider directly deleting prewire records
    instead of setting them to ``finished`` in ``taler-exchange-transfer``.



Exchange database schema changes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Note that there is may be some slight complication in the migration as the
h_wire in deposits is salted, while the h_payto in the new wire_targets is
expected to be unsalted. So converting the existing information to create the
wire_targets table will be tricky!

We can *either* not support a fully automatic migration, or do an "expensive"
migration with C logic (so not just SQL statements).

.. sourcecode:: sql

  -- Everything in one big transaction
  BEGIN;
  -- Check patch versioning is in place.
  SELECT _v.register_patch('exchange-TBD', NULL, NULL);
  --
  CREATE TABLE IF NOT EXISTS wire_targets
  (wire_target_serial_id BIGSERIAL UNIQUE
  ,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=64),
  ,payto_uri STRING NOT NULL
  ,kyc_ok BOOLEAN NOT NULL DEFAULT (false)
  ,oauth_username STRING NOT NULL
  ,PRIMARY KEY (h_wire)
  );
  COMMENT ON TABLE wire_targets
    IS 'All recipients of money via the exchange';
  COMMENT ON COLUMN wire_targets.payto_uri
    IS 'Can be a regular bank account, or also be a URI identifying a reserve-account (for P2P payments)';
  COMMENT ON COLUMN wire_targets.h_payto
    IS 'Unsalted hash of payto_uri';
  COMMENT ON COLUMN wire_targets.kyc_ok
    IS 'true if the KYC check was passed successfully';
  COMMENT ON COLUMN wire_targets.oauth_username
    IS 'Name of the user that was used for OAuth 2.0-based legitimization';
  --
  -- NOTE: logic to fill wire_target missing, so this
  -- CANNOT work if the database contains any data!
  --
  ALTER TABLE wire_out
    ADD COLUMN wire_target_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
    DROP COLUMN wire_target;
  COMMENT ON COLUMN wire_out.wire_target_serial_id
    IS 'Identifies the target bank account and KYC status';
  --
  ALTER TABLE reserves_in
    ADD COLUMN wire_source_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
    DROP COLUMN sender_account_details;
  COMMENT ON COLUMN wire_out.wire_target_serial_id
    IS 'Identifies the target bank account and KYC status';
  --
  ALTER TABLE reserves_close
    ADD COLUMN wire_source_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
    DROP COLUMN receiver_account;
  COMMENT ON COLUMN reserves_close.wire_target_serial_id
    IS 'Identifies the target bank account and KYC status. Note that closing does not depend on KYC.';
  --
  ALTER TABLE deposits
    ADD COLUMN wire_target_serial_id INT8 NOT NULL,
    DROP COLUMN h_wire,
    DROP COLUMN wire;
  COMMENT ON COLUMN deposits.wire_target_serial_id
    IS 'Identifies the target bank account and KYC status';
  -- Complete transaction
  COMMIT;


TODO: Check if we missed miss any tables to migrate!


Merchant modifications
^^^^^^^^^^^^^^^^^^^^^^

We introduce new ``kyc_status``, ``kyc_timestamp`` and ``kyc_serial`` fields
into a new table with primary keys ``exchange_url`` and ``account``.  This
status is updated whenever a deposit is created or tracked, or whenever the
mechant backend receives a ``/kyc/`` response from the exchange.  Initially,
``kyc_serial`` is zero, indicating that the merchant has not yet made any
deposits and thus does not have an account at the exchange.

A new private endpoint ``/kyc`` is introduced which allows frontends to
request the ``/kyc`` status of any configured account (including with long
polling).  If the KYC status is negative or the ``kyc_timestamp`` not recent
(say older than one month), the merchant backend will re-check the KYC status
at the exchange (and update its cached status).  The endpoint then returns
either that the KYC is OK, or information (same as from the exchange endpoint)
to begin the KYC process.

The merchant backend uses the new field to remember that a KYC is pending
(after ``/deposit``, or tracing deposits) and the SPA then shows a
notification whenever the staff is logged in to the system.  The notification
can be hidden for the current day (remembered in local storage).

The notification links to a (new) KYC status page. When opened, the KYC status
page first re-checks the KYC status with the exchange.  If the KYC is still
unfinished, that page contains another link to begin the KYC process
(redirecting to the OAuth 2.0 login page of the legitimization resource
server), otherwise it shows that the KYC process is done. If the KYC is
unfinished, the SPA should use long-polling on the KYC status on this page to
ensure it is always up-to-date, and change to ``KYC satisfied`` should the
long-poller return with positive news.


Bank requirements
^^^^^^^^^^^^^^^^^

The exchange primarily requires an OAuth 2.0 login page where the user
can either login (and share an access token that grants access to only
the username) or register to initiate the KYC process.


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

We may not need the oauth_username, but it seems saner to store it to
provide a link to the legitimization resource server.

We could also store the access token, but that seems slightly more
dangerous and given the close business relationship is unnecessary.

We may want to store some additional "permission level" obtained from the
resource server to say for which of the operations (see requirements section)
the legitimization is sufficient.



Drawbacks
=========


Discussion / Q&A
================

(This should be filled in with results from discussions on mailing lists / personal communication.)