summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-10-31 16:08:52 +0100
committerChristian Grothoff <christian@grothoff.org>2020-10-31 16:09:06 +0100
commitf82d5092cdc8fa3a123404f680b43a937cc6ef11 (patch)
tree54b3a89348b5921efcf753890b375ad9d31f72d2 /core
parent83caabf7abca7c89230300a02ce5d980ab9ddf56 (diff)
downloaddocs-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.rst9
-rw-r--r--core/api-exchange.rst28
-rw-r--r--core/api-merchant.rst1706
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: