diff options
author | Christian Grothoff <christian@grothoff.org> | 2020-10-31 16:08:52 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2020-10-31 16:09:06 +0100 |
commit | f82d5092cdc8fa3a123404f680b43a937cc6ef11 (patch) | |
tree | 54b3a89348b5921efcf753890b375ad9d31f72d2 /core | |
parent | 83caabf7abca7c89230300a02ce5d980ab9ddf56 (diff) | |
download | docs-f82d5092cdc8fa3a123404f680b43a937cc6ef11.tar.gz docs-f82d5092cdc8fa3a123404f680b43a937cc6ef11.tar.bz2 docs-f82d5092cdc8fa3a123404f680b43a937cc6ef11.zip |
restructure merchant API documentation (#6492)
Diffstat (limited to 'core')
-rw-r--r-- | core/api-common.rst | 9 | ||||
-rw-r--r-- | core/api-exchange.rst | 28 | ||||
-rw-r--r-- | core/api-merchant.rst | 1706 |
3 files changed, 955 insertions, 788 deletions
diff --git a/core/api-common.rst b/core/api-common.rst index 1364ae5b..12f4a360 100644 --- a/core/api-common.rst +++ b/core/api-common.rst @@ -335,6 +335,15 @@ Blinded coin // Blinded coin's `public EdDSA key <eddsa-coin-pub>`, `base32` encoded type CoinEnvelope = string; +.. ts:def:: DenominationBlindingKeyP + + // Secret for blinding/unblinding. + // An RSA blinding secret, which is basically + // a 256-bit nonce, converted to Crockford `Base32`. + type DenominationBlindingKeyP = string; + + + .. _signature: Signatures diff --git a/core/api-exchange.rst b/core/api-exchange.rst index bc6a98d5..8b5c43cc 100644 --- a/core/api-exchange.rst +++ b/core/api-exchange.rst @@ -1435,21 +1435,21 @@ Refunds exchange_pub: EddsaPublicKey; } - .. ts:def:: RefundFailure + .. ts:def:: RefundFailure - interface RefundFailure { + interface RefundFailure { - // Numeric error code unique to the condition, which can be either - // related to the deposit value being insufficient for the requested - // refund(s), or the requested refund conflicting due to refund - // transaction number re-use (with different amounts). - code: number; + // Numeric error code unique to the condition, which can be either + // related to the deposit value being insufficient for the requested + // refund(s), or the requested refund conflicting due to refund + // transaction number re-use (with different amounts). + code: number; - // Human-readable description of the error message - hint: string; + // Human-readable description of the error message + hint: string; - // Information about the conflicting refund request(s). - // This will not be the full history of the coin, but only - // the relevant subset of the transactions. - history: CoinSpendHistoryItem[]; - } + // Information about the conflicting refund request(s). + // This will not be the full history of the coin, but only + // the relevant subset of the transactions. + history: CoinSpendHistoryItem[]; + } diff --git a/core/api-merchant.rst b/core/api-merchant.rst index 809bcbf1..53b64243 100644 --- a/core/api-merchant.rst +++ b/core/api-merchant.rst @@ -35,9 +35,12 @@ used. .. contents:: Table of Contents -------------------------- -Getting the configuration -------------------------- +----------------- +Configuration API +----------------- + +The configuration API exposes basic information about a merchant backend, +such as the implemented version of the protocol and the currency used. .. http:get:: /config @@ -63,53 +66,725 @@ Getting the configuration currency: string; } + +---------- +Wallet API +---------- --------------------------- -Dynamic Merchant Instances --------------------------- +This section describes (public) endpoints that wallets must be able +to interact with directly (without HTTP-based authentication). These +endpoints are used to process payments (claiming an order, paying +for the order, checking payment/refund status and aborting payments), +process refunds (check refund status, obtain refund), and to pickup +tips. -.. _instances: -.. http:get:: /private/instances - This is used to return the list of all the merchant instances +Claiming an order +----------------- + +The first step of processing any Taler payment consists of the +(authorized) wallet claiming the order for itself. In this process, +the wallet provides a wallet-generated nonce that is added +into the contract terms. This step prevents two different +wallets from paying for the same contract, which would be bad +especially if the merchant only has finite stocks. + +A claim token can be used to ensure that the wallet claiming an +order is actually authorized to do so. This is useful in cases +where order IDs are predictable and malicious actors may try to +claim orders (say in a case where stocks are limited). + + +.. http:post:: /orders/$ORDER_ID/claim + + Wallet claims ownership (via nonce) over an order. By claiming + an order, the wallet obtains the full contract terms, and thereby + implicitly also the hash of the contract terms it needs for the + other ``/public/`` APIs to authenticate itself as the wallet that + is indeed eligible to inspect this particular order's status. + + **Request:** + + The request must be a `ClaimRequest` + + .. ts:def:: ClaimRequest + + interface ClaimRequest { + // Nonce to identify the wallet that claimed the order. + nonce: string; + + // Token that authorizes the wallet to claim the order. + // *Optional* as the merchant may not have required it + // (``create_token`` set to ``false`` in `PostOrderRequest`). + token?: ClaimToken; + } **Response:** :status 200 OK: - The backend has successfully returned the list of instances stored. Returns - a `InstancesResponse`. + The client has successfully claimed the order. + The response contains the :ref:`contract terms <contract-terms>`. + :status 404 Not found: + The backend is unaware of the instance or order. + :status 409 Conflict: + The someone else claimed the same order ID with different nonce before. - .. ts:def:: InstancesResponse + .. ts:def:: ClaimResponse - interface InstancesResponse { - // List of instances that are present in the backend (see `Instance`) - instances: Instance[]; + interface ClaimResponse { + // Contract terms of the claimed order + contract_terms: ContractTerms; + + // Signature by the merchant over the contract terms. + sig: EddsaSignature; } + +Making the payment +------------------ - The `Instance` object describes the instance registered with the backend. - It does not include the full details, only those that usually concern the frontend. - It has the following structure: +.. http:post:: /orders/$ORDER_ID/pay - .. ts:def:: Instance + Pay for an order by giving a deposit permission for coins. Typically used by + the customer's wallet. Note that this request does not include the + usual ``h_contract`` argument to authenticate the wallet, as the hash of + the contract is implied by the signatures of the coins. Furthermore, this + API doesn't really return useful information about the order. - interface Instance { - // Merchant name corresponding to this instance. - name: string; + **Request:** - // Merchant instance this response is about ($INSTANCE) - id: string; + The request must be a `pay request <PayRequest>`. - // Public key of the merchant/instance, in Crockford Base32 encoding. + **Response:** + + :status 200 OK: + The exchange accepted all of the coins. + The body is a `payment response <PaymentResponse>`. + The ``frontend`` should now fulfill the contract. + Note that it is possible that refunds have been granted. + :status 400 Bad request: + Either the client request is malformed or some specific processing error + happened that may be the fault of the client as detailed in the JSON body + of the response. + :http:statuscode:`402 Payment required`: + There used to be a sufficient payment, but due to refunds the amount effectively + paid is no longer sufficient. (If the amount is generally insufficient, we + return "406 Not Acceptable", only if this is because of refunds we return 402.) + :status 403 Forbidden: + One of the coin signatures was not valid. + :status 404 Not found: + The merchant backend could not find the order or the instance and thus cannot process the payment. + :status 406 Not Acceptable: + The payment is insufficient (sum is below the required total amount). + :status 408 Request Timeout: + The backend took too long to process the request. Likely the merchant's connection + to the exchange timed out. Try again. + :status 409 Conflict: + The exchange rejected the payment because a coin was already spent before. + The response will include the ``coin_pub`` for which the payment failed, + in addition to the response from the exchange to the ``/deposit`` request. + :status 410 Gone: + The offer has expired and is no longer available. + :status 412 Precondition Failed: + The given exchange is not acceptable for this merchant, as it is not in the + list of accepted exchanges and not audited by an approved auditor. + :status 424 Failed Dependency: + The merchant's interaction with the exchange failed in some way. + The client might want to try later again. + This includes failures like the denomination key of a coin not being + known to the exchange as far as the merchant can tell. + + The backend will return verbatim the error codes received from the exchange's + :ref:`deposit <deposit>` API. If the wallet made a mistake, like by + double-spending for example, the frontend should pass the reply verbatim to + the browser/wallet. If the payment was successful, the frontend MAY use + this to trigger some business logic. + + .. ts:def:: PaymentResponse + + interface PaymentResponse { + // Signature on ``TALER_PaymentResponsePS`` with the public + // key of the merchant instance. + sig: EddsaSignature; + + } + + .. ts:def:: PayRequest + + interface PayRequest { + // The coins used to make the payment. + coins: CoinPaySig[]; + + // The session for which the payment is made (or replayed). + // Only set for session-based payments. + session_id?: string; + + } + + .. ts:def:: CoinPaySig + + export interface CoinPaySig { + // Signature by the coin. + coin_sig: EddsaSignature; + + // Public key of the coin being spend. + coin_pub: EddsaPublicKey; + + // Signature made by the denomination public key. + ub_sig: RsaSignature; + + // The hash of the denomination public key associated with this coin. + h_denom: HashCode; + + // The amount that is subtracted from this coin with this payment. + contribution: Amount; + + // URL of the exchange this coin was withdrawn from. + exchange_url: string; + } + +Querying payment status +----------------------- + +.. http:get:: /orders/$ORDER_ID + + Query the payment status of an order. This endpoint is for the wallet. + When the wallet goes to this URL and it is unpaid, + they will be prompted for payment. + This endpoint typically also supports requests with the "Accept" header + requesting "text/html". In this case, an HTML response suitable for + triggering the interaction with the wallet is returned, with ``timeout_ms`` + ignored (treated as zero). If the backend installation does not include the + required HTML templates, a 406 status code is returned. + + In the case that the request was made with a claim token (even the wrong one) + and the order was claimed and paid, the server will redirect the client to + the fulfillment URL. This redirection will happen with a 302 status code + if the "Accept" header specified "text/html", and with a 202 status code + otherwise. + + **Request:** + + :query h_contract=HASH: hash of the order's contract terms (this is used to authenticate the wallet/customer in case $ORDER_ID is guessable). Required once an order was claimed. + :query token=TOKEN: *Optional*. Authorizes the request via the claim token that was returned in the `PostOrderResponse`. Used with unclaimed orders only. Whether token authorization is required is determined by the merchant when the frontend creates the order. + :query session_id=STRING: *Optional*. Session ID that the payment must be bound to. If not specified, the payment is not session-bound. + :query timeout_ms=NUMBER: *Optional.* If specified, the merchant backend will + wait up to ``timeout_ms`` milliseconds for completion of the payment before + sending the HTTP response. A client must never rely on this behavior, as the + merchant backend may return a response immediately. + :query refund=AMOUNT: *Optional*. Indicates that we are polling for a refund above the given AMOUNT. Only useful in combination with timeout. + :query await_refund_obtained=BOOLEAN: *Optional*. If set to "yes", poll for the order's pending refunds to be picked up. + + **Response:** + + :status 200 OK: + The response is a `StatusPaidResponse`. + :status 202 Accepted: + The response is a `StatusGotoResponse`. Only returned if the content type requested was not HTML. + :status 302 Found: + The client should go to the indicated location. Only returned if the content type requested was HTML. + :status 402 PaymentRequired: + The response is a `StatusUnpaidResponse`. + :status 403 Forbidden: + The ``h_contract`` (or the ``token`` for unclaimed orders) does not match the order + and we have no fulfillment URL in the contract. + :status 410 Gone: + The response is a `StatusGoneResponse`. + :status 404 Not found: + The merchant backend is unaware of the order. + :status 406 Not Acceptable: + The merchant backend could not load the template required to generate a reply in the desired format. (Likely HTML templates were not properly installed.) + + .. ts:def:: StatusPaidResponse + + interface StatusPaid { + // Was the payment refunded (even partially, via refund or abort)? + refunded: boolean; + + // Is any amount of the refund still waiting to be picked up (even partially) + refund_pending: boolean; + + // Amount that was refunded in total. + refund_amount: Amount; + } + + .. ts:def:: StatusGotoResponse + + interface StatusGotoResponse { + // The client should go to the fulfillment URL, it may be ready or + // might have some other interesting status. + fulfillment_url: string; + } + + .. ts:def:: StatusUnpaidResponse + + interface StatusUnpaidResponse { + // URI that the wallet must process to complete the payment. + taler_pay_uri: string; + + // Status URL, can be used as a redirect target for the browser + // to show the order QR code / trigger the wallet. + fulfillment_url?: string; + + // Alternative order ID which was paid for already in the same session. + // Only given if the same product was purchased before in the same session. + already_paid_order_id?: string; + } + + .. ts:def:: StatusGoneResponse + + // The client tried to access the order via the claim + // token (and not a valid h_contract), but the order can't be claimed + // anymore, as it is already paid. + interface StatusGoneResponse { + // Fulfillment URL for the order. + fulfillment_url: string; + } + + +Demonstrating payment +--------------------- + +In case a wallet has already paid for an order, this is a fast way of proving +to the merchant that the order was already paid. The alternative would be to +replay the original payment, but simply providing the merchant's signature +saves bandwidth and computation time. + +Demonstrating payment is useful in case a digital good was made available +only to clients with a particular session ID: if that session ID expired or +if the user is using a different client, demonstrating payment will allow +the user to regain access to the digital good without having to pay for it +again. + +.. http:post:: /orders/$ORDER_ID/paid + + Prove that the client previously paid for an order by providing + the merchant's signature from the `payment response <PaymentResponse>`. + Typically used by the customer's wallet if it receives a request for + payment for an order that it already paid. This is more compact then + re-transmitting the full payment details. + Note that this request does include the + usual ``h_contract`` argument to authenticate the wallet and + to allow the merchant to verify the signature before checking + with its own database. + + **Request:** + + The request must be a `paid request <PaidRequest>`. + + **Response:** + + :status 204 No content: + The merchant accepted the signature. + The ``frontend`` should now fulfill the contract. + Note that it is possible that refunds have been granted. + :status 400 Bad request: + Either the client request is malformed or some specific processing error + happened that may be the fault of the client as detailed in the JSON body + of the response. + :status 403 Forbidden: + The signature was not valid. + :status 404 Not found: + The merchant backend could not find the order or the instance + and thus cannot process the request. + :status 409 Conflict: + The provided contract hash does not match this order. + + .. ts:def:: PaidRequest + + interface PaidRequest { + // Signature on ``TALER_PaymentResponsePS`` with the public + // key of the merchant instance. + sig: EddsaSignature; + + // hash of the order's contract terms (this is used to authenticate the + // wallet/customer and to enable signature verification without + // database access). + h_contract: HashCode; + + // Session id for which the payment is proven. + session_id: string; + } + + +Aborting incomplete payments +---------------------------- + +In rare cases (such as a wallet restoring from an outdated backup) it is possible +that a wallet fails to complete a payment because it runs out of e-cash in the +middle of the process. The abort API allows the wallet to abort the payment for +such an incomplete payment and to regain control over the coins that were spent +so far. Aborts are not permitted for payments that completed. In contrast to +refunds, aborts do not require approval by the merchant because aborts always +are for incomplete payments for an order and never for established contracts. + + +.. _order-abort: +.. http:post:: /orders/$ORDER_ID/abort + + Abort paying for an order and obtain a refund for coins that + were already deposited as part of a failed payment. + + **Request:** + + The request must be an `abort request <AbortRequest>`. We force the wallet + to specify the affected coins as it may only request for a subset of the coins + (i.e. because the wallet knows that some were double-spent causing the failure). + Also we need to know the coins because there may be two wallets "competing" over + the same order and one wants to abort while the other still proceeds with the + payment. Here we need to again know which subset of the deposits to abort. + + **Response:** + + :status 200 OK: + The merchant accepted the request, and passed it on to the exchange. The body is a + a `merchant refund response <MerchantRefundResponse>`. Note that the exchange + MAY still have encountered errors in processing. Those will then be part of + the body. Wallets MUST carefully consider errors for each of the coins as + returned by the exchange. + :status 400 Bad request: + Either the client request is malformed or some specific processing error + happened that may be the fault of the client as detailed in the JSON body + of the response. + :status 403 Forbidden: + The ``h_contract`` does not match the $ORDER_ID. + :status 404 Not found: + The merchant backend could not find the order or the instance + and thus cannot process the abort request. + :status 408 Request Timeout: + The merchant backend took too long getting a response from the exchange. + The wallet SHOULD retry soon. + :status 412 Precondition Failed: + Aborting the payment is not allowed, as the original payment did succeed. + It is possible that a different wallet succeeded with the payment. This + wallet should thus try to refresh all of the coins involved in the payment. + :status 424 Failed Dependency: + The merchant's interaction with the exchange failed in some way. + The error from the exchange is included. + + The backend will return an `abort response <AbortResponse>`, which includes + verbatim the error codes received from the exchange's + :ref:`refund <exchange_refund>` API. The frontend should pass the replies verbatim to + the browser/wallet. + + .. ts:def:: AbortRequest + + interface AbortRequest { + + // hash of the order's contract terms (this is used to authenticate the + // wallet/customer in case $ORDER_ID is guessable). + h_contract: HashCode; + + // List of coins the wallet would like to see refunds for. + // (Should be limited to the coins for which the original + // payment succeeded, as far as the wallet knows.) + coins: AbortingCoin[]; + } + + .. ts:def:: AbortingCoin + + interface AbortingCoin { + // Public key of a coin for which the wallet is requesting an abort-related refund. + coin_pub: EddsaPublicKey; + + // The amount to be refunded (matches the original contribution) + contribution: Amount; + + // URL of the exchange this coin was withdrawn from. + exchange_url: string; + } + + + .. ts:def:: AbortResponse + + interface AbortResponse { + + // List of refund responses about the coins that the wallet + // requested an abort for. In the same order as the 'coins' + // from the original request. + // The rtransaction_id is implied to be 0. + refunds: MerchantAbortPayRefundStatus[]; + } + + .. ts:def:: MerchantAbortPayRefundStatus + + type MerchantAbortPayRefundStatus = + | MerchantAbortPayRefundSuccessStatus + | MerchantAbortPayRefundFailureStatus; + + .. ts:def:: MerchantAbortPayRefundFailureStatus + + // Details about why a refund failed. + interface MerchantAbortPayRefundFailureStatus { + // Used as tag for the sum type RefundStatus sum type. + type: "failure" + + // HTTP status of the exchange request, must NOT be 200. + exchange_status: Integer; + + // Taler error code from the exchange reply, if available. + exchange_code?: Integer; + + // If available, HTTP reply from the exchange. + exchange_reply?: Object; + } + + .. ts:def:: MerchantAbortPayRefundSuccessStatus + + // Additional details needed to verify the refund confirmation signature + // (``h_contract_terms`` and ``merchant_pub``) are already known + // to the wallet and thus not included. + interface MerchantAbortPayRefundSuccessStatus { + // Used as tag for the sum type MerchantCoinRefundStatus sum type. + type: "success" + + // HTTP status of the exchange request, 200 (integer) required for refund confirmations. + exchange_status: 200; + + // the EdDSA :ref:`signature` (binary-only) with purpose + // `TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND` using a current signing key of the + // exchange affirming the successful refund + exchange_sig: EddsaSignature; + + // public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKey; + } + + +Obtaining refunds +----------------- + +Refunds allow merchants to fully or partially restitute e-cash to a wallet, +for example because the merchant determined that it could not actually fulfill +the contract. Refunds must be approved by the merchant's business logic. + + +.. http:post:: /orders/$ORDER_ID/refund + + Obtain refunds for an order. After talking to the exchange, the refunds will + no longer be pending if processed successfully. + + **Request:** + + The request body is a `WalletRefundRequest` object. + + **Response:** + + :status 200 OK: + The response is a `WalletRefundResponse`. + :status 204 No content: + There are no refunds for the order. + :status 403 Forbidden: + The ``h_contract`` does not match the order. + :status 404 Not found: + The merchant backend is unaware of the order. + + .. ts:def:: WalletRefundRequest + + interface WalletRefundRequest { + // hash of the order's contract terms (this is used to authenticate the + // wallet/customer). + h_contract: HashCode; + } + + .. ts:def:: WalletRefundResponse + + interface WalletRefundResponse { + // Amount that was refunded in total. + refund_amount: Amount; + + // Successful refunds for this payment, empty array for none. + refunds: MerchantCoinRefundStatus[]; + + // Public key of the merchant. merchant_pub: EddsaPublicKey; - // List of the payment targets supported by this instance. Clients can - // specify the desired payment target in /order requests. Note that - // front-ends do not have to support wallets selecting payment targets. - payment_targets: string[]; + } - } + .. ts:def:: MerchantCoinRefundStatus + + type MerchantCoinRefundStatus = + | MerchantCoinRefundSuccessStatus + | MerchantCoinRefundFailureStatus; + + .. ts:def:: MerchantCoinRefundFailureStatus + + // Details about why a refund failed. + interface MerchantCoinRefundFailureStatus { + // Used as tag for the sum type RefundStatus sum type. + type: "failure"; + + // HTTP status of the exchange request, must NOT be 200. + exchange_status: Integer; + + // Taler error code from the exchange reply, if available. + exchange_code?: Integer; + + // If available, HTTP reply from the exchange. + exchange_reply?: Object; + + // Refund transaction ID. + rtransaction_id: Integer; + + // public key of a coin that was refunded + coin_pub: EddsaPublicKey; + + // Amount that was refunded, including refund fee charged by the exchange + // to the customer. + refund_amount: Amount; + } + + .. ts:def:: MerchantCoinRefundSuccessStatus + + // Additional details needed to verify the refund confirmation signature + // (``h_contract_terms`` and ``merchant_pub``) are already known + // to the wallet and thus not included. + interface MerchantCoinRefundSuccessStatus { + // Used as tag for the sum type MerchantCoinRefundStatus sum type. + type: "success"; + + // HTTP status of the exchange request, 200 (integer) required for refund confirmations. + exchange_status: 200; + + // the EdDSA :ref:`signature` (binary-only) with purpose + // `TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND` using a current signing key of the + // exchange affirming the successful refund + exchange_sig: EddsaSignature; + + // public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKey; + + // Refund transaction ID. + rtransaction_id: Integer; + + // public key of a coin that was refunded + coin_pub: EddsaPublicKey; + + // Amount that was refunded, including refund fee charged by the exchange + // to the customer. + refund_amount: Amount; + } + + +Picking up tips +--------------- + +Tips are a way for wallets to obtain e-cash from +a website. + +.. http:get:: /tips/$TIP_ID + + Handle request from wallet to provide details about a tip. + + This endpoint typically also supports requests with the "Accept" header + requesting "text/html". In this case, an HTML response suitable for + triggering the interaction with the wallet is returned. If the backend + installation does not include the required HTML templates, a 406 status + code is returned. + + **Response:** + + :status 200 OK: + A tip is being returned. The backend responds with a `TipInformation`. + :status 404 Not Found: + The tip identifier is unknown. + :status 406 Not Acceptable: + The merchant backend could not load the template required to generate a reply in the desired format. (Likely HTML templates were not properly installed.) + :status 410 Gone: + A tip has been fully claimed. The JSON reply still contains the `TipInformation`. + + .. ts:def:: TipInformation + interface TipInformation { + + // Exchange from which the tip will be withdrawn. Needed by the + // wallet to determine denominations, fees, etc. + exchange_url: string; + + // (remaining) amount of the tip (including fees). + tip_amount: Amount; + + // Timestamp indicating when the tip is set to expire (may be in the past). + // Note that tips that have expired MAY also result in a 404 response. + expiration: Timestamp; + } + + +.. http:post:: /tips/$TIP_ID/pickup + + Handle request from wallet to pick up a tip. + + **Request:** + + The request body is a `TipPickupRequest` object. + + **Response:** + + :status 200 OK: + A tip is being returned. The backend responds with a `TipResponse` + :status 401 Unauthorized: + The tip amount requested exceeds the tip. + :status 404 Not Found: + The tip identifier is unknown. + :status 409 Conflict: + Some of the denomination key hashes of the request do not match those currently available from the exchange (hence there is a conflict between what the wallet requests and what the merchant believes the exchange can provide). + :status 410 Gone: + The tip has expired. + + .. ts:def:: TipPickupRequest + + interface TipPickupRequest { + + // List of planches the wallet wants to use for the tip + planchets: PlanchetDetail[]; + } + + .. ts:def:: PlanchetDetail + + interface PlanchetDetail { + // Hash of the denomination's public key (hashed to reduce + // bandwidth consumption) + denom_pub_hash: HashCode; + + // coin's blinded public key + coin_ev: CoinEnvelope; + } + + .. ts:def:: TipResponse + + interface TipResponse { + + // Blind RSA signatures over the planchets. + // The order of the signatures matches the planchets list. + blind_sigs: BlindSignature[]; + } + + .. ts:def:: BlindSignature + + interface BlindSignature { + + // The (blind) RSA signature. Still needs to be unblinded. + blind_sig: BlindedRsaSignature; + } + + +------------------- +Instance management +------------------- + +Instances allow one merchant backend to be shared by multiple merchants. +Every backend must have at least one instance, typcially the "default" +instance setup before it can be used to manage inventory or process payments. + + +Setting up instances +-------------------- .. http:post:: /private/instances @@ -236,6 +911,51 @@ Dynamic Merchant Instances } +Inspecting instances +-------------------- + +.. _instances: +.. http:get:: /private/instances + + This is used to return the list of all the merchant instances + + **Response:** + + :status 200 OK: + The backend has successfully returned the list of instances stored. Returns + a `InstancesResponse`. + + .. ts:def:: InstancesResponse + + interface InstancesResponse { + // List of instances that are present in the backend (see `Instance`) + instances: Instance[]; + } + + The `Instance` object describes the instance registered with the backend. + It does not include the full details, only those that usually concern the frontend. + It has the following structure: + + .. ts:def:: Instance + + interface Instance { + // Merchant name corresponding to this instance. + name: string; + + // Merchant instance this response is about ($INSTANCE) + id: string; + + // Public key of the merchant/instance, in Crockford Base32 encoding. + merchant_pub: EddsaPublicKey; + + // List of the payment targets supported by this instance. Clients can + // specify the desired payment target in /order requests. Note that + // front-ends do not have to support wallets selecting payment targets. + payment_targets: string[]; + + } + + .. http:get:: /private/instances/$INSTANCE This is used to query a specific merchant instance. @@ -308,7 +1028,9 @@ Dynamic Merchant Instances active: boolean; } - + +Deleting instances +------------------ .. http:delete:: /private/instances/$INSTANCE @@ -349,47 +1071,31 @@ Taler merchant backend to process payments *without* using its inventory management. -.. http:get:: /private/products - - This is used to return the list of all items in the inventory. - - **Response:** - - :status 200 OK: - The backend has successfully returned the inventory. Returns - a `InventorySummaryResponse`. - - .. ts:def:: InventorySummaryResponse - - interface InventorySummaryResponse { - // List of products that are present in the inventory - products: InventoryEntry[]; - } - - The `InventoryEntry` object describes an item in the inventory. It has the following structure: +Adding products to the inventory +-------------------------------- - .. ts:def:: InventoryEntry +.. http:post:: /private/products - interface InventoryEntry { - // Product identifier, as found in the product. - product_id: string; + This is used to add a product to the inventory. - } + **Request:** + The request must be a `ProductAddDetail`. -.. http:get:: /private/products/$PRODUCT_ID + **Response:** - This is used to obtain detailed information about a product in the inventory. + :status 204 No content: + The backend has successfully expanded the inventory. + :status 409 Conflict: + The backend already knows a product with this product ID, but with different details. - **Response:** - :status 200 OK: - The backend has successfully returned the inventory. Returns - a `ProductDetail`. + .. ts:def:: ProductAddDetail - .. ts:def:: ProductDetail + interface ProductAddDetail { - interface ProductDetail { + // product ID to use. + product_id: string; // Human-readable product description. description: string; @@ -419,12 +1125,6 @@ management. // A value of -1 indicates "infinite" (i.e. for "electronic" books). total_stock: Integer; - // Number of units of the product that have already been sold. - total_sold: Integer; - - // Number of units of the product that were lost (spoiled, stolen, etc.) - total_lost: Integer; - // Identifies where the product is in stock. address: Location; @@ -434,28 +1134,33 @@ management. } -.. http:post:: /private/products - This is used to add a product to the inventory. +.. http:patch:: /private/products/$PRODUCT_ID + + This is used to update product details in the inventory. Note that the + ``total_stock`` and ``total_lost`` numbers MUST be greater or equal than + previous values (this design ensures idempotency). In case stocks were lost + but not sold, increment the ``total_lost`` number. All fields in the + request are optional, those that are not given are simply preserved (not + modified). Note that the ``description_i18n`` and ``taxes`` can only be + modified in bulk: if it is given, all translations must be provided, not + only those that changed. "never" should be used for the ``next_restock`` + timestamp to indicate no intention/possibility of restocking, while a time + of zero is used to indicate "unknown". **Request:** - The request must be a `ProductAddDetail`. + The request must be a `ProductPatchDetail`. **Response:** :status 204 No content: The backend has successfully expanded the inventory. - :status 409 Conflict: - The backend already knows a product with this product ID, but with different details. - .. ts:def:: ProductAddDetail - - interface ProductAddDetail { + .. ts:def:: ProductPatchDetail - // product ID to use. - product_id: string; + interface ProductPatchDetail { // Human-readable product description. description: string; @@ -485,6 +1190,9 @@ management. // A value of -1 indicates "infinite" (i.e. for "electronic" books). total_stock: Integer; + // Number of units of the product that were lost (spoiled, stolen, etc.) + total_lost: Integer; + // Identifies where the product is in stock. address: Location; @@ -493,34 +1201,50 @@ management. } +Inspecting inventory +-------------------- +.. http:get:: /private/products -.. http:patch:: /private/products/$PRODUCT_ID + This is used to return the list of all items in the inventory. - This is used to update product details in the inventory. Note that the - ``total_stock`` and ``total_lost`` numbers MUST be greater or equal than - previous values (this design ensures idempotency). In case stocks were lost - but not sold, increment the ``total_lost`` number. All fields in the - request are optional, those that are not given are simply preserved (not - modified). Note that the ``description_i18n`` and ``taxes`` can only be - modified in bulk: if it is given, all translations must be provided, not - only those that changed. "never" should be used for the ``next_restock`` - timestamp to indicate no intention/possibility of restocking, while a time - of zero is used to indicate "unknown". + **Response:** - **Request:** + :status 200 OK: + The backend has successfully returned the inventory. Returns + a `InventorySummaryResponse`. - The request must be a `ProductPatchDetail`. + .. ts:def:: InventorySummaryResponse - **Response:** + interface InventorySummaryResponse { + // List of products that are present in the inventory + products: InventoryEntry[]; + } - :status 204 No content: - The backend has successfully expanded the inventory. + The `InventoryEntry` object describes an item in the inventory. It has the following structure: + + .. ts:def:: InventoryEntry + interface InventoryEntry { + // Product identifier, as found in the product. + product_id: string; - .. ts:def:: ProductPatchDetail + } - interface ProductPatchDetail { + +.. http:get:: /private/products/$PRODUCT_ID + + This is used to obtain detailed information about a product in the inventory. + + **Response:** + + :status 200 OK: + The backend has successfully returned the inventory. Returns + a `ProductDetail`. + + .. ts:def:: ProductDetail + + interface ProductDetail { // Human-readable product description. description: string; @@ -550,6 +1274,9 @@ management. // A value of -1 indicates "infinite" (i.e. for "electronic" books). total_stock: Integer; + // Number of units of the product that have already been sold. + total_sold: Integer; + // Number of units of the product that were lost (spoiled, stolen, etc.) total_lost: Integer; @@ -562,6 +1289,8 @@ management. } +Reserving inventory +------------------- .. http:post:: /private/products/$PRODUCT_ID/lock @@ -609,6 +1338,8 @@ management. } +Removing products from inventory +-------------------------------- .. http:delete:: /private/products/$PRODUCT_ID @@ -629,6 +1360,15 @@ management. Payment processing ------------------ +To process Taler payments, a merchant must first setup an order with +the merchant backend. The order is then claimed by a wallet, and +paid by the wallet. The merchant can check the payment status of the +order. Once the order is paid, the merchant may (for a limited time) +grant refunds on the order. + +Creating orders +--------------- + .. _post-order: .. http:post:: /private/orders @@ -775,13 +1515,7 @@ Payment processing .. ts:def:: OutOfStockResponse interface OutOfStockResponse { - // Which items are out of stock? - missing_products: OutOfStockEntry; - } - - .. ts:def:: OutOfStockEntry - interface OutOfStockEntry { // Product ID of an out-of-stock item product_id: string; @@ -797,6 +1531,8 @@ Payment processing } +Inspecting orders +----------------- .. http:get:: /private/orders @@ -855,386 +1591,6 @@ Payment processing paid: boolean; } - - -.. http:post:: /orders/$ORDER_ID/claim - - Wallet claims ownership (via nonce) over an order. By claiming - an order, the wallet obtains the full contract terms, and thereby - implicitly also the hash of the contract terms it needs for the - other ``/public/`` APIs to authenticate itself as the wallet that - is indeed eligible to inspect this particular order's status. - - **Request:** - - The request must be a `ClaimRequest` - - .. ts:def:: ClaimRequest - - interface ClaimRequest { - // Nonce to identify the wallet that claimed the order. - nonce: string; - - // Token that authorizes the wallet to claim the order. - // *Optional* as the merchant may not have required it - // (``create_token`` set to ``false`` in `PostOrderRequest`). - token?: ClaimToken; - } - - **Response:** - - :status 200 OK: - The client has successfully claimed the order. - The response contains the :ref:`contract terms <contract-terms>`. - :status 404 Not found: - The backend is unaware of the instance or order. - :status 409 Conflict: - The someone else claimed the same order ID with different nonce before. - - .. ts:def:: ClaimResponse - - interface ClaimResponse { - // Contract terms of the claimed order - contract_terms: ContractTerms; - - // Signature by the merchant over the contract terms. - sig: EddsaSignature; - } - - -.. http:post:: /orders/$ORDER_ID/pay - - Pay for an order by giving a deposit permission for coins. Typically used by - the customer's wallet. Note that this request does not include the - usual ``h_contract`` argument to authenticate the wallet, as the hash of - the contract is implied by the signatures of the coins. Furthermore, this - API doesn't really return useful information about the order. - - **Request:** - - The request must be a `pay request <PayRequest>`. - - **Response:** - - :status 200 OK: - The exchange accepted all of the coins. - The body is a `payment response <PaymentResponse>`. - The ``frontend`` should now fulfill the contract. - Note that it is possible that refunds have been granted. - :status 400 Bad request: - Either the client request is malformed or some specific processing error - happened that may be the fault of the client as detailed in the JSON body - of the response. - :status 402 Payment required: - There used to be a sufficient payment, but due to refunds the amount effectively - paid is no longer sufficient. (If the amount is generally insufficient, we - return "406 Not Acceptable", only if this is because of refunds we return 402.) - :status 403 Forbidden: - One of the coin signatures was not valid. - :status 404 Not found: - The merchant backend could not find the order or the instance and thus cannot process the payment. - :status 406 Not Acceptable: - The payment is insufficient (sum is below the required total amount). - :status 408 Request Timeout: - The backend took too long to process the request. Likely the merchant's connection - to the exchange timed out. Try again. - :status 409 Conflict: - The exchange rejected the payment because a coin was already spent before. - The response will include the ``coin_pub`` for which the payment failed, - in addition to the response from the exchange to the ``/deposit`` request. - :status 410 Gone: - The offer has expired and is no longer available. - :status 412 Precondition Failed: - The given exchange is not acceptable for this merchant, as it is not in the - list of accepted exchanges and not audited by an approved auditor. - :status 424 Failed Dependency: - The merchant's interaction with the exchange failed in some way. - The client might want to try later again. - This includes failures like the denomination key of a coin not being - known to the exchange as far as the merchant can tell. - - The backend will return verbatim the error codes received from the exchange's - :ref:`deposit <deposit>` API. If the wallet made a mistake, like by - double-spending for example, the frontend should pass the reply verbatim to - the browser/wallet. If the payment was successful, the frontend MAY use - this to trigger some business logic. - - .. ts:def:: PaymentResponse - - interface PaymentResponse { - // Signature on ``TALER_PaymentResponsePS`` with the public - // key of the merchant instance. - sig: EddsaSignature; - - } - - .. ts:def:: PayRequest - - interface PayRequest { - // The coins used to make the payment. - coins: CoinPaySig[]; - - // The session for which the payment is made (or replayed). - // Only set for session-based payments. - session_id?: string; - - } - - .. ts:def:: CoinPaySig - - export interface CoinPaySig { - // Signature by the coin. - coin_sig: EddsaSignature; - - // Public key of the coin being spend. - coin_pub: EddsaPublicKey; - - // Signature made by the denomination public key. - ub_sig: RsaSignature; - - // The hash of the denomination public key associated with this coin. - h_denom: HashCode; - - // The amount that is subtracted from this coin with this payment. - contribution: Amount; - - // URL of the exchange this coin was withdrawn from. - exchange_url: string; - } - - -.. http:post:: /orders/$ORDER_ID/paid - - Prove that the client previously paid for an order by providing - the merchant's signature from the `payment response <PaymentResponse>`. - Typically used by the customer's wallet if it receives a request for - payment for an order that it already paid. This is more compact then - re-transmitting the full payment details. - Note that this request does include the - usual ``h_contract`` argument to authenticate the wallet and - to allow the merchant to verify the signature before checking - with its own database. - - **Request:** - - The request must be a `paid request <PaidRequest>`. - - **Response:** - - :status 204 No content: - The merchant accepted the signature. - The ``frontend`` should now fulfill the contract. - Note that it is possible that refunds have been granted. - :status 400 Bad request: - Either the client request is malformed or some specific processing error - happened that may be the fault of the client as detailed in the JSON body - of the response. - :status 403 Forbidden: - The signature was not valid. - :status 404 Not found: - The merchant backend could not find the order or the instance - and thus cannot process the request. - :status 409 Conflict: - The provided contract hash does not match this order. - - .. ts:def:: PaidRequest - - interface PaidRequest { - // Signature on ``TALER_PaymentResponsePS`` with the public - // key of the merchant instance. - sig: EddsaSignature; - - // hash of the order's contract terms (this is used to authenticate the - // wallet/customer and to enable signature verification without - // database access). - h_contract: HashCode; - - // Session id for which the payment is proven. - session_id: string; - } - -.. _order-abort: -.. http:post:: /orders/$ORDER_ID/abort - - Abort paying for an order and obtain a refund for coins that - were already deposited as part of a failed payment. - - **Request:** - - The request must be an `abort request <AbortRequest>`. We force the wallet - to specify the affected coins as it may only request for a subset of the coins - (i.e. because the wallet knows that some were double-spent causing the failure). - Also we need to know the coins because there may be two wallets "competing" over - the same order and one wants to abort while the other still proceeds with the - payment. Here we need to again know which subset of the deposits to abort. - - **Response:** - - :status 200 OK: - The merchant accepted the request, and passed it on to the exchange. The body is a - a `merchant refund response <MerchantRefundResponse>`. Note that the exchange - MAY still have encountered errors in processing. Those will then be part of - the body. Wallets MUST carefully consider errors for each of the coins as - returned by the exchange. - :status 400 Bad request: - Either the client request is malformed or some specific processing error - happened that may be the fault of the client as detailed in the JSON body - of the response. - :status 403 Forbidden: - The ``h_contract`` does not match the $ORDER_ID. - :status 404 Not found: - The merchant backend could not find the order or the instance - and thus cannot process the abort request. - :status 408 Request Timeout: - The merchant backend took too long getting a response from the exchange. - The wallet SHOULD retry soon. - :status 412 Precondition Failed: - Aborting the payment is not allowed, as the original payment did succeed. - It is possible that a different wallet succeeded with the payment. This - wallet should thus try to refresh all of the coins involved in the payment. - :status 424 Failed Dependency: - The merchant's interaction with the exchange failed in some way. - The error from the exchange is included. - - The backend will return an `abort response <AbortResponse>`, which includes - verbatim the error codes received from the exchange's - :ref:`refund <exchange_refund>` API. The frontend should pass the replies verbatim to - the browser/wallet. - - .. ts:def:: AbortRequest - - interface AbortRequest { - - // hash of the order's contract terms (this is used to authenticate the - // wallet/customer in case $ORDER_ID is guessable). - h_contract: HashCode; - - // List of coins the wallet would like to see refunds for. - // (Should be limited to the coins for which the original - // payment succeeded, as far as the wallet knows.) - coins: AbortingCoin[]; - } - - .. ts:def:: AbortingCoin - - interface AbortingCoin { - // Public key of a coin for which the wallet is requesting an abort-related refund. - coin_pub: EddsaPublicKey; - - // The amount to be refunded (matches the original contribution) - contribution: Amount; - - // URL of the exchange this coin was withdrawn from. - exchange_url: string; - } - - - .. ts:def:: AbortResponse - - interface AbortResponse { - - // List of refund responses about the coins that the wallet - // requested an abort for. In the same order as the 'coins' - // from the original request. - // The rtransaction_id is implied to be 0. - refunds: MerchantAbortPayRefundStatus[]; - } - - .. ts:def:: MerchantAbortPayRefundStatus - - type MerchantAbortPayRefundStatus = - | MerchantAbortPayRefundSuccessStatus - | MerchantAbortPayRefundFailureStatus; - - .. ts:def:: MerchantAbortPayRefundFailureStatus - - // Details about why a refund failed. - interface MerchantAbortPayRefundFailureStatus { - // Used as tag for the sum type RefundStatus sum type. - type: "failure" - - // HTTP status of the exchange request, must NOT be 200. - exchange_status: Integer; - - // Taler error code from the exchange reply, if available. - exchange_code?: Integer; - - // If available, HTTP reply from the exchange. - exchange_reply?: Object; - } - - .. ts:def:: MerchantAbortPayRefundSuccessStatus - - // Additional details needed to verify the refund confirmation signature - // (``h_contract_terms`` and ``merchant_pub``) are already known - // to the wallet and thus not included. - interface MerchantAbortPayRefundSuccessStatus { - // Used as tag for the sum type MerchantCoinRefundStatus sum type. - type: "success" - - // HTTP status of the exchange request, 200 (integer) required for refund confirmations. - exchange_status: 200; - - // the EdDSA :ref:`signature` (binary-only) with purpose - // `TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND` using a current signing key of the - // exchange affirming the successful refund - exchange_sig: EddsaSignature; - - // public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from /keys. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: EddsaPublicKey; - } - - -.. http:patch:: /private/orders/$ORDER_ID/forget - - Forget fields in an order's contract terms that the merchant no - longer needs. - - **Request:** - - The request must be a `forget request <ForgetRequest>`. The fields specified - must have been marked as forgettable when the contract was created. Fields in - the request that are not in the `contract terms <ContractTerms>` are ignored. - - A valid - JSON path is defined as a string beginning with ``$.`` that follows the dot - notation: ``$.wire_fee``, for example. The ``$`` represents the `contract terms <ContractTerms>` - object, and an identifier following a ``.`` represents the field of that - identifier belonging to the object preceding the dot. Arrays can be indexed - by an non-negative integer within brackets: ``$.products[1]``. An asterisk ``*`` - can be used to index an array as a wildcard, which expands the path into a - list of paths containing one path for - each valid array index: ``$.products[*].description``. For a path to be valid, - it must end with a reference to a field of an object (it cannot end with an - array index or wildcard). - - **Response:** - - :status 200 OK: - The merchant deleted the specified fields from the contract of - order $ORDER_ID. - :status 400 Bad request: - The request is malformed or one of the paths is invalid. - :status 404 Not found: - The merchant backend could not find the order or the instance - and thus cannot process the abort request. - :status 409 Conflict: - The request includes a field that was not marked as forgettable, so - the merchant cannot delete that field. - - .. ts:def:: ForgetRequest - - interface ForgetRequest { - - // Array of valid JSON paths to forgettable fields in the order's - // contract terms. - fields: string[]; - } - - .. http:get:: /private/orders/$ORDER_ID Merchant checks the payment status of an order. If the order exists but is not paid @@ -1404,101 +1760,62 @@ Payment processing coin_pub: CoinPublicKey; } + +Private order data cleanup +-------------------------- -.. http:get:: /orders/$ORDER_ID +Some orders may contain sensitive information that the merchant may not want +to retain after fulfillment, such as the customer's shipping address. By +initially labeling these order components as forgettable, the merchant can +later tell the backend to forget those details (without changing the hash of +the contract!) to minimize risks from information leakage. - Query the payment status of an order. This endpoint is for the wallet. - When the wallet goes to this URL and it is unpaid, - they will be prompted for payment. - This endpoint typically also supports requests with the "Accept" header - requesting "text/html". In this case, an HTML response suitable for - triggering the interaction with the wallet is returned, with ``timeout_ms`` - ignored (treated as zero). If the backend installation does not include the - required HTML templates, a 406 status code is returned. - In the case that the request was made with a claim token (even the wrong one) - and the order was claimed and paid, the server will redirect the client to - the fulfillment URL. This redirection will happen with a 302 status code - if the "Accept" header specified "text/html", and with a 202 status code - otherwise. +.. http:patch:: /private/orders/$ORDER_ID/forget + + Forget fields in an order's contract terms that the merchant no + longer needs. **Request:** - :query h_contract=HASH: hash of the order's contract terms (this is used to authenticate the wallet/customer in case $ORDER_ID is guessable). Required once an order was claimed. - :query token=TOKEN: *Optional*. Authorizes the request via the claim token that was returned in the `PostOrderResponse`. Used with unclaimed orders only. Whether token authorization is required is determined by the merchant when the frontend creates the order. - :query session_id=STRING: *Optional*. Session ID that the payment must be bound to. If not specified, the payment is not session-bound. - :query timeout_ms=NUMBER: *Optional.* If specified, the merchant backend will - wait up to ``timeout_ms`` milliseconds for completion of the payment before - sending the HTTP response. A client must never rely on this behavior, as the - merchant backend may return a response immediately. - :query refund=AMOUNT: *Optional*. Indicates that we are polling for a refund above the given AMOUNT. Only useful in combination with timeout. - :query await_refund_obtained=BOOLEAN: *Optional*. If set to "yes", poll for the order's pending refunds to be picked up. + The request must be a `forget request <ForgetRequest>`. The fields specified + must have been marked as forgettable when the contract was created. Fields in + the request that are not in the `contract terms <ContractTerms>` are ignored. + + A valid + JSON path is defined as a string beginning with ``$.`` that follows the dot + notation: ``$.wire_fee``, for example. The ``$`` represents the `contract terms <ContractTerms>` + object, and an identifier following a ``.`` represents the field of that + identifier belonging to the object preceding the dot. Arrays can be indexed + by an non-negative integer within brackets: ``$.products[1]``. An asterisk ``*`` + can be used to index an array as a wildcard, which expands the path into a + list of paths containing one path for + each valid array index: ``$.products[*].description``. For a path to be valid, + it must end with a reference to a field of an object (it cannot end with an + array index or wildcard). **Response:** :status 200 OK: - The response is a `StatusPaidResponse`. - :status 202 Accepted: - The response is a `StatusGotoResponse`. Only returned if the content type requested was not HTML. - :status 302 Found: - The client should go to the indicated location. Only returned if the content type requested was HTML. - :status 402 PaymentRequired: - The response is a `StatusUnpaidResponse`. - :status 403 Forbidden: - The ``h_contract`` (or the ``token`` for unclaimed orders) does not match the order - and we have no fulfillment URL in the contract. - :status 410 Gone: - The response is a `StatusGoneResponse`. + The merchant deleted the specified fields from the contract of + order $ORDER_ID. + :status 400 Bad request: + The request is malformed or one of the paths is invalid. :status 404 Not found: - The merchant backend is unaware of the order. - :status 406 Not Acceptable: - The merchant backend could not load the template required to generate a reply in the desired format. (Likely HTML templates were not properly installed.) - - .. ts:def:: StatusPaidResponse - - interface StatusPaid { - // Was the payment refunded (even partially, via refund or abort)? - refunded: boolean; - - // Is any amount of the refund still waiting to be picked up (even partially) - refund_pending: boolean; - - // Amount that was refunded in total. - refund_amount: Amount; - } - - .. ts:def:: StatusGotoResponse - - interface StatusGotoResponse { - // The client should go to the fulfillment URL, it may be ready or - // might have some other interesting status. - fulfillment_url: string; - } - - .. ts:def:: StatusUnpaidResponse - - interface StatusUnpaidResponse { - // URI that the wallet must process to complete the payment. - taler_pay_uri: string; - - // Status URL, can be used as a redirect target for the browser - // to show the order QR code / trigger the wallet. - fulfillment_url?: string; + The merchant backend could not find the order or the instance + and thus cannot process the abort request. + :status 409 Conflict: + The request includes a field that was not marked as forgettable, so + the merchant cannot delete that field. - // Alternative order ID which was paid for already in the same session. - // Only given if the same product was purchased before in the same session. - already_paid_order_id?: string; - } + .. ts:def:: ForgetRequest - .. ts:def:: StatusGoneResponse + interface ForgetRequest { - // The client tried to access the order via the claim - // token (and not a valid h_contract), but the order can't be claimed - // anymore, as it is already paid. - interface StatusGoneResponse { - // Fulfillment URL for the order. - fulfillment_url: string; + // Array of valid JSON paths to forgettable fields in the order's + // contract terms. + fields: string[]; } @@ -1572,119 +1889,18 @@ Giving Refunds } -.. http:post:: /orders/$ORDER_ID/refund - - Obtain refunds for an order. After talking to the exchange, the refunds will - no longer be pending if processed successfully. - - **Request:** - - The request body is a `WalletRefundRequest` object. - - **Response:** - - :status 200 OK: - The response is a `WalletRefundResponse`. - :status 204 No content: - There are no refunds for the order. - :status 403 Forbidden: - The ``h_contract`` does not match the order. - :status 404 Not found: - The merchant backend is unaware of the order. - - .. ts:def:: WalletRefundRequest - - interface WalletRefundRequest { - // hash of the order's contract terms (this is used to authenticate the - // wallet/customer). - h_contract: HashCode; - } - - .. ts:def:: WalletRefundResponse - - interface WalletRefundResponse { - // Amount that was refunded in total. - refund_amount: Amount; - - // Successful refunds for this payment, empty array for none. - refunds: MerchantCoinRefundStatus[]; - - // Public key of the merchant. - merchant_pub: EddsaPublicKey; - - } - - .. ts:def:: MerchantCoinRefundStatus - - type MerchantCoinRefundStatus = - | MerchantCoinRefundSuccessStatus - | MerchantCoinRefundFailureStatus; - - .. ts:def:: MerchantCoinRefundFailureStatus - - // Details about why a refund failed. - interface MerchantCoinRefundFailureStatus { - // Used as tag for the sum type RefundStatus sum type. - type: "failure"; - - // HTTP status of the exchange request, must NOT be 200. - exchange_status: Integer; - - // Taler error code from the exchange reply, if available. - exchange_code?: Integer; - - // If available, HTTP reply from the exchange. - exchange_reply?: Object; - - // Refund transaction ID. - rtransaction_id: Integer; - - // public key of a coin that was refunded - coin_pub: EddsaPublicKey; - - // Amount that was refunded, including refund fee charged by the exchange - // to the customer. - refund_amount: Amount; - } - - .. ts:def:: MerchantCoinRefundSuccessStatus - - // Additional details needed to verify the refund confirmation signature - // (``h_contract_terms`` and ``merchant_pub``) are already known - // to the wallet and thus not included. - interface MerchantCoinRefundSuccessStatus { - // Used as tag for the sum type MerchantCoinRefundStatus sum type. - type: "success"; - - // HTTP status of the exchange request, 200 (integer) required for refund confirmations. - exchange_status: 200; - - // the EdDSA :ref:`signature` (binary-only) with purpose - // `TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND` using a current signing key of the - // exchange affirming the successful refund - exchange_sig: EddsaSignature; - - // public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from /keys. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: EddsaPublicKey; - - // Refund transaction ID. - rtransaction_id: Integer; - - // public key of a coin that was refunded - coin_pub: EddsaPublicKey; +----------------------- +Tracking Wire Transfers +----------------------- - // Amount that was refunded, including refund fee charged by the exchange - // to the customer. - refund_amount: Amount; - } +This API is used by merchants that want to track the payments from the +exchange to be sure that they have been paid on time. By telling the merchant +backend about all incoming wire transfers, the backend can detect if an +exchange failed to perform a wire transfer that was due. ------------------------- -Tracking Wire Transfers ------------------------- +Informing the backend about incoming wire transfers +--------------------------------------------------- .. http:post:: /private/transfers @@ -1822,7 +2038,7 @@ Tracking Wire Transfers // Master public key of the exchange master_pub: EddsaPublicKey; - } + } .. ts:def:: TrackTransferConflictDetails @@ -1906,6 +2122,8 @@ Tracking Wire Transfers } +Querying known wire transfers +----------------------------- .. http:get:: /private/transfers @@ -1980,12 +2198,32 @@ Tracking Wire Transfers } - - -------------------- -Giving Customer Tips +Backend: Giving tips -------------------- +Tips are a way for websites to give small amounts of e-cash to visitors (for +example as a financial reward for providing information or watching +advertisements). Tips are non-contractual: neither merchant nor consumer +have any contractual information about the other party as a result of the +tip. + + +Create reserve +-------------- + +Reserves are basically funds a merchant has provided +to an exchange for a tipping campaign. Each reserve +has a limited lifetime (say 2--4 weeks). Any funds +not used to tip customers will automatically be wired +back from the exchange to the originating account. + +To begin tipping, a merchant must tell the backend +to setup a reserve. The backend will return a +reserve public key which must be used as the wire +transfer subject when wiring the tipping campaign +funds to the exchange. + .. _tips: .. http:post:: /private/reserves @@ -2088,6 +2326,9 @@ Giving Customer Tips active: boolean; } + +Query funds remaining +--------------------- .. http:get:: /private/reserves/$RESERVE_PUB @@ -2157,6 +2398,9 @@ Giving Customer Tips } +Authorizing tips +---------------- + .. http:post:: /private/reserves/$RESERVE_PUB/authorize-tip Authorize creation of a tip from the given reserve. @@ -2228,6 +2472,9 @@ Giving Customer Tips in all of the reserves of the instance. +Deleting reserves +----------------- + .. http:delete:: /private/reserves/$RESERVE_PUB Delete information about a reserve. Fails if the reserve still has @@ -2250,6 +2497,8 @@ Giving Customer Tips The backend refuses to delete the reserve (committed tips awaiting pickup). +Checking tip status +------------------- .. http:get:: /private/tips/$TIP_ID @@ -2303,46 +2552,6 @@ Giving Customer Tips } - -.. http:get:: /tips/$TIP_ID - - Handle request from wallet to provide details about a tip. - - This endpoint typically also supports requests with the "Accept" header - requesting "text/html". In this case, an HTML response suitable for - triggering the interaction with the wallet is returned. If the backend - installation does not include the required HTML templates, a 406 status - code is returned. - - **Response:** - - :status 200 OK: - A tip is being returned. The backend responds with a `TipInformation`. - :status 404 Not Found: - The tip identifier is unknown. - :status 406 Not Acceptable: - The merchant backend could not load the template required to generate a reply in the desired format. (Likely HTML templates were not properly installed.) - :status 410 Gone: - A tip has been fully claimed. The JSON reply still contains the `TipInformation`. - - .. ts:def:: TipInformation - - interface TipInformation { - - // Exchange from which the tip will be withdrawn. Needed by the - // wallet to determine denominations, fees, etc. - exchange_url: string; - - // (remaining) amount of the tip (including fees). - tip_amount: Amount; - - // Timestamp indicating when the tip is set to expire (may be in the past). - // Note that tips that have expired MAY also result in a 404 response. - expiration: Timestamp; - } - - - .. http:get:: /private/tips Return the list of all tips. @@ -2385,68 +2594,17 @@ Giving Customer Tips -.. http:post:: /tips/$TIP_ID/pickup - - Handle request from wallet to pick up a tip. - - **Request:** - - The request body is a `TipPickupRequest` object. - - **Response:** - - :status 200 OK: - A tip is being returned. The backend responds with a `TipResponse` - :status 401 Unauthorized: - The tip amount requested exceeds the tip. - :status 404 Not Found: - The tip identifier is unknown. - :status 409 Conflict: - Some of the denomination key hashes of the request do not match those currently available from the exchange (hence there is a conflict between what the wallet requests and what the merchant believes the exchange can provide). - :status 410 Gone: - The tip has expired. - - .. ts:def:: TipPickupRequest - - interface TipPickupRequest { - - // List of planches the wallet wants to use for the tip - planchets: PlanchetDetail[]; - } - - .. ts:def:: PlanchetDetail - - interface PlanchetDetail { - // Hash of the denomination's public key (hashed to reduce - // bandwidth consumption) - denom_pub_hash: HashCode; - - // coin's blinded public key - coin_ev: CoinEnvelope; - } - - .. ts:def:: TipResponse - - interface TipResponse { - - // Blind RSA signatures over the planchets. - // The order of the signatures matches the planchets list. - blind_sigs: BlindSignature[]; - } - - .. ts:def:: BlindSignature - - interface BlindSignature { - - // The (blind) RSA signature. Still needs to be unblinded. - blind_sig: BlindedRsaSignature; - } - - ------------------ The Contract Terms ------------------ +This section describes the overall structure of +the contract terms that are the foundation for +Taler payments. + +FIXME: the "forgettable" attribute is not +properly specified here! + .. _contract-terms: The contract terms must have the following structure: |