summaryrefslogtreecommitdiff
path: root/design-documents
diff options
context:
space:
mode:
Diffstat (limited to 'design-documents')
-rw-r--r--design-documents/023-taler-kyc.rst632
-rw-r--r--design-documents/046-mumimo-contracts.rst2
-rw-r--r--design-documents/053-wallet-ui.rst16
-rw-r--r--design-documents/054-dynamic-form.rst673
4 files changed, 608 insertions, 715 deletions
diff --git a/design-documents/023-taler-kyc.rst b/design-documents/023-taler-kyc.rst
index c20844b3..786b0ca7 100644
--- a/design-documents/023-taler-kyc.rst
+++ b/design-documents/023-taler-kyc.rst
@@ -200,12 +200,12 @@ user for *voluntary* KYC processes related to attestation (#7365).
Proposed Solution
=================
-The main state of an account is represented by a set of `KycRules` (the
+The main state of an account is represented by a set of `KYC rules <KycRule>` (the
`LegitimizationRuleSet`) which specify the current *rules* to apply to
transactions involving the account. Rules can *exposed* to the account owner,
or can be secret. Each *rule* specifies certain *conditions* which, if met,
-*trigger* a single specific *measure*. After a *rule* was *triggered* and
-before the *outcome* of the respective *measure* has been produced (say
+*trigger* a set of *measures*. After a *rule* was *triggered* and
+before the *outcome* of a respective *measure* has been produced (say
because the user did not yet enter their data or the AML officer is still
reviewing the case), the existing rules remain in force. Rules have a display
priority, and if a second rule with a higher display priority is also
@@ -431,48 +431,9 @@ expose merchant public key to SPA for wire transfer if needed for KYC.
^^^^^^^^^^^^
When KYC operations are required, various endpoints may respond with a
-``451 Unavailable for Legal Reasons`` status code and a `KycNeededRedirect`
+``451 Unavailable for Legal Reasons`` status code and a `LegitimziationNeededResponse`
body.
- .. ts:def:: KycNeededRedirect
-
- interface KycNeededRedirect {
-
- // Numeric `error code <error-codes>` unique to the condition.
- // Should always be ``TALER_EC_EXCHANGE_GENERIC_KYC_REQUIRED``.
- code: number;
-
- // Human-readable description of the error, i.e. "missing parameter",
- // "commitment violation", ... Should give a human-readable hint
- // about the error's nature. Optional, may change without notice!
- hint?: string;
-
- // Hash of the payto:// account URI for which KYC
- // is required.
- h_payto: PaytoHash;
-
- // Public key associated with the account. The client must sign
- // the initial request for the KYC status using the corresponding
- // private key. Will be either a reserve public key or a merchant
- // (instance) public key.
- //
- // Absent if no public key is currently associated
- // with the account and the client MUST thus first
- // credit the exchange via an inbound wire transfer
- // to associate a public key with the debited account.
- account_pub?: EddsaPublicKey;
-
- // Identifies a set of measures that were triggered and that are
- // now preventing this operation from proceeding. Gives the
- // account holder a starting point for understanding why the
- // transaction was blocked and how to lift it. The account holder
- // should use the number to check for the account's AML/KYC status
- // using the ``/kyc-check/$REQUIREMENT_ROW`` endpoint.
- requirement_row: Integer;
-
- }
-
-
New endpoints
^^^^^^^^^^^^^
@@ -489,7 +450,7 @@ New endpoints
The requirement row of the ``/kyc-check/`` endpoint encodes the
legitimization measure's serial number. It is returned in
- `KycNeededRedirect` responses via the ``requirement_row`` field.
+ `LegitimizationNeededResponse` responses via the ``requirement_row`` field.
Given a valid pair of requirement row and account owner signature, the
``/kyc-check/`` endpoint returns either just the KYC status or redirects the
@@ -543,79 +504,6 @@ New endpoints
:http:statuscode:`404 Not found`:
The requirement row is unknown.
- **Details:**
-
- .. ts:def:: AccountKycStatus
-
- interface AccountKycStatus {
-
- // Current AML state for the target account. True if
- // operations are not happening due to staff processing
- // paperwork *or* due to legal requirements (so the
- // client cannot do anything but wait).
- //
- // Note that not every AML staff action may be legally
- // exposed to the client, so this is merely a hint that
- // a client should be told that AML staff is currently
- // reviewing the account. AML staff *may* review
- // accounts without this flag being set!
- aml_review: boolean;
-
- // Access token needed to construct the ``/kyc-spa/``
- // URL that the user should open in a browser to
- // proceed with the KYC process (optional if the status
- // type is ``200 Ok``, mandatory if the HTTP status
- // is ``202 Accepted``).
- access_token: AccountAccessToken;
-
- // Array with limitations that currently apply to this
- // account and that may be increased or lifted if the
- // KYC check is passed.
- // Note that additional limits *may* exist and not be
- // communicated to the client. If such limits are
- // reached, this *may* be indicated by the account
- // going into ``aml_review`` state. However, it is
- // also possible that the exchange may legally have
- // to deny operations without being allowed to provide
- // any justification.
- // The limits should be used by the client to
- // possibly structure their operations (e.g. withdraw
- // what is possible below the limit, ask the user to
- // pass KYC checks or withdraw the rest after the time
- // limit is passed, warn the user to not withdraw too
- // much or even prevent the user from generating a
- // request that would cause it to exceed hard limits).
- limits?: AccountLimit[];
-
- }
-
- .. ts:def:: AccountLimit
-
- interface AccountLimit {
-
- // Operation that is limited.
- // Must be one of "WITHDRAW", "DEPOSIT", "P2P-RECEIVE"
- // or "WALLET-BALANCE".
- operation_type: string;
-
- // Timeframe during which the limit applies.
- timeframe: RelativeTime;
-
- // Maximum amount allowed during the given timeframe.
- // Zero if the operation is simply forbidden.
- threshold: Amount;
-
- // True if this is a soft limit that could be raised
- // by passing KYC checks. Clients *may* deliberately
- // try to cross limits and trigger measures resulting
- // in 451 responses to begin KYC processes.
- // Clients that are aware of hard limits *should*
- // inform users about the hard limit and prevent flows
- // in the UI that would cause violations of hard limits.
- soft_limit: boolean;
- }
-
-
.. http:get:: /kyc-spa/$ACCESS_TOKEN
.. http:get:: /kyc-spa/$FILENAME
@@ -656,72 +544,13 @@ New endpoints
**Response:**
+ *Etag*: Will be set to the serial ID of the measure. Used for long-polling (only for 200 OK responses).
+
:http:statuscode:`200 OK`:
The body is a `KycProcessClientInformation`.
-
- *Etag*: Will be set to the serial ID of the measure. Used for long-polling.
-
- .. ts:def:: KycProcessClientInformation
-
- interface KycProcessClientInformation {
-
- // List of requirements.
- requirements?: { name : KycRequirementInformation};
-
- // True if the client is expected to eventually satisfy all requirements.
- // Default (if missing) is false.
- is_and_combinator?: boolean
-
- // List of available voluntary checks the client could pay for.
- // Since **vATTEST**.
- voluntary_checks?: { name : KycCheckInformation};
- }
-
- .. ts:def:: KycRequirementInformation
-
- interface KycRequirementInformation {
-
- // Which form should be used? Common values include "INFO"
- // (to just show the descriptions but allow no action),
- // "LINK" (to enable the user to obtain a link via
- // ``/kyc-start/``) or any build-in form name supported
- // by the SPA.
- form: string;
-
- // English description of the requirement.
- description: string;
-
- // Map from IETF BCP 47 language tags to localized
- // description texts.
- description_i18n ?: { [lang_tag: string]: string };
-
- // ID of the requirement, useful to construct the
- // ``/kyc-upload/$ID`` or ``/kyc-start/$ID`` endpoint URLs.
- // Present if and only if "form" is not "INFO". The
- // ``$ID`` value may itself contain ``/`` or ``?`` and
- // basically encode any URL path (and optional arguments).
- id?: string;
-
- }
-
- .. ts:def:: KycCheckInformation
-
- // Since **vATTEST**.
- interface KycCheckInformation {
-
- // English description of the check.
- description: string;
-
- // Map from IETF BCP 47 language tags to localized
- // description texts.
- description_i18n ?: { [lang_tag: string]: string };
-
- }
-
:http:statuscode:`204 No Content`:
There are no open KYC requirements or possible voluntary checks
the client might perform.
-
:http:statuscode:`304 Not Modified`:
The KYC requirements did not change.
@@ -749,7 +578,7 @@ New endpoints
The ``$ID`` is unknown to the exchange.
:http:statuscode:`409 Conflict`:
The upload conflicts with a previous upload.
- :http:statuscode:`413 Content Too Large`:
+ :http:statuscode:`413 Request Entity Too Large`:
The body is too large.
.. http:post:: /kyc-start/$ID
@@ -771,16 +600,6 @@ New endpoints
The KYC process was successfully initiated. The URL is in a
`KycProcessStartInformation` object.
- **Details:**
-
- .. ts:def:: KycProcessStartInformation
-
- interface KycProcessStartInformation {
-
- // URL to open.
- redirect_url: string;
- }
-
:http:statuscode:`404 Not Found`:
The ``$ID`` is unknown to the exchange.
@@ -913,29 +732,7 @@ New endpoints
This response comes with a standard `ErrorDetail` response.
:http:statuscode:`451 Unavailable for Legal Reasons`:
The wallet must undergo a KYC check. A KYC ID was created.
- The response will be a `KycNeededRedirect` object.
-
- **Details:**
-
- .. ts:def:: WalletKycRequest
-
- interface WalletKycRequest {
-
- // Balance threshold (not necessarily exact balance)
- // to be crossed by the wallet that (may) trigger
- // additional KYC requirements.
- balance: Amount;
-
- // EdDSA signature of the wallet affirming the
- // request, must be of purpose
- // ``TALER_SIGNATURE_WALLET_ACCOUNT_SETUP``
- reserve_sig: EddsaSignature;
-
- // long-term wallet reserve-account
- // public key used to create the signature.
- reserve_pub: EddsaPublicKey;
- }
-
+ The response will be a `LegitimizationNeededResponse` object.
.. http:get:: /aml/$OFFICER_PUB/measures
@@ -959,86 +756,6 @@ New endpoints
Information about possible measures is returned in a
`AvailableMeasureSummary` object.
- **Details:**
-
- .. ts:def:: AvailableMeasureSummary
-
- interface AvailableMeasureSummary {
-
- // Available original measures that can be
- // triggered directly by default rules.
- roots: { "$measure_name" : MeasureInformation; };
-
- // Available AML programs.
- programs: { "$prog_name" : AmlProgramRequirement; };
-
- // Available KYC checks.
- checks: { "$check_name" : KycCheckInformation; };
-
- }
-
- .. ts:def:: MeasureInformation
-
- interface MeasureInformation {
-
- // Name of a KYC check.
- check_name: string;
-
- // Name of an AML program.
- prog_name: string;
-
- // Context for the check. Optional.
- context?: Object;
-
- }
-
- .. ts:def:: AmlProgramRequirement
-
- interface AmlProgramRequirement {
-
- // Description of what the AML program does.
- description: string;
-
- // List of required field names in the context to run this
- // AML program. SPA must check that the AML staff is providing
- // adequate CONTEXT when defining a measure using this program.
- context: string[];
-
- // List of required attribute names in the
- // input of this AML program. These attributes
- // are the minimum that the check must produce
- // (it may produce more).
- inputs: string[];
-
- }
-
- .. ts:def:: KycCheckInformation
-
- interface KycCheckInformation {
-
- // Description of the KYC check. Should be shown
- // to the AML staff but will also be shown to the
- // client when they initiate the check in the KYC SPA.
- description: string;
- description_i18n: {};
-
- // Names of the fields that the CONTEXT must provide
- // as inputs to this check.
- // SPA must check that the AML staff is providing
- // adequate CONTEXT when defining a measure using
- // this check.
- requires: string[];
-
- // Names of the attributes the check will output.
- // SPA must check that the outputs match the
- // required inputs when combining a KYC check
- // with an AML program into a measure.
- outputs: string[];
-
- // Name of a root measure taken when this check fails.
- fallback: string;
- }
-
.. http:get:: /aml/$OFFICER_PUB/kyc-statistics/$NAME
Returns the number of KYC events matching the given event type ``$NAME`` in
@@ -1064,13 +781,8 @@ New endpoints
**Response:**
- .. ts:def:: EventCounter
-
- interface EventCounter {
- // Number of events of the specified type in
- // the given range.
- counter: Integer;
- }
+ :http:statuscode:`200 OK`:
+ The responds will be an `EventCounter` message.
.. http:get:: /aml/$OFFICER_PUB/decisions
@@ -1116,31 +828,6 @@ New endpoints
:http:statuscode:`409 Conflict`:
The designated AML account is not enabled.
- **Details:**
-
- .. ts:def:: AmlDecisions
-
- interface AmlDecisions {
-
- // Array of AML decisions matching the query.
- records: AmlDecisions[];
- }
-
- .. ts:def:: AmlRecord
-
- interface AmlRecord {
-
- // Which payto-address is this record about.
- // Identifies a GNU Taler wallet or an affected bank account.
- h_payto: PaytoHash;
-
- // Row ID of the record. Used to filter by offset.
- rowid: Integer;
-
- // FIXME: more fields here!
- }
-
-
.. http:get:: /aml/$OFFICER_PUB/attributes/$H_PAYTO
Obtain attributes obtained as part of AML/KYC processes for a
@@ -1176,40 +863,7 @@ New endpoints
:http:statuscode:`409 Conflict`:
The designated AML account is not enabled.
- .. ts:def:: KycAttributes
-
- interface KycAttributes {
-
- // Matching KYC attribute history of the account.
- details: KycDetail[];
-
- }
-
- .. ts:def:: KycDetail
-
- // FIXME: bad name?
- interface KycDetail {
-
- // Row ID of the record. Used to filter by offset.
- rowid: Integer;
-
- // Name of the configuration section that specifies the provider
- // which was used to collect the attributes. NULL if they were
- // just uploaded via a form by the account owner.
- provider_section?: string;
-
- // The collected KYC data. NULL if the attribute data could not
- // be decrypted (internal error of the exchange, likely the
- // attribute key was changed).
- attributes?: Object;
-
- // Time when the KYC data was collected
- collection_time: Timestamp;
-
- }
-
-
- .. http:post:: /aml/$OFFICER_PUB/decision
+.. http:post:: /aml/$OFFICER_PUB/decision
Make an AML decision. Triggers the respective action and
records the justification.
@@ -1231,35 +885,6 @@ New endpoints
The designated AML account is not enabled or a more recent
decision was already submitted.
- **Details:**
-
- .. ts:def:: AmlDecision
-
- interface AmlDecision {
-
- // Human-readable justification for the decision.
- justification: string;
-
- // Which payto-address is the decision about?
- // Identifies a GNU Taler wallet or an affected bank account.
- h_payto: PaytoHash;
-
- // What are the new rules?
- new_rules: LegitimizationRuleSet;
-
- // True if the account should remain under investigation by AML staff.
- bool keep_investigating;
-
- // When was the decision made?
- decision_time: Timestamp;
-
- // Signature by the AML officer over a `TALER_AmlDecisionPS`.
- // Must have purpose ``TALER_SIGNATURE_MASTER_AML_KEY``.
- officer_sig: EddsaSignature;
-
- }
-
-
Modifications to existing endpoints
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1311,17 +936,6 @@ description of the high-level process for different providers.
# Which plugin is responsible for this provider?
LOGIC = PLUGIN_NAME
- # Optional cost, useful if clients want to voluntarily
- # trigger authentication procedures for attestation.
- # Since **vATTEST**.
- COST = EUR:5
-
- # Plus additional logic-specific options, e.g.:
- AUTHORIZATION_TOKEN = superdupersecret
-
- # Other logic-specific internal options (example):
- FORM_ID = business_legi_form
-
# Name of a program to run on the output of the plugin
# to convert the result into the desired set of attributes.
# The converter must create a log for the system administrator
@@ -1334,6 +948,17 @@ description of the high-level process for different providers.
# (when run regularly).
CONVERTER = taler-exchange-helper-$NAME
+ # Optional cost, useful if clients want to voluntarily
+ # trigger authentication procedures for attestation.
+ # Since **vATTEST**.
+ COST = EUR:5
+
+ # Plus additional logic-specific options, e.g.:
+ AUTHORIZATION_TOKEN = superdupersecret
+
+ # Other logic-specific internal options (example):
+ FORM_ID = business_legi_form
+
Configuration of possible KYC/AML checks
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1364,6 +989,7 @@ providers, one per configuration section:
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
@@ -1381,7 +1007,7 @@ providers, one per configuration section:
DESCRIPTION_I18N = "{"en":"Upload scan of your passport"}"
# ';'-separated list of fields that the CONTEXT must
- # provided as inputs to this check. For example,
+ # 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
@@ -1391,8 +1017,11 @@ providers, one per configuration section:
# Description of the outputs provided by the check.
# Basically, the check's output is expected to
- # provide the following fields as inputs into
+ # provide the following fields as attribute inputs into
# a subsequent AML program.
+ # Only given for type FORM; INFO never has any outputs,
+ # and for type LINK we can obtain the same information
+ # from the CONVERTER via ``--list-outputs``.
OUTPUTS = business_name street city country registration
# **original** measure to take if the check fails
@@ -1422,7 +1051,7 @@ configuration section:
[kyc-rule-$RULE_NAME]
- # Operation that triggers this legitimization.
+ # Operation that triggers this rule.
# Must be one of WITHDRAW, DEPOSIT, P2P-RECEIVE
# or WALLET-BALANCE.
OPERATION_TYPE = WITHDRAW
@@ -1436,8 +1065,8 @@ configuration section:
# (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
+ # "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".
@@ -1450,7 +1079,7 @@ configuration section:
# Defaults to NO if not set.
EXPOSED = YES
- # Threshold amount above which the legitimization is
+ # Threshold amount above which the rule is
# triggered. The total must be exceeded in the given
# timeframe.
THRESHOLD = KUDOS:100
@@ -1460,7 +1089,7 @@ configuration section:
# Ignored for WALLET-BALANCE. Can be 'forever'.
TIMEFRAME = 30 days
- # Enabled (default is NO)
+ # Set to YES to enable the rule (default is NO)
ENABLED = NO
@@ -1487,113 +1116,6 @@ AML programs are helper programs that can:
This is the default behavior if no command-line switches
are provided.
-.. 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 "--requires" from the
- // AML program's command-line option.
- context?: Object;
-
- // JSON object that captures the
- // output of a ``[kyc-provider-]`` or (HTML) FORM.
- // 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.
- attributes: Object;
-
- // JSON array with the results of historic
- // AML desisions about the account.
- aml_history: AmlDecisionDetail[];
-
- // JSON array with the results of historic
- // KYC data about the account.
- kyc_history: KycDetail[];
-
- }
-
-.. 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;
-
- }
-
-.. ts:def:: KycRule
-
- interface KycRule {
-
- // Type of operation to which the rule applies.
- operation_type: string;
-
- // The measures will be taken if the given
- // threshold is crossed over the given timeframe.
- threshold: Amount;
-
- // Over which duration should the ``threshold`` be
- // computed. All amounts of the respective
- // ``operation_type`` will be added up for this
- // duration and the sum compared to the ``threshold``.
- timeframe: RelativeTime;
-
- // Array of names of measures to apply.
- // Names listed can be original measures or
- // custom measures from the `AmlOutcome`.
- // A special measure "verboten" is used if the
- // threshold may never be crossed.
- measures: string[];
-
- // True if the rule (specifically, operation_type,
- // threshold, timeframe) and the general nature of
- // the measures (verboten or approval required)
- // should be exposed to the client.
- // Defaults to "false" if not set.
- exposed?: boolean;
-
- // True if all the measures will eventually need to
- // be satisfied, false if any of the measures should
- // do. Primarily used by the SPA to indicate how
- // the measures apply when showing them to the user;
- // in the end, AML programs will decide after each
- // measure what to do next.
- // Default (if missing) is false.
- is_and_combinator?: boolean;
-
- // If multiple rules apply to the same account
- // at the same time, the number with the highest
- // rule determines which set of measures will
- // be activated and thus become visible for the
- // user.
- display_priority: integer;
- }
-
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
@@ -1646,8 +1168,9 @@ Finally, the configuration specifies a set of
# just an empty JSON object if there is none.
CONTEXT = {"choices":["individual","business"]}
- # Program to run on the context and check data to
+ # 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 no ``CHECK_NAME`` is provided at all, the AML ``PROGRAM`` is to be run
@@ -1738,6 +1261,8 @@ on GET ``/deposits/`` with the respective legitimization requirement row.
)
PARTITION BY HASH (access_token);
+ COMMENT ON TABLE legitimization_measures
+ IS 'Rules that have been triggered for the account (FIXME: check this is consistent with usage)';
COMMENT ON COLUMN legitimization_measures.access_token
IS 'Used to uniquely identify the account and as a symmetric access control mechanism for the SPA';
COMMENT ON COLUMN legitimization_measures.start_time
@@ -1885,86 +1410,13 @@ on GET ``/deposits/`` with the respective legitimization requirement row.
The ``jmeasures`` JSON in the ``legitimization_measures``
-table has is of type `LegitimizationMeasures`:
-
-.. ts:def:: LegitimizationMeasures
-
- interface LegitimizationMeasures {
-
- // Array of legitimization measures that
- // are to be applied.
- measures: MeasureInformation[];
-
- // True if the client is expected to eventually satisfy all requirements.
- // Default (if missing) is false.
- is_and_combinator?: boolean;
- }
-
+table is of type `LegitimizationMeasures`.
The ``jnew_rules`` JSON in the ``legitimization_outcomes``
-table has is of type `LegitimizationRuleSet`:
-
-.. ts:def:: LegitimizationRuleSet
-
- interface LegitimizationRuleSet {
-
- // When does this set of rules expire and
- // we automatically transition to the successor
- // measure?
- expiration_time: Timestamp;
-
- // Name of the measure to apply when the expiration time is
- // reached. If not set, we refer to the default
- // set of rules (and the default account state).
- successor_measure?: string;
-
- // Legitimization rules that are to be applied
- // to this account.
- rules: KycRule[];
-
- // Custom measures that KYC rules and the
- // ``successor_measure`` may refer to.
- custom_measures: { "$measure_name" : MeasureInformation; };
- }
-
-
-The ``jproperties`` JSON in the ``legitimization_outcomes`` table has is of
-type `AccountProperties`. All fields in this object are optional. The actual
-properties collected depend fully on the discretion of the exchange operator;
-however, some common fields are standardized and thus described here.
-
-.. ts:def:: AccountProperties
-
- interface AccountProperties {
-
- // True if this is a politically exposed account.
- // Rules for classifying accounts as politically
- // exposed are country-dependent.
- pep?: boolean;
-
- // True if this is a sanctioned account.
- // Rules for classifying accounts as sanctioned
- // are country-dependent.
- sanctioned?: boolean;
-
- // True if this is a high-risk account.
- // Rules for classifying accounts as at-risk
- // are exchange operator-dependent.
- high_risk?: boolean;
-
- // Business domain of the account owner.
- // The list of possible business domains is
- // operator- or country-dependent.
- business_domain?: string;
-
- // Is the client's account currently frozen?
- is_frozen?: boolean;
-
- // Was the client's account reported to the authorities?
- was_reported?: boolean;
-
- }
+table is of type `LegitimizationRuleSet`.
+The ``jproperties`` JSON in the ``legitimization_outcomes`` table is of
+type `AccountProperties`.
KYC forms
diff --git a/design-documents/046-mumimo-contracts.rst b/design-documents/046-mumimo-contracts.rst
index 8cb35316..7d2197d4 100644
--- a/design-documents/046-mumimo-contracts.rst
+++ b/design-documents/046-mumimo-contracts.rst
@@ -326,7 +326,7 @@ The contract terms v1 will have the following structure:
// Start of the validity period of the token.
valid_after: Timestamp;
- // Number of tokens to be yelded.
+ // Number of tokens to be issued.
// Defaults to one if the field is not provided.
number?: Integer;
diff --git a/design-documents/053-wallet-ui.rst b/design-documents/053-wallet-ui.rst
index 57f9b1f2..792f58e2 100644
--- a/design-documents/053-wallet-ui.rst
+++ b/design-documents/053-wallet-ui.rst
@@ -132,11 +132,6 @@ Issues
the "add" and "send" buttons on this page.
* WebEx (image): Screenshot does not show any
pending transactions.
-* Android (text): Uses "Exchange:" which is
- not user-facing terminology. Should only show the URLs.
-* Android (minor): Screenshot shows only a "pending"
- badge, which seems redundant given "+10 KUDOS inbound"
- (too much information?)
* iOS (major): Way too much detail shown, way too
much explanation text, too many operations
(send money, request payment, spend test money!!!!);
@@ -150,7 +145,6 @@ Issues
'tab' entirely!
* iOS (text): bad icon for "pending outgoing", the
"minus" sign does not give me good associations here
-* Android (text): Title should be *Balances* (plural)
* Android (minor): No "add" and "Send $CURRENCY" buttons
on this page.
@@ -288,8 +282,6 @@ Issues
payments and debit and POS) and again same icon
for invoice and withdraw) is sub-optimal. Should
pick unique icons for each type of operation.
-* Android (text): Button labels should just be
- "Send" and "Receive" (without "funds").
* iOS (text): Iconography (+ / -) is also not
expressive and redundant with the colors.
* iOS (text): Sign of the operation (+ / -) should
@@ -587,9 +579,7 @@ Issues
the amount **3** times. Maybe only show the amount
on top with the wire transfer details, and then at
the bottom show the fee ONCE *if* there is one?
-* Android(text): uses "exchange", which is verboten
* iOS(text): receiver name is missing, MUST be before IBAN
-* Android(text): wire transfer subject is third, should be first
* WebEx(screenshot): the screenshot does not show how to select an alternative bank (see: Netzbon). Would be nice to show that.
* Android(screenshot): the screenshot does not show how to select an alternative bank (see: Netzbon). Would be nice to show that.
@@ -648,8 +638,6 @@ Issues
* WebEx(text): not point in showing details if there are no fees.
* iOS(text): not point in showing details if there are no fees.
-* Android(text): still talks about 'exchange'
-* Android(text): has the amount twice, useless without fees
* iOS(text): status: Done is unnecessary, if we show this screen, it will always be 'done' (ok, theoretically in the middle of withdrawing, but the wire transfer will be done; but then maybe show 'incoming' but hide the status once really "done").
* unclear: "Delete" vs. 'Delete from history" --- two
styles, two translations, gain?
@@ -711,8 +699,6 @@ Actions
Issues
^^^^^^
-* Android(text) has button label "OK", should probably be "Open" for uniformity.
-* Android(text) has "Enter Taler URI", while WebEx has clearer text "enter taler:// URI".
* iOS (screenshot): lacks dialog (or screenshot?) entirely, not sure if we need manual URL-entry on mobile though, so could be OK!
@@ -825,7 +811,6 @@ Actions
Issues
^^^^^^
-* Android(text): details say receipt, but WebEx uses the slightly more accurate "Invoice ID". Could also use "Order #"?
* iOS(major): delete button is missing, instead only has "Done"
@@ -947,7 +932,6 @@ Actions
Issues
^^^^^^
-* Android(text): Webex uses "1 Week" instead of "7 days", let's use "week".
* iOS(text): 3 min/ 1h are inconsistent; other wallets have 1 day, 7 days, 30 days. We should be consistent.
diff --git a/design-documents/054-dynamic-form.rst b/design-documents/054-dynamic-form.rst
index d93b3684..5567fc60 100644
--- a/design-documents/054-dynamic-form.rst
+++ b/design-documents/054-dynamic-form.rst
@@ -1,10 +1,13 @@
-DD 54: Dynamic Web Form
-#######################
+DD 54: Dynamic Form
+###################
Summary
=======
-This document outlines the approach for implementing a dynamic web form feature.
+This document outlines the design of forms defined in the
+backend in a JSON file which will be rendered by a client
+for asking information to a person.
+
Motivation
==========
@@ -12,12 +15,16 @@ Motivation
Currently, creating a new form for a web app involves coding a new
page with HTML, CSS, and JS. Exchange AML requires multiple forms,
and different instances may have distinct forms based on jurisdiction.
+Being able to define forms in a JSON file that a client software
+(not just web SPA) could use to ask the information helps to change
+it without requiring a new upgrade of the client app.
Requirements
============
-A form consist of a layout and a set of fields.
+A form consist of a layout, a set of fields and metadata required to
+recognice which form configuration was used to produce the saved value.
Layout requirements
-------------------
@@ -41,159 +48,609 @@ Fields requirements
complex composite structure.
+Metadata requirements
+---------------------
+
+* **identification**: the form configuration instance should have an unique
+ non reusable id.
+
+* **label**: the form should have a name recognizable for the user
+
+* **version**: the same form, with the same id, could be updated. This will
+ increase the version number. A newer form should support older forms.
+
Proposed Solutions
==================
-Forms are initialized using a flexible structure defined by the
-TypeScript interface FormType<T>. This interface comprises properties
-such as value (current form data), initial (initial form data for resetting),
-readOnly (flag to disable input), onUpdate (callback on form data update),
-and computeFormState (function to derive the form state based on current data).
+The propose solution defines the structure of a form, the fields and additional
+type form-configuration which links a form with a set of fields.
+
+Form metadata
+-------------
+
+This is the root object of the configuration.
+.. code-block:: typescript
+
+ type FormMetadata = {
+ label: string;
+ id: string;
+ version: number;
+ config: FormConfiguration;
+ };
+
+
+Form configuration
+------------------
+
+Defies a basic structure and the set of fields the form is going to have.
+
+The ``FormConfiguration`` is an enumerated type which list can be extended in the
+future.
.. code-block:: typescript
- interface FormType<T extends object> {
- value: Partial<T>;
- initial?: Partial<T>;
- readOnly?: boolean;
- onUpdate?: (v: Partial<T>) => void;
- computeFormState?: (v: Partial<T>) => FormState<T>;
+ type FormConfiguration = DoubleColumnForm;
+
+ type DoubleColumnForm = {
+ type: "double-column";
+ design: DoubleColumnFormSection[];
}
+ type DoubleColumnFormSection = {
+ title: string;
+ description?: string;
+ fields: UIFormElementConfig[];
+ };
-``T``: is the type of the result object
-``value``: is a reference to the current value of the result
-``initial``: data for resetting
-``readOnly``: when true, fields won't allow input
-``onUpdate``: notification of the result update
-``computeFormState``: compute a new state of the form based on the current value
-Form state have the same shape of ``T`` but every field type is ``FieldUIOptions``.
+Form fields
+-----------
-Fields type can be:
- * strings
- * numbers
- * boolean
- * arrays
- * object
+A form can have two type of element: decorative/informative or input.
-The field type ``AmountJson`` and ``AbsoluteTime`` are opaque since field is used as a whole.
+An example of a decorative element is a grouping element which make all the fields
+inside the group look into the same section. This element will not allow the user
+to enter information and won't produce any value in the resulting JSON.
-The form can be instanciated using
+An example of an input field is a text field which allows the user to enter text.
+This element should have an ``id`` which will point into the location in which the
+value will be stored in the resulting JSON. Note that two field in the same form
+with the same ``id`` will result in undefined behavior.
+
+The ``UIFormElementConfig`` is an enumerated type with all type of fields supported
+
+.. code-block:: typescript
+
+ type UIFormElementConfig =
+ | UIFormElementGroup
+ | UIFormElementCaption
+ | UIFormFieldAbsoluteTime
+ | UIFormFieldAmount
+ | UIFormFieldArray
+ | UIFormFieldChoiseHorizontal
+ | UIFormFieldChoiseStacked
+ | UIFormFieldFile
+ | UIFormFieldInteger
+ | UIFormFieldSelectMultiple
+ | UIFormFieldSelectOne
+ | UIFormFieldText
+ | UIFormFieldTextArea
+ | UIFormFieldToggle;
+
+
+All form elements should extend from ``UIFieldElementDescription`` which defines a base
+configuration to show a field.
.. code-block:: typescript
- import { FormProvider } from "@gnu-taler/web-util/browser";
+ type UIFieldElementDescription = {
+ /* label if the field, visible for the user */
+ label: string;
+
+ /* long text to be shown on user demand */
+ tooltip?: string;
+
+ /* short text to be shown close to the field, usually below and dimmer*/
+ help?: string;
+
+ /* name of the field, useful for a11y */
+ name: string;
+ /* if the field should be initially hidden */
+ hidden?: boolean;
+
+ };
+
+That will be enough for a decorative form element (like group element or
+a text element) but if it defines an input field then it should extend
+from ``UIFormFieldBaseConfig`` which add more information to the previously
+defined ``UIFieldElementDescription``.
-Then the field component can access all the properties by the ``useField(name)`` hook,
-which will return
.. code-block:: typescript
- interface InputFieldHandler<Type> {
- value: Type;
- onChange: (s: Type) => void;
- state: FieldUIOptions;
- isDirty: boolean;
- }
+ type UIFormFieldBaseConfig = UIFieldElementDescription & {
+ /* example to be shown inside the field */
+ placeholder?: string;
+ /* show a mark as required */
+ required?: boolean;
-``value``: the current value of the field
-``onChange``: a function to call anytime the user want to change the value
-``state``: the state of the field (hidden, error, etc..)
-``isDirty``: if the user already tried to change the value
+ /* readonly and dim */
+ disabled?: boolean;
-A set of common form field exist in ``@gnu-taler/web-util``:
+ /* conversion id to convert the string into the value type
+ the id should be known to the ui impl
+ */
+ converterId?: string;
- * InputAbsoluteTime
- * InputAmount
- * InputArray
- * InputFile
- * InputText
- * InputToggle
+ /* property id of the form */
+ id: UIHandlerId;
+ };
-and should be used inside a ``Form`` context.
+ /**
+ * string which defined a json path
+ *
+ */
+ type UIHandlerId = string
-.. code-block:: none
- function MyFormComponent():VNode {
- return <FormProvider>
- <InputAmount name="amount" />
- <InputText name="subject" />
- <button type="submit"> Confirm </button>
- </FormProvider>
- }
+The ``id`` property defines the location in which this information is going
+to be saved in the JSON result. Formally formally, it should be a ``dot-selector``
+
+.. code-block:: ini
+
+ dot-selector = "." dot-member-name
+ dot-member-name = name-first *name-char
+ name-first = ALPHA / "_"
+ name-char = DIGIT / name-first
+
+ DIGIT = %x30-39 ; 0-9
+ ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
+All the input fields will create a string value located where the id
+points, unless a ``convertedId`` is specified. The ``convertedId`` is a reference
+to a converter that the client software implements. For example, an input field
+with ``convertedId = "Taler.Amount"`` will transform the value the user
+entered into a *AmountString* with the currency in the configuration.
+
+
+Description of supported fields
+-------------------------------
+
+All of this fields defines an UI handler which help the user to input
+the value with as handy as possible. The type of the field doesn't define
+the type of the value in the resulting JSON, that's defined by the ``converterId``.
+
+Decorative elements
+```````````````````
+
+To show some additional text
+
+.. code-block:: typescript
+
+ type UIFormElementCaption = { type: "caption" } & UIFieldElementDescription;
+
+To group fields in the UI and maybe show a collapsable handler.
+
+.. code-block:: typescript
+
+ type UIFormElementGroup = {
+ type: "group";
+ fields: UIFormElementConfig[];
+ } & UIFieldElementDescription;
+
Example
---------
+'''''''
+
+.. code-block:: json
+
+ {
+ "label": "Example form",
+ "id": "example",
+ "version": 1,
+ "config": {
+ "type": "double-column",
+ "design": [
+ {
+ "title": "Decorative elements",
+ "fields": [
+ {
+ "type": "caption",
+ "name": "cap",
+ "label": "This is a caption"
+ },
+ {
+ "type": "group",
+ "name": "group",
+ "label": "The first name and last name are in a group",
+ "fields": [
+ {
+ "type": "text",
+ "name": "firstName",
+ "id": ".person.name",
+ "label": "First name"
+ },
+ {
+ "type": "text",
+ "name": "lastName",
+ "id": ".person.lastName",
+ "label": "Last name"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+
+.. image:: ../screenshots/dynamic-forms.decorative-elements.png
+ :width: 400
+
+Time input
+``````````
+
+This may be rendered as a calendar
+
+.. code-block:: typescript
+
+ type UIFormFieldAbsoluteTime = {
+ type: "absoluteTimeText";
+ max?: TalerProtocolTimestamp;
+ min?: TalerProtocolTimestamp;
+ pattern: string;
+ } & UIFormFieldBaseConfig;
+
+.. code-block:: json
+
+ {
+ "label": "Example form",
+ "id": "example",
+ "version": 1,
+ "config": {
+ "type": "double-column",
+ "design": [
+ {
+ "title": "Time inputs",
+ "fields": [
+ {
+ "type": "absoluteTime",
+ "name": "thedate",
+ "id": ".birthdate",
+ "converterId": "Taler.AbsoluteTime",
+ "help": "the day you born",
+ "pattern":"dd/MM/yyyy",
+ "label": "Birthdate"
+ }
+ ]
+ }
+ ]
+ }
+ }
+
+.. image:: ../screenshots/dynamic-forms.time.png
+ :width: 400
+
+
+Amount input
+````````````
+
+Money input.
+
+.. code-block:: typescript
+
+ type UIFormFieldAmount = {
+ type: "amount";
+ max?: Integer;
+ min?: Integer;
+ currency: string;
+ } & UIFormFieldBaseConfig;
+
+.. code-block:: json
+
+ {
+ "label": "Example form",
+ "id": "example",
+ "version": 1,
+ "config": {
+ "type": "double-column",
+ "design": [
+ {
+ "title": "Amount inputs",
+ "fields": [
+ {
+ "type": "amount",
+ "name": "thedate",
+ "id": ".amount",
+ "converterId": "Taler.Amount",
+ "help": "how much do you have?",
+ "currency":"EUR",
+ "label": "Amount"
+ }
+ ]
+ }
+ ]
+ }
+ }
+
+.. image:: ../screenshots/dynamic-forms.amount.png
+ :width: 400
-Consider a form shape represented by the TypeScript type:
+
+List input
+``````````
+
+This input allows to enter more than element in the same field, and the
+resulting JSON will have a json list. The UI should show the elements
+already present in the list, and for that it will use ``labelFieldId``.
.. code-block:: typescript
- type TheFormType = {
- name: string,
- age: number,
- savings: AmountJson,
- nextBirthday: AbsoluteTime,
- pets: string[],
- addres: {
- street: string,
- city: string,
+ type UIFormFieldArray = {
+ type: "array";
+ // id of the field shown when the array is collapsed
+ labelFieldId: UIHandlerId;
+ fields: UIFormElementConfig[];
+ } & UIFormFieldBaseConfig;
+
+.. code-block:: json
+
+ {
+ "label": "Example form",
+ "id": "example",
+ "version": 1,
+ "config": {
+ "type": "double-column",
+ "design": [
+ {
+ "title": "Amount inputs",
+ "fields": [
+ {
+ "type": "array",
+ "name": "people",
+ "id": ".people",
+ "help": "who is coming to the party?",
+ "labelFieldId": ".name",
+ "fields": [{
+ "type": "text",
+ "name": "firstName",
+ "id": ".name",
+ "label": "First name"
+ },
+ {
+ "type": "text",
+ "name": "lastName",
+ "id": ".lastName",
+ "label": "Last name"
+ }],
+ }
+ ]
+ }
+ ]
+ }
}
+
+
+.. image:: ../screenshots/dynamic-forms.list.png
+ :width: 400
+
+Choice input
+````````````
+
+To be used when the user need to choose on predefined values
+
+.. code-block:: typescript
+
+ interface SelectUiChoice {
+ label: string;
+ description?: string;
+ value: string;
}
-An example instance of this form could be:
+A set of buttons next to each other
.. code-block:: typescript
- const theFormValue: TheFormType = {
- name: "Sebastian",
- age: 15,
- pets: ["dog","cat"],
- address: {
- street: "long",
- city: "big",
+ type UIFormFieldChoiseHorizontal = {
+ type: "choiceHorizontal";
+ choices: Array<SelectUiChoice>;
+ } & UIFormFieldBaseConfig;
+
+
+A set of buttons next on top of each other
+
+.. code-block:: typescript
+
+ type UIFormFieldChoiseStacked = {
+ type: "choiceStacked";
+ choices: Array<SelectUiChoice>;
+ } & UIFormFieldBaseConfig;
+
+A drop down list to select one of the elements
+
+.. code-block:: typescript
+
+ type UIFormFieldSelectOne = {
+ type: "selectOne";
+ choices: Array<SelectUiChoice>;
+ } & UIFormFieldBaseConfig;
+
+A drop down list to select multiple of the element, which
+will produce a list of values in the resulting JSON.
+
+.. code-block:: typescript
+
+ type UIFormFieldSelectMultiple = {
+ type: "selectMultiple";
+ max?: Integer;
+ min?: Integer;
+ unique?: boolean;
+ choices: Array<SelectUiChoice>;
+ } & UIFormFieldBaseConfig;
+
+
+.. code-block:: json
+
+ {
+ "label": "Example form",
+ "id": "example",
+ "version": 1,
+ "config": {
+ "type": "double-column",
+ "design": [
+ {
+ "title": "Choice inputs",
+ "fields": [
+ {
+ "type": "choiceHorizontal",
+ "name": "food",
+ "label": "Food",
+ "id": ".food",
+ "choices": [
+ {
+ "value": "meat",
+ "label": "Meat"
+ },
+ {
+ "value": "sushi",
+ "label": "Sushi"
+ },
+ {
+ "value": "taco",
+ "label": "Taco"
+ },
+ {
+ "value": "salad",
+ "label": "Salad"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+
+.. image:: ../screenshots/dynamic-forms.choice.png
+ :width: 400
+
+File input
+``````````
+
+.. code-block:: typescript
+
+ type UIFormFieldFile = {
+ type: "file";
+ maxBytes?: Integer;
+ minBytes?: Integer;
+ // comma-separated list of one or more file types
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept#unique_file_type_specifiers
+ accept?: string;
+ } & UIFormFieldBaseConfig;
+
+
+.. code-block:: json
+
+ {
+ "label": "Example form",
+ "id": "example",
+ "version": 1,
+ "config": {
+ "type": "double-column",
+ "design": [
+ {
+ "title": "File inputs",
+ "fields": [
+ {
+ "type": "file",
+ "name": "photo",
+ "id": ".photo",
+ "label": "Photo",
+ "accept": "*.png"
+ }
+ ]
+ }
+ ]
}
}
+.. image:: ../screenshots/dynamic-forms.file.png
+ :width: 400
+
+Number input
+````````````
+
+.. code-block:: typescript
+
+ type UIFormFieldInteger = {
+ type: "integer";
+ max?: Integer;
+ min?: Integer;
+ } & UIFormFieldBaseConfig;
+
+
+.. image:: ../screenshots/dynamic-forms.number.png
+ :width: 400
+
+Text input
+``````````
+
+A simple line of text
-For such a form, a valid state can be computed using a function like
-``computeFormStateBasedOnFormValues``, returning an object indicating
-the state of each field, including properties such as ``hidden``,
-``disabled``, and ``required``.
-
-
-.. code-block:: javascript
-
- function computeFormStateBasedOnFormValues(formValues): {
- //returning fixed state as an example
- //the return state will be commonly be computed from the values of the form
- return {
- age: {
- hidden: true,
- },
- pets: {
- disabled: true,
- elements: [{
- disabled: false,
- }],
- },
- address: {
- street: {
- required: true,
- error: "the street name was not found",
- },
- city: {
- required: true,
- },
- },
+.. code-block:: typescript
+
+ type UIFormFieldText = { type: "text" } & UIFormFieldBaseConfig;
+
+A bigger multi-line of text
+
+.. code-block:: typescript
+
+ type UIFormFieldTextArea = { type: "textArea" } & UIFormFieldBaseConfig;
+
+
+.. image:: ../screenshots/dynamic-forms.text.png
+ :width: 400
+
+Boolean input
+`````````````
+
+.. code-block:: typescript
+
+ type UIFormFieldToggle = { type: "toggle" } & UIFormFieldBaseConfig;
+
+
+.. code-block:: json
+
+ {
+ "label": "Example form",
+ "id": "example",
+ "version": 1,
+ "config": {
+ "type": "double-column",
+ "design": [
+ {
+ "title": "Boolean inputs",
+ "fields": [
+ {
+ "type": "toggle",
+ "name": "the_question",
+ "id": ".the_question",
+ "label": "Yes or no?"
+ }
+ ]
+ }
+ ]
}
}
+.. image:: ../screenshots/dynamic-forms.boolean.png
+ :width: 400
+
+Examples
+========