.. This file is part of GNU TALER. Copyright (C) 2014-2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 2.1, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with TALER; see the file COPYING. If not, see @author Christian Grothoff @author Florian Dold .. _KycOperatorManual: Exchange KYC/AML Operator Manual ################################ .. contents:: Table of Contents :depth: 1 :local: Introduction ============ About GNU Taler --------------- .. include:: frags/about-taler.rst About this manual ----------------- This chapter describes how to setup certain compliance aspects of a GNU Taler exchange. Users that just want to set up an exchange as an experiment without legal or regulatory requirements can safely skip this chapter. This manual targets compliance experts working with system administrators and developers to configure legitimization (KYC/KYB) and anti-money laundering (AML) processes for a GNU Taler exchange. Expertise in all three domains is required, as Taler's KYC and AML processes are highly configurable and programmable. Legal conditions for using the service -------------------------------------- .. include:: frags/legal.rst Know-Your-Customer Setup ======================== To legally operate, Taler exchange operators may have to comply with KYC regulation that requires financial institutions to identify parties involved in transactions at certain points. Taler permits an exchange to require know-your-customer (KYC) or know-your-business (KYB) data under the following circumstances: * Customer withdraws money over a threshold * Wallet receives (via refunds) money resulting in a balance over a threshold * Wallet receives money via P2P payments over a threshold * Merchant receives money over a threshold * Merchant deposits digital cash over a threshold (**planned feature, bug 9040**) * Reserve is "opened" for invoicing (**planned feature**) Any of the above requests can trigger the KYC process, which can be illustrated as follows: .. image:: images/kyc-process.png At the end of the KYC process, the wallet re-tries the original request, and assuming KYC was successful, the request should then succeed. KYC Terminology --------------- * **Attributes**: Attributes are used to represent KYC data obtained about an account holder. Attributes include passport images, address data, business registration documents, and indeed arbitrary forms filed by AML staff or the customer themselves. Attribute data is considered sensitive private information and is thus stored encrypted within the exchange database. * **Check**: A check establishes a particular attribute of a user, such as their name based on an ID document and lifeness, mailing address, phone number, taxpayer identity, etc. Checks may be given *context* (such as whether a customer is an individual or a business) to run correctly. Checks can also be AML staff inserting information for plausibilization. Checks result in *attributes* about the account's owner which are given to an external AML *program* together with the *context* to determine an *outcome*. KYC checks are always specified with a fallback *measure* to be taken if the check fails. * **Condition**: A condition specifies when KYC is required. Conditions include the *type of operation*, a threshold amount (e.g. above EUR:1000) and possibly a time period (e.g. over the last month). * **Configuration**: The configuration determines the *legitimization rules*, and specifies which providers offer which *checks*. * **Context**: Context is information provided as input into a *check* and *program* to customize their execution. The context is initially set by the *measure* (possibly including data from the *trigger*). Naturally, the *program* may use its `AmlProgramInput` which includes *context* and *attribute* data to compute an update *context* for the next set of *measures* that it specifies in the `LegitimizationRuleSet` as part of the `AmlOutcome`. Thus, *context* is something that typically evolves as the *account* undergoes *measures*. Context is lost if an account transitions to default *legitimization rules* due to *expiration*. * **Decision**: AML decisions are these as taken by AML officers (humans). AML outcomes are the results of AML programs (code). Legitimization outcomes (DB table) can arise from either. * **Display priority**: Every rule has a *display priority*. If a second *rule* is *triggered* before the *outcome* of a *rule* could be determined, the *rule* with the larger *display priority* becomes the requirement that the account owner has to satisfy (and that thus will be displayed by the KYC SPA). * **Expiration**: Except for the default rules, any set of KYC rules is subject to *expiration*. This can be because *attributes* become outdated or because sanctions have a time limit. The expiration time thus determines when a new *measure* is triggered in the absence of a transaction crossing thresholds in the current set of *legtimization rules*. * **Legitimization rules**: The *legitimization rules* determine under which *conditions* which *measures* will be taken. A `LegitimizationRuleSet` always also includes an *expiration* time period for (custom, non-default) *legitimization rules* after which a fallback measure* will automatically apply. Legitimization rules may be *exposed* to the client (for example, to allow a wallet to stay below hard withdraw thresholds) or could be secret. * **Logic**: Logic refers to a specific bit of code (realized as an exchange plugin) that enables the interaction with a specific *provider*. Logic typically requires *configuration* for access control (such as an authorization token) and possibly the endpoint of the specific *provider* implementing the respective API. * **Measure**: Describes the possible outgoing edges from one state in the state machine (including how to show the current state). Each edge is given some *context* and a *check* to be performed as well as an AML *program* which determines the *outcome*. We generally distinguish between "original" measures (defined globally in the exchange configuration) and "custom" measures (defined specifically for an account by AML staff). * **Outcome**: An `AmlOutcome` describes the account state that an account ends up in due to either an AML staff action or an AML *program* doing some computation over the attributes resulting from a *check*. Outcomes can be that certain types of transactions are "verboten", that the account is (or remains) under investigation by AML staff, that the account is given certain properties, and/or that certain events are to be logged. Outcomes also include a new set of *legitimization rules* to apply (and an *expiration* time at which point a successor *measure* will be automatically taken). * **Provider**: A provider performs a specific set of *checks* at a certain *cost*. Interaction with a provider is performed by provider-specific *logic*. * **Program**: An AML helper *program* is given *context* about the current state of an account and the attribute data from a *check* to compute the *outcome*. For example, a *program* may look at the "PEP" field of a KYC check and decide if the outcome is to put the account into ``normal`` or ``held-for-manual-review`` state. AML programs are always specified with a fallback *measure* to be taken if the program fails. * **Trigger**: A specific transaction that satisfies a **Condition**. * **Type of operation**: The operation type determines which Taler-specific operation has triggered the KYC requirement. We support four types of operation: withdraw (by customer), deposit (by merchant), aggregate transfer (to merchant), P2P receive (by wallet) and (high) wallet balance. Configuration of possible KYC/AML providers ------------------------------------------- The KYC configuration determines the *legitimization rules*, and specifies which providers offer which *checks*. The configuration specifies a set of providers, one per configuration section. The names of the configuration sections must being with ``kyc-proider-`` followed by an arbitrary ``$PROVIDER_ID``: .. code-block:: ini :caption: /etc/taler/conf.d/exchange-kyc-providers.conf [kyc-provider-$PROVIDER_ID] # Which plugin is responsible for this provider? # Choices include "oauth2", "kycaid" and "persona". LOGIC = oauth2 # Plus additional logic-specific options, e.g.: AUTHORIZATION_TOKEN = superdupersecret # Other logic-specific internal options (example): FORM_ID = business_legi_form OAuth 2.0 specifics ^^^^^^^^^^^^^^^^^^^ In terms of configuration, the OAuth 2.0 logic requires the respective client credentials to be configured apriori to enable access to the legitimization service. The OAuth 2.0 configuration options are: .. code-block:: ini :caption: /etc/taler/conf.d/exchange-oauth2.conf [kyc-provider-example-oauth2] LOGIC = oauth2 # (generic options omitted) # How long is the KYC check valid? KYC_OAUTH2_VALIDITY = forever # URL to which we redirect the user for the login process KYC_OAUTH2_AUTHORIZE_URL = "http://kyc.example.com/authorize" # URL where we POST the user's authentication information KYC_OAUTH2_TOKEN_URL = "http://kyc.example.com/token" # URL of the user info access point. KYC_OAUTH2_INFO_URL = "http://kyc.example.com/info" # Where does the client get redirected upon completion? KYC_OAUTH2_POST_URL = "http://example.com/thank-you" # For authentication to the OAuth2.0 service KYC_OAUTH2_CLIENT_ID = testcase KYC_OAUTH2_CLIENT_SECRET = password # Mustach template that converts OAuth2.0 data about the user # into GNU Taler standardized attribute data. KYC_OAUTH2_CONVERTER_HELPER = taler-exchange-kyc-oauth2-challenger.sh The converter helper is expected to be customized to the selected OAuth2.0 service: different services may return different details about the user or business, hence there cannot be a universal converter for all purposes. The default shell script uses the ``jq`` tool to convert the JSON returned by the service into the KYC attributes (also in JSON) expected by the exchange. The script will need to be adjusted based on the attributes collected by the specific backend. The Challenger service for address validation supports OAuth2.0, but does not have a static AUTHORIZE_URL. Instead, the AUTHORIZE_URL must be enabled by the client using a special authenticated request to the Challenger's ``/setup`` endpoint. The exchange supports this by appending ``#setup`` to the AUTHORIZE_URL (note that fragments are illegal in OAuth2.0 URLs). Be careful to quote the URL, as ``#`` is otherwise interpreted as the beginning of a comment by the configuration file syntax. .. code-block:: ini :caption: /etc/taler/conf.d/exchange-challenger-oauth2.conf [kyc-provider-challenger-oauth2] LOGIC = oauth2 KYC_OAUTH2_AUTHORIZE_URL = "http://challenger.example.com/authorize/#setup" KYC_OAUTH2_TOKEN_URL = "http://challenger.example.com/token" KYC_OAUTH2_INFO_URL = "http://challenger.example.com/info" When using OAuth 2.0, the *CLIENT REDIRECT URI* must be set to the ``/kyc-proof/$PROVIDER_SECTION`` endpoint. For example, given the configuration above and an exchange running on the host ``exchange.example.com``, the redirect URI would be ``https://exchange.example.com/kyc-proof/kyc-provider-challenger-oauth2/``. Persona specifics ^^^^^^^^^^^^^^^^^ We use the hosted flow. The Persona endpoints return a ``request-id``, which we log for diagnosis. Persona should be configured to use the ``/kyc-webhook/`` endpoint of the exchange to notify the exchange about the completion of KYC processes. The webhook is authenticated using a shared secret, which should be in the configuration. To use the Persona webhook, you must set the webhook URL in the Persona service to ``$EXCHANGE_BASE_URL/kyc-webhook/$SECTION_NAME/`` where ``$SECTION_NAME`` is the name of the configuration section. You should also extract the authentication token for the webhook and put it into the configuration as shown above. .. code-block:: ini :caption: /etc/taler/conf.d/exchange-persona.conf [kyclogic-persona] # Webhook authorization token. Global for all uses # of the persona provider! WEBHOOK_AUTH_TOKEN = wbhsec_698b5a19-c790-47f6-b396-deb572ec82f9 [kyc-provider-example-persona] LOGIC = persona # (generic options omitted) # How long is the KYC check valid? KYC_PERSONA_VALIDITY = 365d # Which subdomain is used for our API? KYC_PERSONA_SUBDOMAIN = taler # Authentication token to use. KYC_PERSONA_AUTH_TOKEN = persona_sandbox_42XXXX # Form to use. KYC_PERSONA_TEMPLATE_ID = itempl_Uj6Xxxxx # Where do we redirect to after KYC finished successfully. KYC_PERSONA_POST_URL = "https://taler.net/kyc-done" # Salt to give to requests for idempotency. # Optional. # KYC_PERSONA_SALT = salt # Helper to convert JSON with KYC data returned by Persona into GNU Taler # internal format. Should probably always be set to some variant of # "taler-exchange-kyc-persona-converter.sh". KYC_PERSONA_CONVERTER_HELPER = "taler-exchange-kyc-persona-converter.sh" The converter helper is expected to be customized to the selected template: different templates may return different details about the user or business, hence there cannot be a universal converter for all purposes. The default shell script uses the ``jq`` tool to convert the JSON returned by Persona into the KYC attributes (also in JSON) expected by the exchange. The script will need to be adjusted based on the attributes collected by the specific template. KYC AID specifics ^^^^^^^^^^^^^^^^^ We use the hosted flow. KYCAID must be configured to use the ``/kyc-webhook/$SECTION_NAME/`` endpoint of the exchange to notify the exchange about the completion of KYC processes. .. code-block:: ini :caption: /etc/taler/conf.d/exchange-kycaid.conf [kyc-provider-example-kycaid] LOGIC = kycaid # (generic options omitted) # How long is the KYC check valid? KYC_KYCAID_VALIDITY = 365d # Authentication token to use. KYC_KYCAID_AUTH_TOKEN = XXX # Form to use. KYC_KYCAID_FORM_ID = XXX # URL to go to after the process is complete. KYC_KYCAID_POST_URL = "https://taler.net/kyc-done" # Script to convert the KYCAID data into the Taler format. KYC_KYCAID_CONVERTER_HELPER = taler-exchange-kyc-kycaid-converter.sh The converter helper is expected to be customized to the selected template: different templates may return different details about the user or business, hence there cannot be a universal converter for all purposes. The default shell script uses the ``jq`` tool to convert the JSON returned by Persona into the KYC attributes (also in JSON) expected by the exchange. The script will need to be adjusted based on the attributes collected by the specific template. Configuration of possible KYC/AML checks ---------------------------------------- The configuration specifies a set of possible KYC checks offered by external providers, one per configuration section. The names of the configuration sections must being with ``kyc-check-`` followed by an arbitrary ``$CHECK_NAME``. .. code-block:: ini [kyc-check-$CHECK_NAME] # Which type of check is this? Also determines # the SPA form to show to the user for this check. # # INFO: wait for staff or contact staff out-of band # (only information shown, no SPA action) # FORM: SPA should show an inline (HTML) form # LINK: SPA may start external KYC process or upload # TYPE = INFO|LINK|FORM # Optional. Set to YES to allow this check be # done voluntarily by a client (they may then # still have to pay for it). Used to offer the # SPA to display checks even if they are # not required. Default is NO. # Since **vATTEST**. VOLUNTARY = YES/NO # Provider id, present only if type is LINK. # Refers to a ``kyc-provider-$PROVIDER_ID`` section. PROVIDER_ID = id # Name of the SPA form, if type is FORM # "INFO" and "LINK" are reserved and must not be used. # The exchange server and the SPA must agree on a list # of supported forms and the resulting attributes. # # The SPA should include a JSON resource file # "forms.json" mapping form names to arrays of # attribute names each form provides. FORM_NAME = name # Descriptions to use in the SPA to display the check. DESCRIPTION = "Upload your passport picture" DESCRIPTION_I18N = "{"en":"Upload scan of your passport"}" # ';'-separated list of fields that the CONTEXT must # provide as inputs to this check. For example, # for a FORM of type CHOICE, this might state # ``choices: string[];``. The type after the ":" # is for now purely for documentation and is # not checked. However, it may be shown to AML staff # when they configure measures. REQUIRES = requirement; # Description of the outputs provided by the check. # Basically, the check's output is expected to # provide the following fields as attribute inputs into # a subsequent AML program. # INFO never has any outputs. OUTPUTS = "business_name street city country registration" # **original** measure to take if the check fails # (for any reason, e.g. provider or form fail to # satisfy constraints or provider signals user error) # Usually should point to a measure that requests # AML staff to investigate. The fallback measure # context always includes the reasons for the # failure. FALLBACK = MEASURE_NAME The list of possible FORM names is fixed in the SPA for a particular exchange release. The "check_name" value "skip" is reserved and must not be defined. It can be used in measures where the AML program must be run immediately without any input. The outcome of *any* check is stored encrypted in the ``kyc_attributes`` table. It MUST include an ``expiration_time``. The INFO Type ^^^^^^^^^^^^^ When using KYC checks of type "INFO", the KYC-SPA will simply show the given DESCRIPTION (or translations from DESCRIPTION_I18N) to the user. The FORM Type ^^^^^^^^^^^^^ When using KYC checks of type "FORM", the KYC-SPA will show different forms based on "FORM_NAME" while also showing the user the text from DESCRIPTION as instructions. Some of the forms may be further parameterized via the context in which the form is executed. .. note:: For build-in forms, it should in the future not be necessary to specify the context requirements via REQUIRES as the KYC SPA should inform the exchange about the requirements of each form automatically. However, this is not yet implemented, see #9187. When forms are submitted, the exchange converts the form data into key-value pairs where the key is the form field name and the value is of type `KycStructuredFormData`. The respective AML program can then evaluate the data from the form submission from `attributes`. .. ts:def:: KycStructuredFormData interface KycStructuredFormData { // Content type. Missing if unknown. content_type?: string; // Name of the uploaded file. Missing if unknown // or this was not a file upload. filename?: string; // Base32-encoded binary form value. Only present // if form data was determined to be binary data. data?: string; // Text form value. Only present if the form data // was determined to be in textual format. text?: string; } The LINK Type ^^^^^^^^^^^^^ When using KYC checks of type "FORM", the KYC-SPA will show a link that allows the user to begin the KYC process at an external provider under the given DESCRIPTION. The external providers are expected to yield KYC attributes in the form of key-value pairs where the list of key is defined in the GANA ``gnu-taler-kyc-attributes`` registry, which also defines the format of each attribute. External providers may not directly yield attributes using the correct encodings, thus converter helper programs are typically used to convert external attribute data into the standardized format. Configuration of legitimization rules ------------------------------------- The configuration also must specify a set of legitimization rules, one per configuration section. Each rule specifies the condition and the measure the condition triggers: .. code-block:: ini [kyc-rule-$RULE_NAME] # Operation that triggers this rule. # Must be one of WITHDRAW, DEPOSIT, MERGE, # AGGREGATE or BALANCE. OPERATION_TYPE = WITHDRAW # Space-separated list of next measures to be performed. # The SPA should display *all* of these measures to the user. # (They have a choice of either which ones, or in # which order they are to be performed.) # A special measure name "verboten" is used if the # specified threshold may never be crossed # (under this set of rules). NEXT_MEASURES = SWISSNESS KYB # "YES" if all NEXT_MEASURES will eventually need # to be satisfied, "NO" if the user has a choice between # them. Not actually enforced by the exchange, but # primarily used to inform the user whether this is # an "and" or "or". YES for "and". IS_AND_COMBINATOR = YES # YES if the rule (specifically, operation type, # threshold, timeframe) and the general nature of # the next measure (verboten or approval required) # should be exposed to the client. # Defaults to NO if not set. EXPOSED = YES # Threshold amount above which the rule is # triggered. The total must be exceeded in the given # timeframe. THRESHOLD = KUDOS:100 # Timeframe over which the amount to be compared to # the THRESHOLD is calculated. # Ignored for WALLET-BALANCE. Can be 'forever'. TIMEFRAME = 30 days # Set to YES to enable the rule (default is NO) ENABLED = NO Configuration of AML programs ----------------------------- AML decision processes are automatically executed under certain configurable conditions. Basically, any AML program that is run can flag an account for investigation by setting the respective flag in the `AmlOutcome`. Once this happens, the respective account will be highlighted to AML staff. The AML program can also limit the operations of the account by setting arbitrary thresholds for the various operations (including freezing the account by setting the thresholds to zero). AML staff investigating the account can then request further documentation or set new rules, including new thresholds. AML programs can base their decisions on arbitrary attributes created by the KYC check (such as the user being a politically exposed person). AML programs will be given the KYC attributes in JSON format on standard input, and must output the `AmlOutcome`. If AML programs fail (return non-zero status codes), a FALLBACK measure is automatically triggered. AML programs are listed in the configuration file, one program per section: .. code-block:: ini [aml-program-$PROG_NAME] # Program to run. COMMAND = taler-helper-aml-pep # Human-readable description of what this # AML helper program will do. Used to show # to the AML staff. DESCRIPTION = "check if the customer is a PEP" # True if this AML program is enabled (and thus can be # used in measures and exposed to AML staff). # Optional, default is NO. ENABLED = YES # **original** measure to take if COMMAND fails # Usually points to a measure that asks AML staff # to contact the systems administrator. The fallback measure # context always includes the reasons for the # failure. FALLBACK = MEASURE_NAME Implementing your own AML programs ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When implementing an AML program, developers must ensure to provide three main execution paths: * Generate a list of *required* context field names for the helper (introspection!) when given the "-r" command-line switch. The output should use the same syntax as the REQUIRES clause of ``[kyc-check-]`` configuration sections, except that new lines MUST be used to separate fields instead of ";". * Generate a list of *required* attribute names for the helper (introspection!) when given the "-a" command-line switch. The output should use the same list of names as the ATTRIBUTES in the ``[kyc-provider-]`` configuration section (but may also include FORM field names). * Process an input JSON object of type `AmlProgramInput` into a JSON object of type `AmlOutcome`. This is the default behavior if no command-line switches are provided. AML programs will be given the exchange's configuration filename with the "-c FILENAME" command-line option. They may or may not use the configuration file as they see fit. AML programs should support the canonical "-h" (help) and "-v" (version) command-line options to display a help text and their version number respectively. AML programs may be given the "-V" option to put them into verbose mode, suggesting that they should log additional information to standard error. If the AML program fails (exits with a failure code or does not provide well-formed JSON output) the AML/KYC process continues with the FALLBACK measure. This should usually be one that asks AML staff to contact the systems administrator. .. ts:def:: AmlProgramInput interface AmlProgramInput { // JSON object that was provided as // part of the *measure*. This JSON object is // provided under "context" in the main JSON object // input to the AML program. This "context" should // satify both the REQUIRES clause of the respective // check and the output of "-r" from the // AML program's command-line option. context?: Object; // JSON object that captures the // output of a ``[kyc-provider-]`` or (HTML) FORM. // In the case of KYC data provided by providers, // the keys in the JSON object will be the attribute // names and the values must be strings representing // the data. In the case of file uploads, the data // MUST be base64-encoded. // In the case of KYC data provided by HTML FORMs, the // keys will match the HTML FORM field names and // the values will use the `KycStructuredFormData` // encoding. attributes: Object; // JSON array with the results of historic // AML desisions about the account. aml_history: AmlHistoryEntry[]; // JSON array with the results of historic // KYC data about the account. kyc_history: KycHistoryEntry[]; } .. ts:def:: AmlHistoryEntry interface AmlHistoryEntry { // When was the AML decision taken. decision_time : Timestamp; // What was the justification given for the decision. justification : string; // Public key of the AML officer taking the decision. decider_pub : AmlOfficerPublicKeyP; // Properties associated with the account by the decision. properties : Object; // New set of legitimization rules that was put in place. new_rules : LegitimizationRuleSet; // True if the account was flagged for (further) // investigation. to_investigate : boolean; // True if this is the currently active decision. is_active : boolean; } .. ts:def:: KycHistoryEntry interface KycHistoryEntry { // Name of the provider // which was used to collect the attributes. NULL if they were // just uploaded via a form by the account owner. provider_name?: string; // True if the KYC process completed. finished: boolean; // Numeric `error code `, if the // KYC process did not succeed; 0 on success. code: number; // Human-readable description of ``code``. Optional. hint?: string; // Optional detail given when the KYC process failed. error_message?: string; // Identifier of the user at the KYC provider. Optional. provider_user_id?: string; // Identifier of the KYC process at the KYC provider. Optional. provider_legitimization_id? :string; // The collected KYC data. // NULL if the attribute data could not // be decrypted or was not yet collected. attributes?: Object; // Time when the KYC data was collected collection_time: Timestamp; // Time when the KYC data will expire. expiration_time: Timestamp; } .. ts:def:: AmlOutcome interface AmlOutcome { // Should the client's account be investigated // by AML staff? // Defaults to false. to_investigate?: boolean; // Free-form properties about the account. // Can be used to store properties such as PEP, // risk category, type of business, hits on // sanctions lists, etc. properties?: AccountProperties; // Types of events to add to the KYC events table. // (for statistics). events?: string[]; // KYC rules to apply. Note that this // overrides *all* of the default rules // until the ``expiration_time`` and specifies // the successor measure to apply after the // expiration time. new_rules: LegitimizationRuleSet; } If the AML program fails (exits with a failure code or does not provide well-formed JSON output) the AML/KYC process continues with the FALLBACK measure. This should usually be one that asks AML staff to contact the systems administrator. Configuration of measures ------------------------- Finally, the configuration specifies a set of **original** *measures* one per configuration section: .. code-block:: ini [kyc-measure-$MEASURE_NAME] # Possible check for this measure. Optional. # If not given (or set to "SKIP"), PROGRAM should # be run immediately (on an empty set of attributes). CHECK_NAME = IB_FORM # Context for the check. The context can be # just an empty JSON object if there is none. CONTEXT = {"choices":["individual","business"]} # Program name to run on the context and check data to # determine the outcome and next measure. # Refers to a ``[aml-program-$PROG_NAME]`` section name. PROGRAM = taler-aml-program If ``CHECK_NAME`` is set to "SKIP" (or is not provided at all), the AML ``PROGRAM`` is to be run immediately. This is useful if no client-interaction is required to arrive at a decision. .. note:: The list of *measures* in the configuration is not complete: AML staff may freely define new measures dynamically, usually by selecting checks, an AML program, and providing context. The measures specified in the configuration are called the *original measures* and only those can be used as FALLBACK measures. AML Configuration ================= The AML configuration steps are used to add or remove keys of exchange operator staff that are responsible for anti-money laundering (AML) compliance. These AML officers are shown suspicious transactions and are granted access to the KYC data of an exchange. They can then investigate the transaction and decide on new rules for the respective user. They may request additional KYC data from the consumer, can change the thresholds up to which amounts transactions are allowed, and associate properties with the account that AML programs (and AML officers) may interpret for arbitrary future actions. AML Officer Setup ----------------- To begin the AML setup, AML staff should launch the GNU Taler exchange AML SPA Web interface by going to the ``/aml-spa/`` endpoint of the exchange. This is generally a public endpoint, but of course an operator may restrict access via the reverse proxy. The SPA will generate a public-private key pair and store it in the local storage of the browser. The public key will be displayed and **must** be securely transmitted to the offline system for approval. Using the offline system, one can then configure which staff has access to the AML operations: .. code-block:: shell-session [root@exchange-offline]# taler-exchange-offline \ aml-enable "$PUBLIC_KEY" "Legal Name" rw > aml.json [root@exchange-online]# taler-exchange-offline \ upload < aml.json The above commands would add an AML officer with the given "Legal Name" with read-write (rw) access to the AML officer database. Using "ro" instead of "rw" would grant read-only access to the data, leaving out the ability to actually make AML decisions. Once AML access has been granted, the AML officer can use the SPA to review cases and (with "rw" access) take AML decisions. Access rights can be revoked at any time using: .. code-block:: shell-session [root@exchange-offline]# taler-exchange-offline \ aml-disable $PUBLIC_KEY "Legal Name" > aml-off.json [root@exchange-online]# taler-exchange-offline \ upload < aml-off.json AML Forms --------- AML forms are defined by the :ref:`dynamic forms design document `. The shipped implementation with of the exchange is installed in .. code-block:: shell-session ${INSTALL_PREFIX}/share/taler/exchange/spa/forms.js The variable ``form`` contains the list of all form available. For every entry in the list the next properties are expected to be present: ``label``: used in the UI as the name of the form ``id``: identification name, this will be saved in the exchange database along with the values to correctly render the form again. It should simple, short and without any character outside numbers, letters and underscore. ``version``: when editing a form, instead of just replacing fields it will be better to create a new form with the same id and new version. That way old forms in the database will used old definition of the form. It should be a number. ``impl`` : a function that returns the design and behavior of form. See DD 54 dynamic forms. .. attention:: do not remove a form the list if it has been used. Otherwise you won't be able to see the information save in the exchange database. To add a new one you can simply copy and paste one element, and edit it. It is much easier to download ``@gnu-taler/aml-backoffice-ui`` source from ``https://git.taler.net/wallet-core.git/``, compile and copy the file from the ``dist/prod``. AML Measures ------------ The exchange configuration specifies a set of **original** *measures* one per configuration section: .. code-block:: ini [kyc-measure-$MEASURE_NAME] # Possible check for this measure. Optional. # If not given, PROGRAM should be run immediately # (on an empty set of attributes). CHECK_NAME = IB_FORM # Context for the check. The context can be # just an empty JSON object if there is none. CONTEXT = {"choices":["individual","business"]} # Program to run on the context and check data to # determine the outcome and next measure. PROGRAM = taler-aml-program If no ``CHECK_NAME`` is provided at all, the AML ``PROGRAM`` is to be run immediately. This is useful if no client-interaction is required to arrive at a decision. .. note:: The list of *measures* is not complete: AML staff may freely define new measures dynamically, usually by selecting checks, an AML program, and providing context. .. _ExchangeTemplateCustomization: KYC Process Template Customization ================================== The Exchange comes with various HTML templates that are shown to guide users through the KYC process. The Exchange uses `C implementation of mustache `__ as the templating engine. This section describes the various templates. In general, the templates must be installed to the ``share/taler/exchange/templates/`` directory. The file names must be of the form ``$NAME.$LANG.must`` where ``$NAME`` is the name of the template and ``$LANG`` is the 2-letter language code of the template. English templates must exist and will be used as a fallback. If the browser (user-agent) has provided language preferences in the HTTP header and the respective language exists, the correct language will be automatically served. The following subsections give details about each of the templates. Most subsection titles are the ``$NAME`` of the respective template. Generic Errors Templates ------------------------ A number of templates are used for generic errors. These are: * kyc-proof-already-done (KYC process already completed) * kyc-bad-request (400 Bad Request) * kyc-proof-endpoint-unknown (404 Not Found for KYC logic) * kyc-proof-internal-error (500 Internal Server Error) * kyc-proof-target-unknown (404 Not Found for KYC operation) All of these templates are instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: String; human-readable Taler error code, should be shown for the user to understand the error * message: String; optional, extended human-readable text provided to elaborate on the error, should be shown to provide additional context kycaid-invalid-request ---------------------- The KYCaid plugin does not support requests to the ``/kyc-proof/`` endpoint (HTTP 400 bad request). This template is instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: String; human-readable Taler error code, should be shown for the user to understand the error * error: String; error code from the server * error_details: String; optional error description from the server * error_uri: optional URI with further details about the error from the server oauth2-authentication-failure ----------------------------- The OAuth2 server said that the request was not properly authenticated (HTTP 403 Forbidden). This template is instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: String; human-readable Taler error code, should be shown for the user to understand the error oauth2-authorization-failure ---------------------------- The OAuth2 server refused to return the KYC data because the authorization code provided was invalid (HTTP 403 Forbidden). This template is instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: String; human-readable Taler error code, should be shown for the user to understand the error * error: String; error code from the server * error_message: String; error message from the server oauth2-authorization-failure-malformed -------------------------------------- The server refused the authorization, but then provided a malformed response (HTTP 502 Bad Gateway). This template is instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: string; human-readable Taler error code, should be shown for the user to understand the error * debug: boolean; true if we are running in debug mode and are allowed to return HTML with potentially sensitive information * server_response: Object; could be NULL; this includes the (malformed) OAuth2 server response, it should be shown to the use if "debug" is true oauth2-bad-request ------------------ The client made an invalid request (HTTP 400 Bad Request). This template is instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: String; human-readable Taler error code, should be shown for the user to understand the error * message: String; additional error message elaborating on what was bad about the request oauth2-conversion-failure ------------------------- Converting the KYC data into the exchange's internal format failed (HTTP 502 Bad Gateway). This template is instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: string; human-readable Taler error code, should be shown for the user to understand the error * debug: boolean; true if we are running in debug mode and are allowed to return HTML with potentially sensitive information * converter: String; name of the conversion command that failed which was used by the Exchange * attributes: Object; attributes returned by the conversion command, often NULL (after all, conversion failed) * message: error message elaborating on the conversion failure oauth2-provider-failure ----------------------- We did not get an acceptable response from the OAuth2 provider (HTTP 502 Bad Gateway). This template is instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: String; human-readable Taler error code, should be shown for the user to understand the error * message: String; could be NULL; text elaborating on the details of the failure persona-exchange-unauthorized ----------------------------- The Persona server refused our request (HTTP 403 Forbidden from Persona, returned as a HTTP 502 Bad Gateway). This template is instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: String; human-readable Taler error code, should be shown for the user to understand the error * data: Object; data returned from Persona service, optional * persona_http_status: Integer; HTTP status code returned by Persona persona-load-failure -------------------- The Persona server refused our request (HTTP 429 Too Many Requests from Persona, returned as a HTTP 503 Service Unavailable). This template is instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: String; human-readable Taler error code, should be shown for the user to understand the error * data: Object; data returned from Persona service, optional * persona_http_status: Integer; HTTP status code returned by Persona persona-exchange-unpaid ----------------------- The Persona server refused our request (HTTP 402 Payment REquired from Persona, returned as a HTTP 503 Service Unavailable). This template is instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: String; human-readable Taler error code, should be shown for the user to understand the error * data: Object; data returned from Persona service, optional * persona_http_status: Integer; HTTP status code returned by Persona persona-logic-failure --------------------- The Persona server refused our request (HTTP 400, 403, 409, 422 from Persona, returned as a HTTP 502 Bad Gateway). This template is instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: String; human-readable Taler error code, should be shown for the user to understand the error * data: Object; data returned from Persona service, optional * persona_http_status: Integer; HTTP status code returned by Persona persona-invalid-response ------------------------ The Persona server refused our request in an unexpected way; returned as a HTTP 502 Bad Gateway. This template is instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: string; human-readable Taler error code, should be shown for the user to understand the error * debug: boolean; true if we are running in debug mode and are allowed to return HTML with potentially sensitive information * server_response: Object; could be NULL; this includes the (malformed) OAuth2 server response, it should be shown to the use if "debug" is true persona-network-timeout ----------------------- The Persona server refused our request (HTTP 408 from Persona, returned as a HTTP 504 Gateway Timeout). This template is instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: String; human-readable Taler error code, should be shown for the user to understand the error * data: Object; data returned from Persona service, optional * persona_http_status: Integer; HTTP status code returned by Persona persona-kyc-failed ------------------ The Persona server indicated a problem with the KYC process, saying it was not completed. This template is instantiated using the following information: * persona_inquiry_id: String; internal ID of the inquiry within Persona, useful for further diagnostics by staff * data: Object; could be NULL; this includes the server response, it contains extensive diagnostics, see Persona documentation on their ``/api/v1/inquiries/$ID``. * persona_http_status: Integer; HTTP status code returned by Persona persona-provider-failure ------------------------ The Persona server refused our request (HTTP 500 from Persona, returned as a HTTP 502 Bad Gateway). This template is instantiated using the following information: * ec: Integer; numeric Taler error code, should be shown to indicate the error compactly for reporting to developers * hint: String; human-readable Taler error code, should be shown for the user to understand the error * data: Object; data returned from Persona service, optional * persona_http_status: Integer; HTTP status code returned by Persona