summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2023-04-08 22:13:55 +0200
committerChristian Grothoff <christian@grothoff.org>2023-04-08 22:13:55 +0200
commitb617c6452ec92c06533e98a26b09c23165fbbf44 (patch)
treed5e12c87517c813a14eecd8d7e7e20bbc7a2cd86
parent343f392149caadaf8e4209863364a61672f5c876 (diff)
downloaddocs-b617c6452ec92c06533e98a26b09c23165fbbf44.tar.gz
docs-b617c6452ec92c06533e98a26b09c23165fbbf44.tar.bz2
docs-b617c6452ec92c06533e98a26b09c23165fbbf44.zip
review payment states
-rw-r--r--Makefile4
-rw-r--r--design-documents/037-wallet-transactions-lifecycle.rst137
-rw-r--r--transaction-payment-states.dot44
3 files changed, 131 insertions, 54 deletions
diff --git a/Makefile b/Makefile
index e60c2dd8..b3e04e29 100644
--- a/Makefile
+++ b/Makefile
@@ -57,6 +57,8 @@ transaction-common-states.png: transaction-common-states.dot
dot -Tpng transaction-common-states.dot > transaction-common-states.png
transaction-withdrawal-states.png: transaction-withdrawal-states.dot
dot -Tpng transaction-withdrawal-states.dot > transaction-withdrawal-states.png
+transaction-payment-states.png: transaction-payment-states.dot
+ dot -Tpng transaction-payment-states.dot > transaction-payment-states.png
coin.png: coin.dot
dot -Tpng coin.dot > coin.png
deposit.png: deposit.dot
@@ -64,7 +66,7 @@ deposit.png: deposit.dot
reserve.png: reserve.dot
dot -Tpng reserve.dot > reserve.png
-diagrams: arch-api.png coin.png deposit.png reserve.png transaction-common-states.png transaction-withdrawal-states.png
+diagrams: arch-api.png coin.png deposit.png reserve.png transaction-common-states.png transaction-withdrawal-states.png transaction-payment-states.png
# The html-linked builder does not support caching, so we
diff --git a/design-documents/037-wallet-transactions-lifecycle.rst b/design-documents/037-wallet-transactions-lifecycle.rst
index e2a04820..366079a2 100644
--- a/design-documents/037-wallet-transactions-lifecycle.rst
+++ b/design-documents/037-wallet-transactions-lifecycle.rst
@@ -286,87 +286,120 @@ shown in the above diagram to keep it on point.
Transaction Type: Payment to Merchant
-------------------------------------
-XXX: Also consider re-selection when the wallet accidentally double-spends coins
-or the selected coins have expired. Do we ask the user in this case?
+* ``pending(claim-proposal)``
-CG: I think no. We correct our balance (after all, we got a proof of
-double-spending) and try other coins. If we do not have enough money left, we
-abort and simply inform the user that their balance was insufficient to make
-the payment after all (very sorry...).
+ Initial state. 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
+ ``delete`` 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``).
-Note that the case of selected coins having expired shouldn't really happen,
-as the wallet should have noticed that when is started up, tried to refresh,
-and if that already failed should have update the balance with a transaction
-history entry saying something like "coins expired, offline too long" or
-something like that.
+ * ``[error: already claimed] => deleted`` -- the proposal was
+ already claimed by someone else; we go directly into the ``deleted``
+ state and only show a transient warning.
+ * ``[error: invalid proposal] => deleted`` -- the merchant provided a
+ proposal that is invalid (e.g. malformed contract
+ terms or bad signature); we go directly into the ``deleted`` state
+ and only show a transient warning.
-(??) If transaction can be aborted while in-progress then it should return
-instructedAmount, effectiveAmount and the affectedAmount (partially effective amount)
-
-* ``initial(download-proposal)``
-
- Initial state. Download (claim) the proposal from the merchant.
-
- XXX: Also consider repurchase detection here?
-
- CG: Well, we could mention that this is a possible transition from
- ``pending(download-proposal)`` to ``deleted`` with a side-effect
- of transitioning the UI into a ``pending(repurchase-session-reset)``
- on a different transaction (which before was in ``done``).
-
- (??) should not reset another transaction?
* ``pending(proposed)``
Let the user accept (or refuse) the payment.
* ``[action:pay-accept] => pending(submit-payment)``
- * ``[action:abort] => deleted`` -- user explicitly decides not
- to proceed
- * ``[action:expired] => deleted`` -- when the offer expires
- before the user decides to make the payment! (We can keep
- pending contracts even in a 'pending transaction' list to
- allow the user to choose to not proceed, but then this
- transition would clean up that list).
+ * ``[action:pay-refuse] => ``aborting(unclaim)`` -- The user explicitly
+ decided not to proceed (at least not with this wallet).
+ * ``[expired] => deleted`` -- The offer has expired before the user made any
+ decision. (We can keep pending contracts even in a 'pending transaction'
+ list to allow the user to choose to not proceed, but then this transition
+ would clean up that list). 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.
+
+* ``aborting(unclaim)``
+
+ Tells the merchant that some *other* wallet is now again free to claim this
+ offer.
* ``pending(submit-payment)``
-Submit coin by coin (or in bulk groups) until payment is complete.
-
- * ``[action:abort] => aborting(refund)``
- * ``[processed-success(auto-refund-enabled)] => pending(refundable)``
- * ``[processed-success(auto-refund-disabled)] => done``
- * ``[processed-error(expired)] => aborting(refresh)`` XXX: If the order is expired but the payment
- succeeded partially before, do we still try an abort-refund? CG: YES, but of course
- we probably should use the ``expired`` transition above a few seconds before the
- offer *actually* expires to avoid this problem in 99.9% of real-world scenarios
- ("prevent last-second payments client-side")
+ Submit coin-by-coin (or in bulk groups) until payment is complete.
+
+ * ``[action:abort] => aborting(refund)`` -- 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(refundable)`` state (and subsequently
+ a ``done`` state) instead!
+ * ``[success] => pending(refundable)`` -- Upon receiving confirmation from
+ the merchant that the purchase was completed.
+ * ``[error(insufficient balance)] => aborting(refresh)`` 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(refundable)``
-The payment succeed but if auto-refund-check is active it will be checking for refunds
+ The payment succeed. We remain in this tate as long as an auto-refund-check
+ is active. If auto refunds are not enabled, we immediately continue to
+ ``done``.
- * ``[auto-refund-timeout] => done``
+ * ``[timeout] => done`` -- This happens when the auto refund set by the contract expired.
+ * ``[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(refund)``
- * ``[processed-success] => aborted(refunded)``
- * ``[processed-failure] => aborting(refresh)``
+ The wallet should interact with the merchant to confirm that a refund
+ was approved.
-* ``aborting(refresh)``
+ * ``[success] => aborted(refunded)``
+ * ``[failure] => aborting(refresh)``
-* ``failed(invalid-proposal)``
+* ``aborting(refresh)``
- The merchant provided a proposal that is invalid (e.g. malformed contract terms or bad signature).
+ The wallet should interact with the exchange to obtain fresh coins for
+ the refunded balance.
* ``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.
+
+ * ``[URI:refund] => aborting(refund)`` -- The previous refund was partial,
+ and we have received an additional refund for this transaction.
+ * ``[action:delete] => deleted``
+
+* ``done``
+
+ The purchase is completed.
+
* ``[action:delete] => deleted``
+ * ``[refund URI trigger] => aborting(refund)`` -- A refund was initiated for the purchase.
+ * ``[repurchase] => pending(repurchase-session-reset)`` -- Another offer became pending for this product.
+
+* ``pending(repurchase-session-reset)``
+
+ The wallet should reset the associated session for the already purchased item.
+
+ * ``[success] => done``
+ * ``[action:abort] => done`` -- User aborted the session reset.
* ``deleted``
- When a payment is deleted, associated refunds are always deleted with it
+ When a payment is deleted, associated refunds are always deleted with it.
+.. image:: ../transaction-payment-states.png
+ :width: 800
+
+Purple arrows are used to indicate transitions triggered in special ways outside
+of the user interface for this transaction, such as side-effects of repurchase
+detection for another purchase or opening of a refund URI.
Transaction Type: Refund
@@ -709,8 +742,6 @@ Alternatives
not ask again."). Probably at best a post-1.0 feature.
-
-
Drawbacks
=========
diff --git a/transaction-payment-states.dot b/transaction-payment-states.dot
new file mode 100644
index 00000000..066fb026
--- /dev/null
+++ b/transaction-payment-states.dot
@@ -0,0 +1,44 @@
+digraph G {
+
+ initial[label="", shape="circle"];
+ pending_dp[label="pending(claim-proposal)"];
+ pending_pr[label="pending(proposed)"];
+ pending_sp[label="pending(submit-payment)"];
+ pending_rf[label="pending(refundable)"];
+ pending_re[label="pending(repurchase-session-reset)"];
+ aborting_unclaim[label="aborting(unclaim)"];
+ aborting_refund[label="aborting(refund)"];
+ aborting_refresh[label="aborting(refresh)"];
+ aborted_refund[label="aborted(refunded)", shape="box"];
+ done[label="done", shape="box"];
+ deleted[label="deleted", shape="box"];
+
+ subgraph {
+ rank = same; pending_dp; pending_pr; pending_sp; pending_rf;
+ }
+
+ initial->pending_dp [color="blue", label="URI trigger"];
+ pending_dp->pending_pr;
+ pending_re->done;
+ pending_dp->deleted [label="repurchase\ndetected"];
+ pending_dp->deleted [color="red", label="already\nclaimed"];
+ pending_dp->deleted [color="red", label="invalid\nproposal"];
+ pending_pr->pending_sp [color="blue", label="action:pay-accept"];
+ pending_pr->aborting_unclaim [color="blue", label="action:pay-refuse"];
+ pending_pr->deleted [label="expired"];
+ pending_sp->pending_rf;
+ pending_sp->aborting_refund [color="blue", label="action:abort"]; pending_sp->aborting_refund [color="red", label="insufficient balance"];
+ pending_rf->aborting_refund [label="long-poll:refund"];
+ done->aborting_refund [color="purple", xlabel="refund URI trigger"];
+ aborted_refund->aborting_refund [color="purple", label="refund URI trigger"];
+ aborting_refund->aborting_refresh;
+ aborting_refund->aborted_refund [color="red", label="failure"];
+ aborting_refresh->aborted_refund;
+ aborted_refund->deleted [color="blue", label="action:delete"];
+ pending_rf->done [label="timeout"];
+ pending_rf->done [color="blue", label="action:abort"];
+ aborting_unclaim->deleted;
+ done->deleted [color="blue", xlabel="action:delete"];
+ done->pending_re [color="purple", label="repurchase"];
+ pending_re->done [color="blue", label="action:abort"];
+}