summaryrefslogtreecommitdiff
path: root/design-documents/037-wallet-transactions-lifecycle.rst
diff options
context:
space:
mode:
Diffstat (limited to 'design-documents/037-wallet-transactions-lifecycle.rst')
-rw-r--r--design-documents/037-wallet-transactions-lifecycle.rst1049
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.