taler-docs

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

commit 7dc0e5a0f4e3ae3207a6554110d12055396b4f5d
parent 2615380f936607485ae9ba6dd151bd080e7c7247
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 10 Oct 2026 17:08:04 +0200

specify vDonauReceipts API change

Diffstat:
Mcore/api-donau.rst | 3++-
Mcore/api-merchant.rst | 5+++++
Mcore/donau/post-batch-issue-CHARITY_ID.rst | 21+++++++++++++--------
Mcore/merchant/post-orders-ORDER_ID-pay.rst | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mcore/merchant/post-private-orders-ORDER_ID-refund-external.rst | 4++++
Mcore/merchant/post-private-orders-ORDER_ID-refund.rst | 4++++
6 files changed, 163 insertions(+), 23 deletions(-)

diff --git a/core/api-donau.rst b/core/api-donau.rst @@ -67,7 +67,8 @@ The current protocol version is **v1**. **Upcoming versions:** -* none anticipated +* ``vLimitConflict``: answers a ``/batch-issue`` request that exceeds the + charity's remaining yearly limit with 409 instead of 400 **Ideas for future version:** diff --git a/core/api-merchant.rst b/core/api-merchant.rst @@ -92,6 +92,11 @@ The currently implemented protocol version is **v42**. * ``vTokenFountains``: adds fountains for distributing promotional merchant tokens (see :doc:`DD 98 </design-documents/098-token-fountains>`) +* ``vDonauReceipts``: returns donation receipts from ``/pay`` in the new + ``donau_sigs`` field instead of inside ``token_sigs`` (breaking for orders + with a donation receipt output), answers a Donau failure after a + successful payment with a 502 that carries the payment confirmation, and + refuses to replay ``/pay`` after a refund **Ideas for future version:** diff --git a/core/donau/post-batch-issue-CHARITY_ID.rst b/core/donau/post-batch-issue-CHARITY_ID.rst @@ -11,11 +11,11 @@ Also returned for idempotent re-requests of an already-processed batch. :http:statuscode:`400 Bad Request`: The request was malformed. This can occur when the ``$CHARITY_ID`` cannot be - parsed, the ``budikeypairs`` array is empty or contains malformed entries, - or the total donation amount exceeds the charity's per-year limit. - Returned with error code ``TALER_EC_GENERIC_PARAMETER_MALFORMED``, - ``TALER_EC_GENERIC_JSON_INVALID``, or - ``TALER_EC_DONAU_EXCEEDING_DONATION_LIMIT``. + parsed, or the ``budikeypairs`` array is empty or contains malformed entries. + Returned with error code ``TALER_EC_GENERIC_PARAMETER_MALFORMED`` or + ``TALER_EC_GENERIC_JSON_INVALID``. + Before protocol **vLimitConflict**, an exceeded per-year limit was also + reported with this status (see 409). :http:statuscode:`403 Forbidden`: The charity signature is invalid. Returned with error code ``TALER_EC_DONAU_CHARITY_SIGNATURE_INVALID``. @@ -25,9 +25,14 @@ Returned with error code ``TALER_EC_DONAU_CHARITY_NOT_FOUND`` or ``TALER_EC_DONAU_GENERIC_DONATION_UNIT_UNKNOWN``. :http:statuscode:`409 Conflict`: - The year specified does not match the validity year for the given - denomination unit key. Returned with an error code of - ``TALER_EC_DONAU_GENERIC_DONATION_UNIT_WRONG_YEAR``. + The request conflicts with the state of the Donau. Clients must + distinguish the cases by the error code: + + * ``TALER_EC_DONAU_GENERIC_DONATION_UNIT_WRONG_YEAR``: the year specified + does not match the validity year for the given donation unit key. + * ``TALER_EC_DONAU_EXCEEDING_DONATION_LIMIT``: the total donation amount + exceeds what remains of the charity's per-year limit. + Since protocol **vLimitConflict**; before, this was a 400. :http:statuscode:`410 Gone`: The year specified is in the past, and the Donau does not permit issuing donation receipts in the past. The diff --git a/core/merchant/post-orders-ORDER_ID-pay.rst b/core/merchant/post-orders-ORDER_ID-pay.rst @@ -6,6 +6,14 @@ the contract is implied by the signatures of the coins. Furthermore, this API doesn't really return useful information about the order. + Repeating the request for an order that is already paid (a *replay*) is + answered from the state stored with the original payment: the token + signatures are the ones created for the original payment, and donation + receipts that are still missing are requested from the Donau with the + ``budikeypairs`` of the original payment, regardless of the ``wallet_data`` + of the replay. Since protocol **vDonauReceipts**, a replay is refused + once the order has been refunded, even if only in part. + **Request:** The request must be a `pay request <PayRequest>`. @@ -15,6 +23,8 @@ :http:statuscode:`200 OK`: The exchange accepted all of the coins. The body is a `payment response <PaymentResponse>`. + If the wallet requested donation receipts, the Donau issued them + and they are part of the response. The ``frontend`` should now fulfill the contract. Note that it is possible that refunds have been granted. :http:statuscode:`400 Bad request`: @@ -94,7 +104,8 @@ The exchange rejected the payment because a coin was already spent (or used in a different way for the same purchase previously), or the merchant rejected the payment because the order was already fully paid - (and then return signatures with refunds). If a coin was already spent + (and then return signatures with refunds) or has been refunded since it + was paid. If a coin was already spent (this includes re-using the same coin after a refund), the response will include the ``exchange_url`` for which the payment failed, in addition to the response from the exchange to the :http:post:`/batch-deposit </batch-deposit>` request. @@ -114,7 +125,21 @@ * ``MERCHANT_POST_ORDERS_ID_PAY_TOKEN_INVALID``: A token was already used in a previous payment. * ``MERCHANT_POST_ORDERS_ID_PAY_DONATION_AMOUNT_MISMATCH``: The donation - amount does not match the expected amount or exceeds the yearly limit. + amount does not match the expected amount or exceeds what remains of + the charity's yearly limit. Since protocol **vDonauReceipts**, the + donations whose receipts are still pending count against that limit. + * ``MERCHANT_POST_ORDERS_ID_PAY_DONATION_YEAR_INVALID``: The ``year`` in + the ``donau`` field of the wallet data is not the current year (UTC). + Nothing was paid. The response is a `DonationYearInvalidResponse`; + the wallet should create donation receipt requests for the returned + ``current_year`` and pay again. + Since protocol **vDonauReceipts**. + * ``MERCHANT_POST_ORDERS_ID_PAY_REPLAY_AFTER_REFUND``: The order was + already paid and has since been refunded, possibly only in part, so the + request cannot be replayed. The response carries no payment + signature, token signatures or donation receipts; the wallet should + query the refunds instead. + Since protocol **vDonauReceipts**. * ``MERCHANT_GENERIC_CURRENCY_MISMATCH``: Coin currency does not match the expected payment currency. @@ -178,15 +203,37 @@ * ``MERCHANT_GENERIC_FEATURE_NOT_AVAILABLE``: usually returned if a token type is not supported :http:statuscode:`502 Bad gateway`: - The merchant's interaction with the exchange failed in some way. - The client might want to try again later. + The merchant's interaction with the exchange or the Donau failed in + some way. + For the exchange, the client might want to try again later. This includes failures such as the denomination key of a coin not being known to the exchange as far as the merchant can tell. + The merchant only contacts the Donau after the payment succeeded, so + a Donau failure does not undo the payment: the response then carries the + payment confirmation. Applicable error codes: * ``MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS`` * ``MERCHANT_GENERIC_EXCHANGE_REPLY_MALFORMED`` - * ``MERCHANT_GENERIC_DONAU_INVALID_RESPONSE`` + * ``MERCHANT_POST_ORDERS_ID_PAY_DONAU_RECEIPTS_DELAYED``: The payment + succeeded, but the Donau could not issue the donation receipts at this + time (it was unreachable, timed out, failed internally or replied with + something malformed). The body is a `PaymentDonauFailureResponse`. + The wallet should treat the order as paid, keep its donation receipt + requests and replay the payment request later. The merchant keeps + reporting this code until the Donau issues the receipts or stops + issuing receipts for the donation year; from then on it reports + ``MERCHANT_POST_ORDERS_ID_PAY_DONAU_RECEIPTS_REFUSED``. + Since protocol **vDonauReceipts**. + * ``MERCHANT_POST_ORDERS_ID_PAY_DONAU_RECEIPTS_REFUSED``: The payment + succeeded, but the Donau refused to issue the donation receipts. The + body is a `PaymentDonauFailureResponse`. The wallet should treat the + order as paid, tell the user that the promised donation receipts will + not be issued, and not replay the request. + Since protocol **vDonauReceipts**. + * ``MERCHANT_GENERIC_DONAU_INVALID_RESPONSE``: Only before protocol + **vDonauReceipts**, for any Donau failure; the response then carried + no payment confirmation. :http:statuscode:`504 Gateway timeout`: The merchant's interaction with the exchange took too long. @@ -230,18 +277,92 @@ // Since protocol **vChallengeConfirmation**. pos_confirmation?: string; - // Signed tokens. Returned in the same order as the - // token envelopes were provided in the request. Specifically, - // the order will follow the order of the outputs from the - // contract terms, and then within each output follow the - // order in which the ``wallet_data`` contained the respective - // blinded envelopes. The donation tokens will be present - // at the offset matching the place where a donation receipt - // was indicated in the outputs array, and of course be skipped - // if the `PayWalletData` did not have a ``donau`` field. + // Signed tokens for the outputs of type ``token``. Returned in + // the same order as the token envelopes were provided in the + // request. Specifically, the order will follow the order of the + // outputs from the contract terms, and then within each output + // follow the order in which the ``wallet_data`` contained the + // respective blinded envelopes. + // Since protocol **vDonauReceipts**, donation receipts are never + // part of this array, see ``donau_sigs``. Before, they were + // present at the offset matching the place where a donation + // receipt was indicated in the outputs array, and ``donau_sigs`` + // was absent; clients must still handle that layout when + // ``donau_sigs`` is missing although they requested donation + // receipts. // @since protocol **v21** token_sigs?: SignedTokenEnvelope[]; + // Donation receipts blindly signed by the Donau, in the order of + // the ``budikeypairs`` in the ``donau`` field of the + // `PayWalletData`. Present if the wallet requested donation + // receipts. + // @since protocol **vDonauReceipts** + donau_sigs?: SignedDonationReceipt[]; + + } + + .. ts:def:: SignedDonationReceipt + + interface SignedDonationReceipt { + + // Blind signature made by the Donau over one of the + // `BlindedDonationReceiptKeyPair` entries of the request. + blind_sig: BlindedDonationReceiptSignature; + + } + + .. ts:def:: PaymentDonauFailureResponse + + interface PaymentDonauFailureResponse { + + // Numeric `error code <error-codes>`, either + // ``MERCHANT_POST_ORDERS_ID_PAY_DONAU_RECEIPTS_DELAYED`` or + // ``MERCHANT_POST_ORDERS_ID_PAY_DONAU_RECEIPTS_REFUSED``. + code: Integer; + + // Human-readable description of the failure. + hint?: string; + + // The order is paid: as in `PaymentResponse`. + sig: EddsaSignature; + + // As in `PaymentResponse`. + pos_confirmation?: string; + + // As in `PaymentResponse`. The merchant's own token + // signatures are returned even though the donation + // receipts are missing. + token_sigs?: SignedTokenEnvelope[]; + + // Base URL of the Donau that was asked for the receipts. + donau_url: WebURL; + + // HTTP status code of the Donau's reply, or 0 if there was + // no reply (Donau unreachable, timeout, or the request could + // not be created). A malformed reply keeps its status code. + donau_http_status: Integer; + + // Error code of the Donau's reply, if it had one. + donau_ec?: Integer; + + // Hint of the Donau's reply, if it had one. + donau_hint?: string; + + } + + .. ts:def:: DonationYearInvalidResponse + + interface DonationYearInvalidResponse { + + // Error code, always + // ``MERCHANT_POST_ORDERS_ID_PAY_DONATION_YEAR_INVALID``. + code: Integer; + + // Current year (UTC), the only year for which the merchant + // accepts donation receipt requests. + current_year: Integer; + } .. ts:def:: PayRequest diff --git a/core/merchant/post-private-orders-ORDER_ID-refund-external.rst b/core/merchant/post-private-orders-ORDER_ID-refund-external.rst @@ -6,6 +6,10 @@ funds, and no wallet pickup step exists or is needed. The recorded entries are exposed through the order status as ``refunds_external``. + Since protocol **vDonauReceipts**, an external refund, like a Taler refund, + ends the wallet's ability to replay its payment request. Donation receipts + that the Donau had not yet issued for the order are then never issued. + External refunds are available for any *paid* order, including pure Taler orders without ``amount_external``. This is relevant because Taler refunds are constrained by the contract's refund deadline; once it has passed, the diff --git a/core/merchant/post-private-orders-ORDER_ID-refund.rst b/core/merchant/post-private-orders-ORDER_ID-refund.rst @@ -7,6 +7,10 @@ methods, the cumulative Taler refund must not exceed the full order total minus those external refunds. + Since protocol **vDonauReceipts**, any refund, even a partial one, ends the + wallet's ability to replay its payment request. Donation receipts that the + Donau had not yet issued for the order are then never issued. + **Required permission:** ``orders-refund`` (see :ref:`Scopes <merchant-api-scopes>`) **Request:**