taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

commit 03f3b96138999431f117c4fec5001c43c8ae5665
parent 9bf6ea3483c8e7426141fe1f0d979e70e4e043b3
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Mon,  7 Sep 2026 01:11:07 +0200

dd98 related updates

Diffstat:
Mcore/merchant/get-fountain-info.rst | 4++++
Mcore/merchant/patch-private-fountains-FOUNTAIN_ID.rst | 8++++----
Mcore/merchant/post-fountain-withdraw.rst | 21+++++++++++++++++++++
Mcore/merchant/post-private-fountains.rst | 4+++-
Mdesign-documents/098-token-fountains.rst | 24+++++++++++++++++++-----
5 files changed, 51 insertions(+), 10 deletions(-)

diff --git a/core/merchant/get-fountain-info.rst b/core/merchant/get-fountain-info.rst @@ -20,6 +20,9 @@ :http:statuscode:`401 Unauthorized`: The fountain secret is unknown. This includes deleted fountains: the wallet should report the access as revoked. + :http:statuscode:`404 Not found`: + A token family disappeared during issue-key resolution, returned with + ``TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN``. :http:statuscode:`500 Internal Server Error`: The server experienced an internal failure. @@ -34,6 +37,7 @@ // Grants of this fountain, with the token family metadata // and issue keys needed to prepare blinded envelopes. + // May be empty; this does not revoke the credential. grants: FountainWalletGrant[]; } diff --git a/core/merchant/patch-private-fountains-FOUNTAIN_ID.rst b/core/merchant/patch-private-fountains-FOUNTAIN_ID.rst @@ -20,10 +20,10 @@ The request body is malformed. Returned with ``TALER_EC_GENERIC_PARAMETER_MALFORMED``. :http:statuscode:`404 Not found`: - The fountain or merchant instance is unknown, or a - ``token_family_slug`` in the grants does not refer to a known - token family. - Returned with ``TALER_EC_MERCHANT_GENERIC_FOUNTAIN_UNKNOWN``. + The fountain or merchant instance is unknown. An unknown fountain is + returned with ``TALER_EC_MERCHANT_GENERIC_FOUNTAIN_UNKNOWN``. + An unknown ``token_family_slug`` in the grants is returned with + ``TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN``. :http:statuscode:`500 Internal Server Error`: The server experienced an internal failure. diff --git a/core/merchant/post-fountain-withdraw.rst b/core/merchant/post-fountain-withdraw.rst @@ -10,6 +10,23 @@ The fountain secret travels in the request body, never in the URL. + A wallet may retry a withdrawal with the same ``grants`` array and + fountain credential. The backend identifies a completed withdrawal + by its canonical JSON hash within that fountain and returns the + original grant results, without consuming quota again. Keep the + envelopes, array order, and ``valid_at`` values (including omission) + unchanged when retrying; JSON whitespace and object member order do + not matter. + + A completed result stays available for as long as the tokens it covers + can still be used, that is until the issue keys it was signed with stop + being valid. This holds across changes to the grants or the key window + of the fountain. Afterwards the result is garbage collected along with + the withdrawal counters it accounts for, and a repeated request is + treated as a new one; deleting the fountain drops its results at once. + Quota consumption, issued-token records and the replay result are + committed together before a successful response is sent. + **Request:** The request must be a `FountainWithdrawRequest`. @@ -25,6 +42,10 @@ :http:statuscode:`401 Unauthorized`: The fountain secret is unknown. This includes deleted fountains: the wallet should report the access as revoked. + :http:statuscode:`404 Not found`: + A token family or issue key disappeared during processing, returned with + ``TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN`` + or ``TALER_EC_MERCHANT_GENERIC_TOKEN_KEY_UNKNOWN``. :http:statuscode:`409 Conflict`: An entry refers to a token family without a matching grant, or its ``valid_at`` lies outside the grant's key window. diff --git a/core/merchant/post-private-fountains.rst b/core/merchant/post-private-fountains.rst @@ -23,6 +23,8 @@ :http:statuscode:`404 Not found`: The merchant instance is unknown, or a ``token_family_slug`` in the grants does not refer to a known token family. + The latter is returned with + ``TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN``. :http:statuscode:`500 Internal Server Error`: The server experienced an internal failure. @@ -43,7 +45,7 @@ poll_freq: RelativeTime; // One grant per token family the user may withdraw - // tokens for. + // tokens for. May be empty; grants can be added later. grants: FountainGrant[]; } diff --git a/design-documents/098-token-fountains.rst b/design-documents/098-token-fountains.rst @@ -4,7 +4,7 @@ DD 98: Token Fountains for Promotions ##################################### :Design status: Draft -:Implementation status: Not started +:Implementation status: Partial :DD shepherd: TBD :Historical contributors: Bohdan Potuzhnyi :First published: 2026-07-23 @@ -108,7 +108,7 @@ A fountain has: * a polling frequency; and -* one or more grants describing which token families may be withdrawn and +* zero or more grants describing which token families may be withdrawn and at what rate. The onboarding URI is: @@ -162,6 +162,9 @@ Wallets periodically retrieve the current grants according to ``poll_freq``. Removing a grant prevents future withdrawals from that family, but tokens already withdrawn remain valid according to the token family's issue keys. +An empty grants array means no grants are currently available, not that the +credential is revoked. Wallets continue polling for updates. + Protocol Flow ------------- @@ -231,6 +234,16 @@ the backend can enforce each grant's limits. Each grant result returns ``token_sigs`` using the same :ts:type:`SignedTokenEnvelope` format as the order payment response. +The backend records completed withdrawals by fountain and canonical request +hash. Retrying with the same ``grants`` array returns the original result +without consuming quota or signing again, even if the grants or key window +have changed. Quota consumption, issued-token records and replay data are +committed in one transaction so a failed operation does not consume quota. +Replay data is retained until the issue keys of the stored signatures expire: +past that point the tokens it would hand back are worthless and the quota it +protects has been collected as well, so it is garbage collected together with +the withdrawal counters. Deleting a fountain drops its replay data at once. + Deletion invalidates future requests using the credential. It does not invalidate tokens that the wallet already withdrew. @@ -242,9 +255,10 @@ systems must therefore protect the URI like any bearer credential. Wallets must avoid exposing it in history, telemetry, screenshots or backups that are not intended to contain secrets. -The backend stores only a hash of ``fountain_secret``. It must rate-limit -failed authentication and enforce withdrawal limits atomically to prevent -concurrent requests from exceeding a grant. +The backend stores only a hash of ``fountain_secret``. Rate limiting of failed +authentication may be handled by the deployment's reverse proxy. The backend +must enforce withdrawal limits atomically to prevent concurrent requests from +exceeding a grant. Fountains are not proof of student identity. Eligibility is decided by the institution when it distributes the link. A shared campaign link can be