diff options
Diffstat (limited to 'design-documents/037-wallet-transactions-lifecycle.rst')
-rw-r--r-- | design-documents/037-wallet-transactions-lifecycle.rst | 1049 |
1 files changed, 1049 insertions, 0 deletions
diff --git a/design-documents/037-wallet-transactions-lifecycle.rst b/design-documents/037-wallet-transactions-lifecycle.rst new file mode 100644 index 00000000..535e4ff8 --- /dev/null +++ b/design-documents/037-wallet-transactions-lifecycle.rst @@ -0,0 +1,1049 @@ +DD 37: Wallet Transaction Lifecycle +################################### + +.. contents:: Table of Contents + :depth: 2 + +Summary +======= + +This design doc discusses the lifecycle of transactions in wallet-core. + +Motivation +========== + +The transactions in wallet-core all should have an associated state machine. All transactions +should have some common actions that work uniformly across all transactions. + +Requirements +============ + +The underlying state machine should make it obvious what interactions +are possible for the user. The number of possible user interactions +in any state should be small. + +Proposed Solution +================= + + +Common States +------------- + +The following states apply to multiple different transactions. Only pending +and aborting have transaction-specific sub-states, denoted by ``state(substate)``. + +``pending``: A pending transaction waits for some external event/service. +The transaction stays pending until its change on the wallet's material balance +is finished. Any pending state can be suspended and resumed. + +There are some other distinctions for pending transactions: + +* long-polling vs. exponential backoff: A pending transaction is either waiting + on an external service by making a long-polling request or by repeating requests + with exponential back-off. +* ``lastError``: A pending transaction is either clean (i.e. the network interaction + is literally active in transmission or the external service successfully + communicated that it is not ready yet and this is perfectly normal) + or has a ``lastError``, which is a ``TalerErrorDetails`` + object with details about what happened during the last attempt to proceed + with the transaction. + +``done``: A transaction that is done does not require any more processing. It also +never has a ``lastError`` but is considered successful. + +``dialog``: A transaction requires input from the user. + +``aborting``: Similar to a pending transaction, but instead of taking active steps to +complete the transaction, the wallet is taking active steps to abort it. The ``lastError`` +indicates errors the wallet experienced while taking active steps to abort the transaction. + +``aborted``: Similar to ``done``, but the transaction was successfully aborted +instead of successfully finished. It will have the information of when (timestamp) it was +aborted and in which pending sub-state the abort action was initiated. Also, we can +include more information information relevant to the transaction in ``abortReason`` + +``suspended``: Similar to a ``aborted`` transaction, but the transaction was could be +resumed and may then still succeed. + +``suspended-aborting``: Network requests or other expensive work +to abort a transaction is paused. + +``failed``: Similar to ``done``, but the transaction could not be completed or +possibly not even be aborted properly. The user may have lost money. In some +cases, a report to the auditor would make sense in this state. + +``expired``: Similar to ``failed``, but the failure was caused by a timeout. + +``deleted``: A ``deleted`` state is always a final state. We only use this +state for illustrative purposes. In the implementation, the data associated +with the transaction would be literally deleted. + + +Common Transitions +------------------ + +Transitions are actions or other events. + +``[action:retry]``: Retrying a transaction *(1.)* stops ongoing long-polling +requests for the transaction *(2.)* resets the retry timeout *(3.)* re-runs the +handler to process the transaction. Retries are always possible the following +states: ``pending(*)`` and ``aborting(*)``. + +.. attention:: + + Should we show the retry timeout in the UI somewhere? Should we show it in dev mode? + + SEBASJM: Since the wallet will retry anyway, maybe is better if we replace the "retry" + button with a "try now" button and a side text "retrying in xxx seconds". + + CG: Instead of a side text, this *might* make a good mouse-over hint for + a "retry" (or "try now") button. I would not make this overly visible with + side-text as the information is not that important. The text should also be + "retrying next at XXX" using an absolute time XXX --- otherwise the UI would + be way too busy recomputing/updating all of these strings: Using an absolute time, + we only have to redraw anything once a retry actually happened. Given that + retries should basically never be > 24h (we can impose a hard cap), the absolute + time can just be in the format HH:MM:SS (without day). + +``[action:suspend]``: Suspends a pending transaction, stopping any associated +network activities, but with a chance of trying again at a later time. This +could be useful if a user needs to save battery power or bandwidth and an +operation is expected to take longer (such as a backup, recovery or very large +withdrawal operation). + +``[action:resume]``: Suspended transactions may be resumed, placing them back +into a pending state. + +``[action:abort]``: Aborting a transaction either directly stops processing for the +transaction and puts it in an ``aborted`` state, or starts the necessary steps to +actively abort the transaction (e.g. to avoid losing money) and puts it in an +``aborting`` state. + +``[action:fail]``: Directly puts an ``aborting`` or ``pending`` transaction into a +``failed`` state. May result in an ultimate loss of funds (beyond fees) to the +user and thus requires additional consent. + +``[action:delete]``: Deleting a transaction completely deletes the transaction +from the database. Depending on the type of transaction, some of the other +data *resulting* from the transaction might still survive deletion. For +example, deleting a withdrawal transaction does not delete already +successfully withdrawn coins. Deleting is only safe (no money lost) on initial +and final states (failed, aborted, done). + +Whether aborting, deleting or suspending are possible depends on +the transaction type, and usually only one of the four choices should be +offered. + + +.. image:: ../images/transaction-common-states.png + + +Boxed labels indicate an end state in which there is no network activity and +hence no need to give the user a way to abort or suspend the activity. The +circle indicates the initial state. Ovals are states with network activity. + +Blue arrows are used for user-triggered actions (via UI buttons). Purple +arrows are used to indicate externally triggered actions. Black arrows +without labels are used for the normal successful path. Red arrows indicate +failure paths. + + +Common pending sub-states +------------------------- + +During the pending state the transaction can go through several sub-states before +reaching a final state. Some of this sub-states are shared between different +transaction types: + +``kyc``: The transaction cannot proceed because the user needs to actively +finish a KYC process. The wallet should show the user a hint on how to +start the KYC process. + +``aml``: The transaction can't proceed because the user needs to wait for the +exchange operator to conclude an AML investigation by the staff at the +exchange. There are two AML substates. In the substate ``pending`` the user +is not expected to take any action and should just wait for the investigation +to conclude. In the substate ``frozen`` the staff at the exchange decided that +the account needed to be frozen. The user should contact the exchange +provider's customer service department and seek resolution (possibly through +the courts) to avoid losing the funds for good. + + +Transaction Type: Withdrawal +---------------------------- + +* ``pending(bank-register-reserve)`` + + Initial state for bank-integrated withdrawals. The wallet submits the reserve public key + and selected exchange to the bank (via the bank integration API). Note that if the + user aborts at this stage, we do not know if the bank is in the confirmation stage, + so we must still *try* to abort the transaction at the bank. + + * ``[processed-success] => pending(bank-confirm-transfer)`` + * ``[processed-error] => failed``: On permanent errors (like 404 for the withdrawal operation), + the wallet gives up. + * ``[action:abort] => aborting(bank)`` + +* ``pending(bank-confirm-transfer)`` + + The wallet waits until the bank has confirmed the withdrawal operation; + usually the user has to complete a 2FA step to *approve* that the money is + wired to the chosen exchange. Note that the user's *approve* action is done + in the bank's user interface and not the wallet's user interface. The wallet + internally merely *polls* for the success or failure of the approve action. + The wallet **may** occasionally (after some initial delay, especially on + failures from the bank-poll to return any result) long-poll for the reserve + status and, if successful, may then directly jump to + ``pending(withdraw-coins)`` if the reserve is filled even if the poll at + the bank did not return success or failure. + + * ``[bank-poll-success] => pending(exchange-wait-reserve)`` + * ``[bank-aborted] => aborted``: Bank denied the operation. + * ``[exchange-poll-success] => pending(withdraw-coins)``: Optional + short-cut transition. Exchange was faster than the bank. + * ``[action:abort] => aborting(bank)`` + +* ``aborting(bank)`` + + The user aborted the withdraw operation in the wallet. The wallet must now + try to signal the bank that the wire transfer should no longer be performed. + Note that it is possible that the bank registration never succeeded (if the + user aborted us during ``pending(bank-register-reserve)``) and in this case + we get an ``unknown transaction`` failure here. It is also theoretically + possible that the user approved the transaction in the bank while + simultaneously aborting in the wallet. In this case, we transition to + ``suspended(exchange-wait-reserve)`` (treating the ``abort`` action as a ``suspend`` + action). + + * ``[processed-success] => aborted`` + * ``[processed-error(already-confirmed)] => suspended(exchange-wait-reserve)``: We + keep a transaction history entry reminding the user about when the already + wired funds will be returned. + * ``[processed-error(unknown-transaction)] => failed`` + +* ``suspended(exchange-wait-reserve)`` + + State where funds were (presumably) wired to the exchange but the wallet + was asked to not proceed with the withdraw, but we still resume. + + In this state, the wallet should show to the user that the money from the + withdrawal reserve will be sent back to the originating bank account after + ``$closing_delay``. Note that the ``resume`` action should be disabled + after ``$closing_delay``. + + * ``[action:delete] => deleted`` + * ``[action:resume] => pending(exchange-wait-reserve)`` + +* ``pending(exchange-wait-reserve)`` + + Initial state for manual withdrawals. Here, the wallet long-polls the + exchange for the reserve status, waiting for the wire transfer to arrive + at the exchange. + + * ``[exchange-poll-success] => pending(withdraw-coins)`` + * ``[action:suspend] => suspended(exchange-wait-reserve)`` + +* ``pending(withdraw-coins)`` + + State where we are finally withdrawing the actual coins. Depending on + the AML and KYC thresholds, we may at any time transition into a + holding pattern on the AML or KYC checks of the exchange. + + It is possible that the selected denominations expired. + In that case, the wallet will re-select denominations. + + * ``[processed-success] => done`` + * ``[processed-kyc-required] => pending(kyc)`` + * ``[processed-aml-required] => pending(aml)`` + * ``[reserve-expired] => expired(reserve)`` + * ``[action:suspend] => suspended(withdraw-coins)`` + +* ``pending(kyc)`` + + State where the user needs to provide some identity data to pass a KYC + check. The wallet only shows the user the link for starting the KYC + process and long-polls the exchange in anticipation of the user + completing the KYC requirement. + + * ``[poll-success] => pending(withdraw-coins)`` + * ``[action:suspend] => suspended(kyc)`` + +* ``suspended(kyc)`` + + State where the user needs to provide some identity data to pass a KYC + check, but the long-polling was explicitly stopped. The user can + choose to resume or delete. + + * ``[action:delete] => deleted`` + * ``[action:resume] => pending(kyc)`` + +* ``pending(aml)`` + + State where the wallet needs to wait for completion of an AML process by an + AML officer of the exchange. The wallet shows that the AML process is + blocking progress. The message shown should distinguish between a mere + pending AML process and an AML freezing decision in terms of the message + shown to the user. If the AML decision is pending at the exchange, he user + should be urged to simply wait. If the funds were frozen, the wallet + informs the user that their funds were frozen due to an AML decision. The + user is urged to contact the exchange operator's AML department out-of-band. + In any case, the wallet long-polls for the AML decision to be made or change + (possibly at a lower frequeny in case of a freeze). + + * ``[poll-success] => pending(withdraw-coins)`` + * ``[action:suspend] => suspended(aml)`` + +* ``suspended(aml)`` + + State where the user needs to await some AML decision by the exchange. + The long-polling was explicitly stopped. The user can choose to resume or delete. + + * ``[action:delete] => deleted`` + * ``[action:resume] => pending(aml)`` + +* ``suspended(withdraw-coins)`` + + In this state, the wallet should show how much money arrived into the wallet + and the rest of the money will be sent back to the originating bank account + after ``$closing_delay``. Note that the ``resume`` action should be + disabled after ``$closing_delay``. + + * ``[action:delete] => deleted`` + * ``[action:resume] => pending(exchange-wait-reserve)`` + +* ``done`` + + The withdrawal operation is complete. + + * ``[action:delete] => deleted`` + +* ``deleted`` + + Withdrawn coins are preserved, as is reserve information for recoup. + So this mostly removes the entry from the visible transaction history. + Only once all coins were spent, the withdraw is fully removed. + + +.. image:: ../images/transaction-withdrawal-states.png + + +Transaction Type: Payment to Merchant +------------------------------------- + +* ``pending(claim-proposal)`` + + We received a ``pay`` URI. Download (claim) the proposal from the merchant. Can fail if + the proposal was already claimed by someone else. If repurchase detection + tells us that we already paid for this product, we go immediately to + ``failed(repurchase)`` state for this transaction, but with a side-effect of + transitioning the UI into a ``pending(repurchase-session-reset)`` on a + *different* transaction (which before was in ``done``). + + A ``failed(repurchase)`` transaction will eventually be GCed (=deleted) + automatically. + + * ``[error:already-claimed] => failed(already-claimed)`` -- the proposal was + already claimed by someone else. + * ``[error:invalid-proposal] => failed(invalid-proposal)`` -- the merchant provided a + proposal that is invalid (e.g. malformed contract + terms or bad signature). + +* ``dialog(merchant-order-proposed)`` + + Let the user accept (or refuse) the payment. + + * ``[action:pay-accept] => pending(submit-payment)`` + * ``[action:pay-refuse] => ``aborted(refused)`` -- The user explicitly + decided not to proceed (at least not with this wallet). + * ``[expired] => failed(expired)`` -- The offer has expired before the user made any + decision. Note that we should use this transition at + least a few seconds before the offer *actually* expires to avoid + encountering an expiration during ``pending(submit-payment)`` in most + real-world scenarios. Basically, we should prevent last-second payments to + be event attempted client-side. + + The ``failed(expired)`` might be automatically deleted upon GC. + +* ``pending(submit-payment)`` + + Submit coin-by-coin (or in bulk groups) until payment is complete. + + * ``[action:abort] => aborting(pay-incomplete)`` -- The user explicitly decided to + abort the process while the payment was happening. Note that if the + payment was already completed (and hence the merchant refuses any + refunds), it is theoretically possible that pressing the abort button will + nevertheless end up in a ``pending(auto-refund)`` state (and subsequently + a ``done`` state) instead! + * ``[success] => pending(auto-refund)`` -- Upon receiving confirmation from + the merchant that the purchase was completed. + * ``[error(insufficient balance)] => aborting(pay-incomplete)`` This transition + happens if we detect double-spending and our balance is not sufficient + after the double-spending. It is also conceivable (but should be rare) + that this transition happens because the offer expired. + +* ``pending(auto-refund)`` + + The payment succeed. We remain in this state as long as an auto-refund-check + is active. If auto refunds are not enabled, we immediately continue to + ``done``. + + * ``[no-auto-refund] => done`` + * ``[timeout] => done`` -- This happens when the auto refund set by the + contract expired. + * ``[long-poll:refund] => aborting(pay-incomplete)`` -- An auto-refund was detected. + * ``[action:abort] => done`` -- The user may explicitly request to abort the + auto-refund processing (for example to enable subsequent deletion before + the auto-refund delay expires). + +* ``aborting(pay-incomplete)`` + + The wallet should interact with the merchant to request + a refund on the incomplete payment. + + * ``[success] => aborted(pay-incomplete)`` + * ``[already-paid] => done`` + +* ``aborted(refunded)`` + + The purchase ended with a (partial) refund. The state (and UI) should show + the specific provenance of the state, which may include an insufficient + balance (due to double-spending being detected during payment), and one or + more partial or full refunds. + + * ``[action:delete] => deleted`` + +* ``done`` + + The purchase is completed. + + * ``[action:delete] => deleted`` + * ``[repurchase] => pending(rebind-session)``: Another offer + became pending for this product and we need to update the session so + that the user does not have to buy it again. + * ``[check-refunds]` => pending(check-refunds)``: New refunds + might be available for this purchase. + +* ``pending(check-refund)`` + + New refunds might be available for this purchase. + This state must only be entered *after* the payment has successfully + completed. It is not relevant for auto-refunds or refunds for incomplete + payments. + + * ``[refunds-checked] => pending(user-new-refund)`` --- New + refund(s) are available, user needs to confirm. + * ``[refunds-checked] => done`` --- Refunds were checked, but no + new refunds are available. + * ``[action:stop-refund-query] => done`` --- + This action would usually only be offered when the state is pending + with errors. It stops the refund query, but the payment of course + is left intact. + +* ``pending(rebind-session)`` + + The wallet should reset the associated session for the already purchased + (digital) item. + + * ``[success] => done`` + * ``[action:abort] => done`` -- User aborted the session reset. + +* ``deleted`` + + When a payment is deleted, associated refund transactions are always deleted + with it. + +.. image:: ../images/transaction-payment-states.png + + +Transaction Type: Refund +------------------------ + +A refund is a pseudo-transaction that is always associated with a merchant +payment transaction. + +* ``pending(accept)`` + + Initial state for a refund. + + * ``[processed-error] => failed``: we received a permanent failure (such as money already wired to the merchant) + +* ``failed`` + + The refund failed permanently. + +.. image:: ../images/transaction-refund-states.png + + +Transaction Type: Refresh +------------------------- + +This is about refreshes that are triggered via coin expiration or as part of +getting change after making a payment. In the first case, the refresh +transaction is forever shown as a separate transaction in the history unless +it did not affect the wallet balance (in which case we hide it). In the second +case, the refresh transaction is folded into the payment transaction upon +completion, so that the balance changes are included in the fees of the +transaction that caused us to obtain change. + +If we have to adjust the refund amount (because a coin has fewer funds on it +than we expect) the transaction only shows the changes due to the refresh, and +we merely adjust the current balance of the wallet but without giving any +justification (as we cannot give details we do not have). So this will look +the same as if the double-spending transaction had been deleted by the user. + +* ``pending`` + + A refresh operation is pending. + + * ``[processed-success] => done`` + * ``[action:suspend] => suspended`` + * ``[failed] => failed`` + +* ``suspended`` + + A refresh operation was suspended by the user. + + * ``[action:resume] => pending`` + +* ``done`` + + The refresh operation completed. + + * ``[action:delete] => deleted`` + +* ``failed`` + + The refresh operation failed. The user lost funds. + + * ``[action:delete] => deleted`` + +* ``deleted`` + + All memory of the refresh operation is lost, but of course the resulting + fresh coins are preserved. + +.. image:: ../images/transaction-refresh-states.png + + +Transaction Type: Deposit +------------------------- + +* ``pending(deposit)`` + + Initial state for deposit transactions. + We deposit the amount coin-by-coin (or in bulk groups) until deposit is completed. + + * ``[action:suspend] => suspended(submit-deposit)`` + * ``[processed-success] => pending(track)`` + * ``[processed-failure] => aborting(refund)`` + +* ``suspended(deposit)`` + + The user suspended our ongoing deposit operation. + + * ``[action:resume] => pending(deposit)`` + * ``[action:abort] => aborting(refund)`` + +* ``pending(track)`` + + All the coins were submitted, waiting to be wired. + + * ``[poll-success] => done`` + * ``[poll-accepted-kyc] => pending(kyc)`` + * ``[poll-accepted-aml] => pending(aml)`` + * ``[action:abort] => aborting(refund)`` + +* ``pending(kyc)`` + + Exchange requires KYC before making the wire transfer. + + * ``[long-poll:kyc] => done`` + * ``[action:suspend] => suspended(kyc)`` + +* ``suspended(kyc)`` + + The user suspended us while we were waiting for KYC to be finished. + + * ``[action:resume] => pending(kyc)`` + +* ``pending(aml)`` + + Exchange requires AML before making the wire transfer. + + * ``[long-poll:aml] => done`` + * ``[action:suspend] => suspended(aml)`` + +* ``suspended(aml)`` + + The user suspended us while we were waiting for AML to be finished. + + * ``[action:resume] => pending(aml)`` + +* ``aborting(refund)`` + + Wallet should try to get the deposited amount back from the exchange (by submitting a refund). + + * ``[action:suspend] => suspended(refund)`` + * ``[processed-success] => aborting(refresh)`` + * ``[processed-error] => aborting(refresh)``: Even if the refund attempt failed, maybe the deposit failed as well and we can still succeed with a refresh. + +* ``suspended(refund)`` + + The user suspended us while we were trying to get a refund. + + * ``[action:resume] => aborting(refund)`` + +* ``aborting(refresh)`` + + * ``[action:suspend] => suspended(refresh)`` + * ``[processed-success] => aborted`` + * ``[processed-error] => failed`` + +* ``suspended(refresh)`` + + The user suspended us while we were trying to do the refresh. + + * ``[action:resume] => aborting(refresh)`` + +* ``aborted`` + + The operation was aborted, some funds may have been lost (to fees or deposited anyway). + + * ``[action:delete] => deleted`` + +* ``done`` + + The deposit operation completed. + + * ``[action:delete] => deleted`` + +* ``deleted`` + + All memory of the deposit operation is lost. + +.. image:: ../images/transaction-deposit-states.png + + +Transaction Type: Peer Push Debit +--------------------------------- + +Peer Push Debit transactions are created when the user wants to transfer money +to another wallet. + +States and transitions: + +* ``pending(purse-create)`` + + The wallet is creating a purse. Initial state. + + * ``[process-success] => pending(ready)``: The wallet has created the purse. + * ``[process-failure] => aborting(refund)``: The purse creation failed. + * ``[action:suspend] => suspended(purse-create)``: The user suspended the operation. + +* ``suspended(purse-create)`` + + * ``[action:resume] => pending(purse-create)``: The user resumed the operation. + * ``[action:abort] => aborting(refund)``: The user aborted the operation. + +* ``pending(ready)`` + + In this state, the user can send / show the ``taler://`` URI or QR code to somebody else. + + * ``[action:abort] => aborting(delete-purse)``: The user aborts the P2P payment. The wallet tries to reclaim money in the purse. + * ``[purse-timeout] => aborting(refresh)``: The other party was too slow and the purse has now expired. + * ``[poll-success] => done``: The other party has accepted the payment. + * ``[poll-error] => aborting(refresh)``: The exchange claims that there is a permanent error regarding the purse. (FIXME(CG): not clear that this is the best transition! Could also go to ``aborting(refund)`` or ``aborting(delete-purse)``; best choice may depend on the specific error returned.) + +* ``aborting(delete-purse)`` + + The wallet is deleting the purse to prevent the receiver from merging it and to reclaim the funds in it. + + * ``[processed-success] => aborting(refresh)``: The purse was deleted successfully, and refunded coins must be refreshed. + * ``[processed-failed(already-merged)] => done``: The other party claimed the funds faster that we were able to abort. + * ``[processed-failed(other)] => aborting(refresh)``: The exchange reports a permanent error. We still try to refresh. + * ``[action:fail] => failed``: The user explicitly asked us to give up and accepted the possible loss of funds. + +* ``aborting(refund)`` + + We abandon the purse that was never fully funded and ask for the deposited coins to be refunded. + + * ``[processed-success] => aborting(refresh)``: After the refund, we still need to refresh the coins. + * ``[processed-failure] => aborting(refresh)``: The refund failed, we still try to refresh the coins. + * ``[action:fail] => failed``: The user explicitly asked us to give up and accepted the possible loss of funds. + +* ``aborting(refresh)`` + + * ``[processed-success] => aborted``: Refresh group finished. Aborting was successful, money was reclaimed. + * ``[processed-failed] => failed``: Refresh group failed to complete with a permanent error. + * ``[action:fail] => failed``: The user explicitly asked us to give up and accepted the possible loss of funds. + +* ``done`` + + The transfer was successful. + + * ``[action:delete] => deleted`` + +* ``aborted`` + + The transfer was aborted. Except for fees, the money was recovered. + + * ``[action:delete] => deleted`` + +* ``failed`` + + The transfer failed. Money was lost. Unless on a forced abort, we should probably complain to the auditor. + + * ``[action:delete] => deleted`` + +* ``deleted`` + + All memory of the push debit operation is lost. + +.. image:: ../images/transaction-push-debit-states.png + + +Transaction Type: Peer Push Credit +---------------------------------- + +Peer Push Credit transactions are created when the user accepts to be paid via +a ``taler://pay-push`` URI. + +States and transitions: + +* ``pending(download)`` + + Wallet read the taler:// URI and is downloading the contract details for the user. + + * ``[processed-success] => pending(user)``: Contract can be shown to the user. + * ``[action:suspend] => suspended(download)``: User suspended the operation. + +* ``suspended(download)`` + + The download of the purse meta data was suspended by the user. + + * ``[action:resume] => pending(download)`` + +* ``pending(user)`` + + User needs to decide about accepting the money. + + * ``[action:accept] => pending(merge)`` + * ``[timeout] => failed``: User took too long to decide. + +* ``pending(merge)`` + + * ``[processed-success] => pending(withdraw)``: Merging the reserve was successful. + * ``[kyc-required] => pending(merge-kyc)``: User must pass KYC checks before the purse can be merged. + * ``[timeout] => failed``: The purse expired before we could complete the merge. + * ``[failure] => failed``: The merge failed permanently. + * FIXME(CG): do we want to allow suspending here? + +* ``pending(merge-kyc)`` + + We cannot merge the purse until passing a KYC check. + The user is shown a hint where to begin the KYC + process and the wallet long-polls on the KYC status. + + * ``[poll-success] => pending(withdraw)`` + * ``[action:suspend] => suspended(kyc)`` + * ``[timeout] => failed``: The purse expired before we could complete the merge. + +* ``suspended(merge-kyc)`` + + We cannot merge the purse until passing a KYC check, + and that check was suspended by the user. + + * ``[action:resume] => pending(kyc)`` + * ``[timeout] => failed``: The purse expired before we could complete the merge. + +* ``pending(withdraw)`` + + The wallet is withdrawing coins from the reserve that was filled by merging + the purse. + + * ``[kyc-required] => pending(withdraw-kyc)`` + * ``[aml-required] => pending(withdraw-aml)`` + * ``[withdraw-failure] => failed`` + * ``[withdraw-success] => done`` + * ``[action:suspend] => suspended(withdraw)`` + +* ``suspended(withdraw)`` + + The user requested the withdraw operation to be suspended. + + * ``[action:resume] => pending(withdraw)`` + +* ``pending(withdraw-kyc)`` + + We cannot withdraw more coins until passing a KYC check. + The user is shown a hint where to begin the KYC + process and the wallet long-polls on the KYC status. + + * ``[poll-success] => pending(withdraw-coins)`` + * ``[action:suspend] => suspended(withdraw-kyc)`` + +* ``suspended(withdraw-kyc)`` + + We cannot withdraw from the reserve until passing a KYC check, + and that check was suspended by the user. + + * ``[action:resume] => pending(withdraw-kyc)`` + +* ``pending(withdraw-aml)`` + + We cannot withdraw more coins until AML rules are satisfied. + The user is shown a hint as to the AML status (pending or frozen). + + * ``[poll-success] => pending(withdraw-coins)`` + * ``[action:suspend] => suspended(withdraw-aml)`` + +* ``suspended(withdraw-aml)`` + + We cannot withdraw from the reserve until AML rules are satisfied, + and the status check was suspended by the user. + + * ``[action:resume] => pending(withdraw-aml)`` + * ``[action:delete] => deleted`` + +* ``failed`` + + The operation failed. Details are shown to the user. The money from the purse eventually goes to the sender (or some other wallet that merged it). + + * ``[action:delete] => deleted`` + +* ``done`` + + The operation succeeded. + + * ``[action:delete] => deleted``: No money will be lost, the withdrawn coins will be kept + +* ``deleted`` + + All memory of the push credit operation is lost. + +.. image:: ../images/transaction-push-credit-states.png + + +Transaction Type: Peer Pull Credit +---------------------------------- + +TODO: Also specify variant where account reserve needs to be created / funded first (Note: post 1.0-feature). + +* ``pending(purse-create)`` + + The wallet is creating a purse. Initial state. + + * ``[process-success] => pending(ready)``: The wallet has created the purse. + * ``[process-failure] => deleted``: The purse creation failed. We only show a transient error. + * ``[action:abort] => deleted``: The user aborted the operation. + +* ``pending(ready)`` + + In this state, the user can send / show the ``taler://`` URI or QR code to + somebody else. + + * ``[action:abort] => aborting(delete-purse)``: The user aborts the P2P payment. + * ``[purse-timeout] => aborted``: The other party was too slow and the purse + has now expired. + * ``[poll-success] => pending(withdraw)``: The other party has made the payment. + * ``[poll-error] => aborting(delete-purse)``: The exchange claims that there + is a permanent error regarding the purse. We should try to delete it. + +* ``aborting(delete-purse)`` + + We are cleaning up the purse after the operation failed or was aborted by + the user. + + * ``[failure:already-merged] => pending(withdraw)``: Too late to abort, the + other side already paid the invoice. + * ``[process-success] => aborted``: The wallet has deleted the purse. + * ``[failure:other] => failed``: The purse deletion failed; we are + nevertheless done. + * ``[action:fail] => failed``: Money may be lost if it was deposited + into the purse in the meantime. + +* ``aborted`` + + The invoicing process ended without success. + + * ``[action:delete] => deleted`` + +* ``pending(withdraw)`` + + The wallet is withdrawing the money paid for the invoice. + + * ``[processed-success] => done`` + * ``[failure] => failed`` + * ``[processed-kyc] => pending(kyc)`` + * ``[processed-aml] => pending(aml)`` + * ``[action:suspend] => suspended(withdraw)`` + +* ``suspended(withdraw)`` + + The user suspended a withdraw operation. + + * ``[action:resume] => pending(withdraw)`` + +* ``pending(kyc)`` + + The user must supply KYC information before withdrawing can continue. + + * ``[poll-success] => pending(withdraw)`` + * ``[action:suspend] => suspended(kyc)`` + +* ``suspended(kyc)`` + + The user suspended waiting for the KYC operation to complete. + + * ``[action:resume] => pending(kyc)`` + +* ``pending(aml)`` + + The user must await a positive exchange AML decision. + + * ``[poll-success] => pending(withdraw)`` + * ``[action:suspend] => suspended(aml)`` + +* ``suspended(aml)`` + + The user suspended waiting for the AML decision to be successful. + + * ``[action:resume] => pending(aml)`` + +* ``failed`` + + Obtaining the money for the invoce failed. This is likely a case for the + auditor. + + * ``[action:delete] => deleted`` + +* ``done`` + + The payment for the invoice was successfully received. + + * ``[action:delete] => deleted`` + +* ``deleted`` + +.. image:: ../images/transaction-pull-credit-states.png + + +Transaction Type: Peer Pull Debit +--------------------------------- + +* ``pending(download)`` + + We are downloading the information about the invoice. Initial state. + + * ``[action:suspend] => suspended(download)`` + * ``[success] => pending(user)`` + +* ``suspended(download)`` + + User suspended downloading the information about the invoice. + + * ``[action:resume] => pending(download)`` + * ``[action:delete] => deleted`` + +* ``pending(user)`` + + We have downloaded information about the pull payment and are waiting for + the user to confirm. + + * ``[action:confirm-pay] => pending(submit-payment)`` + * ``[action:delete] => deleted`` + * ``[timeout] => aborted`` + +* ``pending(deposit)`` + + The user has confirmed the payment and the wallet tries to deposit + into the provided purse. + + * ``[action:suspend] => suspended(deposit)`` + * ``[processed-success] => done`` + * ``[failure:timeout] => aborting(refresh)`` + * ``[processed-success] => done`` + * ``[failure:other] => aborting(refund)`` + +* ``suspended(deposit)`` + + User suspended depositing into the purse. + + * ``[action:resume] => pending(deposit)`` + * ``[action:abort] => aborting_refund`` + +* ``aborting(refund)`` + + Aborts the payment, asking for the already deposited coins to be refunded. + + * ``[processed-success] => aborted(refunded)`` + * ``[processed-failure] => aborting(refresh)`` + * ``[action:fail] => failed`` + +* ``aborting(refresh)`` + + Refreshes the coins that were previously deposited into the purse to recover their value. + + * ``[processed-success] => aborted`` + * ``[processed-failed] => failed`` + +* ``done`` + + The invoice was successfully paid. + + * ``[action:delete] => deleted`` + +* ``deleted`` + + All information about the invoice has been deleted. + +.. image:: ../images/transaction-pull-debit-states.png + +Alternatives +============ + +* Each transaction could be treated completely separately; however, uniform + terminology for actions (and thus button labels) is likely more helpful for + the user experience. + +* We could require user re-approval if fees changed when the available + denominations change during a *withdraw*. This would require a different + state machine on withdraw. We believe the answer can be "no", for two + reasons: the wallet MUST pick denominations to withdraw with the "most + long-term" withdraw window (i.e. active denominations that have the longest + available withdraw durations). So in virtually all normal cases, this will + just succeed as a sane exchange will have a reasonable duration overlap, and + in the very few cases it's really the user's fault for going offline in the + middle of the operation. Plus, even in those few cases, it is highly + unlikely that the fee would actually change: again most key rotations can be + expected to be there to rotate the key, and not to adjust the withdraw fee. + And in the extremely rare case that the user went offline and in the + meantime the fees did *increase*, it's again unlikely to matter much to the + user. So special-casing this and testing this is probably not worth it. + +* We could require user re-approval if due to expired/invalid coins the coin + selection (and thus fees) changes during a *deposit*. Again, expired coins + should virtually never happen unless a user goes offline for a long time in + the middle of a purchase (which would be very strange). If deposit fees + *increase* due to a double-spend detection during payment, we might want to + have an *optional* dialog ("Balance reduced by X as wallet state was not + up-to-date (did you restore from backup?). Consequently, the fees for this + transactions increased from Y to Z. [Abort] [Continue] + checkbox: [X] Do + not ask again."). Probably at best a post-1.0 feature. + + +Discussion / Q&A +================ + +* The diagrams only show what is happening **after** the wallet + has created the transaction. It is possible that network requests + are happening before that, but they are not considered to be part + of the transaction. +* We have decided against a ``cancel`` state, because it resulted + in too much complexity. Instead of doing a direct ``cancel``, + the user has to go to the transaction and abort and/or delete + it. +* We might add a ``revive`` action in the future that allows + to go from ``aborting`` back to ``pending`` for transactions + where this makes sense. We're not doing it right now + to simplify things. |