[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-docs] branch master updated: restructure merchant API documentati
From: |
gnunet |
Subject: |
[taler-docs] branch master updated: restructure merchant API documentation (#6492) |
Date: |
Sat, 31 Oct 2020 16:09:09 +0100 |
This is an automated email from the git hooks/post-receive script.
grothoff pushed a commit to branch master
in repository docs.
The following commit(s) were added to refs/heads/master by this push:
new f82d509 restructure merchant API documentation (#6492)
f82d509 is described below
commit f82d5092cdc8fa3a123404f680b43a937cc6ef11
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sat Oct 31 16:08:52 2020 +0100
restructure merchant API documentation (#6492)
---
core/api-common.rst | 9 +
core/api-exchange.rst | 28 +-
core/api-merchant.rst | 2374 ++++++++++++++++++++++++---------------------
taler-merchant-manual.rst | 6 +-
4 files changed, 1291 insertions(+), 1126 deletions(-)
diff --git a/core/api-common.rst b/core/api-common.rst
index 1364ae5..12f4a36 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 bc6a98d..8b5c43c 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 809bcbf..53b6424 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,1177 +66,1530 @@ 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
+-----------------
- **Response:**
+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.
- :status 200 OK:
- The backend has successfully returned the list of instances stored. Returns
- a `InstancesResponse`.
+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).
- .. ts:def:: InstancesResponse
- interface InstancesResponse {
- // List of instances that are present in the backend (see `Instance`)
- instances: Instance[];
- }
+.. http:post:: /orders/$ORDER_ID/claim
- 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:
+ 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.
- .. ts:def:: Instance
+ **Request:**
- interface Instance {
- // Merchant name corresponding to this instance.
- name: string;
+ The request must be a `ClaimRequest`
- // Merchant instance this response is about ($INSTANCE)
- id: string;
+ .. ts:def:: ClaimRequest
- // Public key of the merchant/instance, in Crockford Base32 encoding.
- merchant_pub: EddsaPublicKey;
+ interface ClaimRequest {
+ // Nonce to identify the wallet that claimed the order.
+ nonce: string;
- // 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[];
+ // 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
-.. http:post:: /private/instances
+ interface ClaimResponse {
+ // Contract terms of the claimed order
+ contract_terms: ContractTerms;
- This request will be used to create a new merchant instance in the backend.
+ // Signature by the merchant over the contract terms.
+ sig: EddsaSignature;
+ }
+
+Making the payment
+------------------
+
+.. 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 `InstanceConfigurationMessage`.
+ The request must be a `pay request <PayRequest>`.
**Response:**
- :status 204 No content:
- The backend has successfully created the instance.
+ :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:
- This instance already exists, but with other configuration options.
- Use "PATCH" to update an instance configuration.
+ 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.
- .. ts:def:: InstanceConfigurationMessage
+ 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.
- interface InstanceConfigurationMessage {
- // The URI where the wallet will send coins. A merchant may have
- // multiple accounts, thus this is an array. Note that by
- // removing URIs from this list the respective account is set to
- // inactive and thus unavailable for new contracts, but preserved
- // in the database as existing offers and contracts may still refer
- // to it.
- payto_uris: string[];
+ .. ts:def:: PaymentResponse
- // Name of the merchant instance to create (will become $INSTANCE).
- id: string;
+ interface PaymentResponse {
+ // Signature on ``TALER_PaymentResponsePS`` with the public
+ // key of the merchant instance.
+ sig: EddsaSignature;
- // Merchant name corresponding to this instance.
- name: string;
+ }
- // The merchant's physical address (to be put into contracts).
- address: Location;
+ .. ts:def:: PayRequest
- // The jurisdiction under which the merchant conducts its business
- // (to be put into contracts).
- jurisdiction: Location;
+ interface PayRequest {
+ // The coins used to make the payment.
+ coins: CoinPaySig[];
- // Maximum wire fee this instance is willing to pay.
- // Can be overridden by the frontend on a per-order basis.
- default_max_wire_fee: Amount;
+ // The session for which the payment is made (or replayed).
+ // Only set for session-based payments.
+ session_id?: string;
- // Default factor for wire fee amortization calculations.
- // Can be overridden by the frontend on a per-order basis.
- default_wire_fee_amortization: Integer;
+ }
- // Maximum deposit fee (sum over all coins) this instance is willing to
pay.
- // Can be overridden by the frontend on a per-order basis.
- default_max_deposit_fee: Amount;
+ .. ts:def:: CoinPaySig
- // If the frontend does NOT specify an execution date, how long should
- // we tell the exchange to wait to aggregate transactions before
- // executing the wire transfer? This delay is added to the current
- // time when we generate the advisory execution time for the exchange.
- default_wire_transfer_delay: RelativeTime;
+ export interface CoinPaySig {
+ // Signature by the coin.
+ coin_sig: EddsaSignature;
- // If the frontend does NOT specify a payment deadline, how long should
- // offers we make be valid by default?
- default_pay_delay: RelativeTime;
+ // 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:patch:: /private/instances/$INSTANCE
+.. http:get:: /orders/$ORDER_ID
- Update the configuration of a merchant instance.
+ 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.
- **Request**
+ 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.
- The request must be a `InstanceReconfigurationMessage`.
- Removing an existing payto_uri deactivates
- the account (it will no longer be used for future contracts).
+ **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 204 No content:
- The backend has successfully created the instance.
+ :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:
- This instance is unknown and thus cannot be reconfigured.
+ 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:: InstanceReconfigurationMessage
+ .. ts:def:: StatusPaidResponse
- interface InstanceReconfigurationMessage {
- // The URI where the wallet will send coins. A merchant may have
- // multiple accounts, thus this is an array. Note that by
- // removing URIs from this list
- payto_uris: string[];
+ interface StatusPaid {
+ // Was the payment refunded (even partially, via refund or abort)?
+ refunded: boolean;
- // Merchant name corresponding to this instance.
- name: string;
+ // Is any amount of the refund still waiting to be picked up (even
partially)
+ refund_pending: boolean;
- // The merchant's physical address (to be put into contracts).
- address: Location;
+ // Amount that was refunded in total.
+ refund_amount: Amount;
+ }
- // The jurisdiction under which the merchant conducts its business
- // (to be put into contracts).
- jurisdiction: Location;
-
- // Maximum wire fee this instance is willing to pay.
- // Can be overridden by the frontend on a per-order basis.
- default_max_wire_fee: Amount;
+ .. ts:def:: StatusGotoResponse
- // Default factor for wire fee amortization calculations.
- // Can be overridden by the frontend on a per-order basis.
- default_wire_fee_amortization: Integer;
+ interface StatusGotoResponse {
+ // The client should go to the fulfillment URL, it may be ready or
+ // might have some other interesting status.
+ fulfillment_url: string;
+ }
- // Maximum deposit fee (sum over all coins) this instance is willing to
pay.
- // Can be overridden by the frontend on a per-order basis.
- default_max_deposit_fee: Amount;
+ .. ts:def:: StatusUnpaidResponse
- // If the frontend does NOT specify an execution date, how long should
- // we tell the exchange to wait to aggregate transactions before
- // executing the wire transfer? This delay is added to the current
- // time when we generate the advisory execution time for the exchange.
- default_wire_transfer_delay: RelativeTime;
+ interface StatusUnpaidResponse {
+ // URI that the wallet must process to complete the payment.
+ taler_pay_uri: string;
- // If the frontend does NOT specify a payment deadline, how long should
- // offers we make be valid by default?
- default_pay_delay: RelativeTime;
+ // 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
-.. http:get:: /private/instances/$INSTANCE
-
- This is used to query a specific merchant instance.
-
- **Response:**
+ // 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;
+ }
- :status 200 OK:
- The backend has successfully returned the list of instances stored. Returns
- a `QueryInstancesResponse`.
+
+Demonstrating payment
+---------------------
- .. ts:def:: QueryInstancesResponse
+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.
- interface QueryInstancesResponse {
- // The URI where the wallet will send coins. A merchant may have
- // multiple accounts, thus this is an array.
- accounts: MerchantAccount[];
+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.
- // Merchant name corresponding to this instance.
- name: string;
+.. http:post:: /orders/$ORDER_ID/paid
- // Public key of the merchant/instance, in Crockford Base32 encoding.
- merchant_pub: EddsaPublicKey;
+ 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.
- // The merchant's physical address (to be put into contracts).
- address: Location;
+ **Request:**
- // The jurisdiction under which the merchant conducts its business
- // (to be put into contracts).
- jurisdiction: Location;
+ The request must be a `paid request <PaidRequest>`.
- // Maximum wire fee this instance is willing to pay.
- // Can be overridden by the frontend on a per-order basis.
- default_max_wire_fee: Amount;
+ **Response:**
- // Default factor for wire fee amortization calculations.
- // Can be overridden by the frontend on a per-order basis.
- default_wire_fee_amortization: Integer;
+ :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.
- // Maximum deposit fee (sum over all coins) this instance is willing to
pay.
- // Can be overridden by the frontend on a per-order basis.
- default_max_deposit_fee: Amount;
+ .. ts:def:: PaidRequest
- // If the frontend does NOT specify an execution date, how long should
- // we tell the exchange to wait to aggregate transactions before
- // executing the wire transfer? This delay is added to the current
- // time when we generate the advisory execution time for the exchange.
- default_wire_transfer_delay: RelativeTime;
+ interface PaidRequest {
+ // Signature on ``TALER_PaymentResponsePS`` with the public
+ // key of the merchant instance.
+ sig: EddsaSignature;
- // If the frontend does NOT specify a payment deadline, how long should
- // offers we make be valid by default?
- default_pay_deadline: RelativeTime;
+ // 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;
}
- .. ts:def:: MerchantAccount
-
- interface MerchantAccount {
-
- // payto:// URI of the account.
- payto_uri: string;
-
- // Hash over the wire details (including over the salt)
- h_wire: HashCode;
-
- // salt used to compute h_wire
- salt: HashCode;
-
- // true if this account is active,
- // false if it is historic.
- active: boolean;
- }
+
+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.
-.. http:delete:: /private/instances/$INSTANCE
+.. _order-abort:
+.. http:post:: /orders/$ORDER_ID/abort
- This request will be used to delete (permanently disable)
- or purge merchant instance in the backend. Purging will
- delete all offers and payments associated with the instance,
- while disabling (the default) only deletes the private key
- and makes the instance unusable for new orders or payments.
+ Abort paying for an order and obtain a refund for coins that
+ were already deposited as part of a failed payment.
**Request:**
- :query purge: *Optional*. If set to YES, the instance will be fully
- deleted. Otherwise only the private key would be deleted.
+ 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**
+ **Response:**
- :status 204 No content:
- The backend has successfully removed the instance. The body is empty.
+ :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 instance is unknown to the backend.
- :status 409 Conflict:
- The instance cannot be deleted because it has pending offers, or
- the instance cannot be purged because it has successfully processed
- payments that have not passed the TAX_RECORD_EXPIRATION time.
- The latter case only applies if ``purge`` was set.
-
-
---------------------
-Inventory management
---------------------
+ 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.
-.. _inventory:
+ 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.
-Inventory management is an *optional* backend feature that can be used to
-manage limited stocks of products and to auto-complete product descriptions in
-contracts (such that the frontends have to do less work). You can use the
-Taler merchant backend to process payments *without* using its inventory
-management.
+ .. ts:def:: AbortRequest
+ interface AbortRequest {
-.. http:get:: /private/products
+ // hash of the order's contract terms (this is used to authenticate the
+ // wallet/customer in case $ORDER_ID is guessable).
+ h_contract: HashCode;
- This is used to return the list of all items in the inventory.
+ // 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[];
+ }
- **Response:**
+ .. ts:def:: AbortingCoin
- :status 200 OK:
- The backend has successfully returned the inventory. Returns
- a `InventorySummaryResponse`.
+ interface AbortingCoin {
+ // Public key of a coin for which the wallet is requesting an
abort-related refund.
+ coin_pub: EddsaPublicKey;
- .. ts:def:: InventorySummaryResponse
+ // The amount to be refunded (matches the original contribution)
+ contribution: Amount;
- interface InventorySummaryResponse {
- // List of products that are present in the inventory
- products: InventoryEntry[];
+ // URL of the exchange this coin was withdrawn from.
+ exchange_url: string;
}
- The `InventoryEntry` object describes an item in the inventory. It has the
following structure:
- .. ts:def:: InventoryEntry
+ .. ts:def:: AbortResponse
- interface InventoryEntry {
- // Product identifier, as found in the product.
- product_id: string;
+ 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
-.. http:get:: /private/products/$PRODUCT_ID
-
- This is used to obtain detailed information about a product in the inventory.
+ type MerchantAbortPayRefundStatus =
+ | MerchantAbortPayRefundSuccessStatus
+ | MerchantAbortPayRefundFailureStatus;
- **Response:**
+ .. ts:def:: MerchantAbortPayRefundFailureStatus
- :status 200 OK:
- The backend has successfully returned the inventory. Returns
- a `ProductDetail`.
-
- .. ts:def:: ProductDetail
-
- interface ProductDetail {
-
- // Human-readable product description.
- description: string;
+ // Details about why a refund failed.
+ interface MerchantAbortPayRefundFailureStatus {
+ // Used as tag for the sum type RefundStatus sum type.
+ type: "failure"
- // Map from IETF BCP 47 language tags to localized descriptions
- description_i18n: { [lang_tag: string]: string };
+ // HTTP status of the exchange request, must NOT be 200.
+ exchange_status: Integer;
- // unit in which the product is measured (liters, kilograms, packages,
etc.)
- unit: string;
+ // Taler error code from the exchange reply, if available.
+ exchange_code?: Integer;
- // The price for one ``unit`` of the product. Zero is used
- // to imply that this product is not sold separately, or
- // that the price is not fixed, and must be supplied by the
- // front-end. If non-zero, this price MUST include applicable
- // taxes.
- price: Amount;
+ // If available, HTTP reply from the exchange.
+ exchange_reply?: Object;
+ }
- // An optional base64-encoded product image
- image: ImageDataUrl;
+ .. ts:def:: MerchantAbortPayRefundSuccessStatus
- // a list of taxes paid by the merchant for one unit of this product
- taxes: Tax[];
+ // 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"
- // Number of units of the product in stock in sum in total,
- // including all existing sales ever. Given in product-specific
- // units.
- // A value of -1 indicates "infinite" (i.e. for "electronic" books).
- total_stock: Integer;
+ // HTTP status of the exchange request, 200 (integer) required for
refund confirmations.
+ exchange_status: 200;
- // Number of units of the product that have already been sold.
- total_sold: Integer;
+ // 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;
- // Number of units of the product that were lost (spoiled, stolen, etc.)
- total_lost: Integer;
+ // 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;
+ }
- // Identifies where the product is in stock.
- address: Location;
- // Identifies when we expect the next restocking to happen.
- next_restock?: Timestamp;
+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:: /private/products
+.. http:post:: /orders/$ORDER_ID/refund
- This is used to add a product to the inventory.
+ Obtain refunds for an order. After talking to the exchange, the refunds will
+ no longer be pending if processed successfully.
**Request:**
- The request must be a `ProductAddDetail`.
+ The request body is a `WalletRefundRequest` object.
**Response:**
+ :status 200 OK:
+ The response is a `WalletRefundResponse`.
: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.
+ 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
- .. ts:def:: ProductAddDetail
+ interface WalletRefundRequest {
+ // hash of the order's contract terms (this is used to authenticate the
+ // wallet/customer).
+ h_contract: HashCode;
+ }
- interface ProductAddDetail {
+ .. ts:def:: WalletRefundResponse
- // product ID to use.
- product_id: string;
+ interface WalletRefundResponse {
+ // Amount that was refunded in total.
+ refund_amount: Amount;
- // Human-readable product description.
- description: string;
+ // Successful refunds for this payment, empty array for none.
+ refunds: MerchantCoinRefundStatus[];
- // Map from IETF BCP 47 language tags to localized descriptions
- description_i18n: { [lang_tag: string]: string };
+ // Public key of the merchant.
+ merchant_pub: EddsaPublicKey;
- // unit in which the product is measured (liters, kilograms, packages,
etc.)
- unit: string;
+ }
- // The price for one ``unit`` of the product. Zero is used
- // to imply that this product is not sold separately, or
- // that the price is not fixed, and must be supplied by the
- // front-end. If non-zero, this price MUST include applicable
- // taxes.
- price: Amount;
+ .. ts:def:: MerchantCoinRefundStatus
- // An optional base64-encoded product image
- image: ImageDataUrl;
+ type MerchantCoinRefundStatus =
+ | MerchantCoinRefundSuccessStatus
+ | MerchantCoinRefundFailureStatus;
- // a list of taxes paid by the merchant for one unit of this product
- taxes: Tax[];
+ .. ts:def:: MerchantCoinRefundFailureStatus
- // Number of units of the product in stock in sum in total,
- // including all existing sales ever. Given in product-specific
- // units.
- // A value of -1 indicates "infinite" (i.e. for "electronic" books).
- total_stock: Integer;
+ // Details about why a refund failed.
+ interface MerchantCoinRefundFailureStatus {
+ // Used as tag for the sum type RefundStatus sum type.
+ type: "failure";
- // Identifies where the product is in stock.
- address: Location;
+ // HTTP status of the exchange request, must NOT be 200.
+ exchange_status: Integer;
- // Identifies when we expect the next restocking to happen.
- next_restock?: Timestamp;
+ // 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;
-.. http:patch:: /private/products/$PRODUCT_ID
+ // Amount that was refunded, including refund fee charged by the exchange
+ // to the customer.
+ refund_amount: Amount;
+ }
- 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".
+ .. ts:def:: MerchantCoinRefundSuccessStatus
- **Request:**
+ // 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";
- The request must be a `ProductPatchDetail`.
+ // HTTP status of the exchange request, 200 (integer) required for
refund confirmations.
+ exchange_status: 200;
- **Response:**
+ // 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;
- :status 204 No content:
- The backend has successfully expanded the inventory.
+ // 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;
- .. ts:def:: ProductPatchDetail
+ // public key of a coin that was refunded
+ coin_pub: EddsaPublicKey;
- interface ProductPatchDetail {
+ // Amount that was refunded, including refund fee charged by the exchange
+ // to the customer.
+ refund_amount: Amount;
+ }
- // Human-readable product description.
- description: string;
- // Map from IETF BCP 47 language tags to localized descriptions
- description_i18n: { [lang_tag: string]: string };
+Picking up tips
+---------------
- // unit in which the product is measured (liters, kilograms, packages,
etc.)
- unit: string;
+Tips are a way for wallets to obtain e-cash from
+a website.
- // The price for one ``unit`` of the product. Zero is used
- // to imply that this product is not sold separately, or
- // that the price is not fixed, and must be supplied by the
- // front-end. If non-zero, this price MUST include applicable
- // taxes.
- price: Amount;
+.. http:get:: /tips/$TIP_ID
- // An optional base64-encoded product image
- image: ImageDataUrl;
+ Handle request from wallet to provide details about a tip.
- // a list of taxes paid by the merchant for one unit of this product
- taxes: Tax[];
+ 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.
- // Number of units of the product in stock in sum in total,
- // including all existing sales ever. Given in product-specific
- // units.
- // A value of -1 indicates "infinite" (i.e. for "electronic" books).
- total_stock: Integer;
+ **Response:**
- // Number of units of the product that were lost (spoiled, stolen, etc.)
- total_lost: Integer;
+ :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`.
- // Identifies where the product is in stock.
- address: Location;
+ .. ts:def:: TipInformation
- // Identifies when we expect the next restocking to happen.
- next_restock?: Timestamp;
+ 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
-.. http:post:: /private/products/$PRODUCT_ID/lock
-
- This is used to lock a certain quantity of the product for a limited
- duration while the customer assembles a complete order. Note that
- frontends do not have to "unlock", they may rely on the timeout as
- given in the ``duration`` field. Re-posting a lock with a different
- ``duration`` or ``quantity`` updates the existing lock for the same UUID
- and does not result in a conflict.
-
- Unlocking by using a ``quantity`` of zero is is
- optional but recommended if customers remove products from the
- shopping cart. Note that actually POSTing to ``/orders`` with set
- ``manage_inventory`` and using ``lock_uuid`` will **transition** the
- lock to the newly created order (which may have a different ``duration``
- and ``quantity`` than what was requested in the lock operation).
- If an order is for fewer items than originally locked, the difference
- is automatically unlocked.
+ Handle request from wallet to pick up a tip.
**Request:**
- The request must be a `LockRequest`.
+ The request body is a `TipPickupRequest` object.
**Response:**
- :status 204 No content:
- The backend has successfully locked (or unlocked) the requested
``quantity``.
- :status 404 Not found:
- The backend has does not know this product.
+ :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 backend does not have enough of product in stock.
+ The tip has expired.
- .. ts:def:: LockRequest
+ .. ts:def:: TipPickupRequest
- interface LockRequest {
+ interface TipPickupRequest {
- // UUID that identifies the frontend performing the lock
- lock_uuid: UUID;
+ // List of planches the wallet wants to use for the tip
+ planchets: PlanchetDetail[];
+ }
- // How long does the frontend intend to hold the lock
- duration: RelativeTime;
+ .. ts:def:: PlanchetDetail
- // How many units should be locked?
- quantity: Integer;
+ 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
-.. http:delete:: /private/products/$PRODUCT_ID
+ interface TipResponse {
- Delete information about a product. Fails if the product is locked by
- anyone.
+ // Blind RSA signatures over the planchets.
+ // The order of the signatures matches the planchets list.
+ blind_sigs: BlindSignature[];
+ }
- **Response:**
+ .. ts:def:: BlindSignature
- :status 204 No content:
- The backend has successfully deleted the product.
- :status 404 Not found:
- The backend does not know the instance or the product.
- :status 409 Conflict:
- The backend refuses to delete the product because it is locked.
+ interface BlindSignature {
+ // The (blind) RSA signature. Still needs to be unblinded.
+ blind_sig: BlindedRsaSignature;
+ }
-------------------
-Payment processing
-------------------
-.. _post-order:
+-------------------
+Instance management
+-------------------
-.. http:post:: /private/orders
+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.
- Create a new order that a customer can pay for.
- This request is **not** idempotent unless an ``order_id`` is explicitly
specified.
- However, while repeating without an ``order_id`` will create another order,
that is
- generally pretty harmless (as long as only one of the orders is returned to
the wallet).
+Setting up instances
+--------------------
- .. note::
+.. http:post:: /private/instances
- This endpoint does not return a URL to redirect your user to confirm the
- payment. In order to get this URL use :http:get:/orders/$ORDER_ID. The
- API is structured this way since the payment redirect URL is not unique
- for every order, there might be varying parameters such as the session id.
+ This request will be used to create a new merchant instance in the backend.
**Request:**
- The request must be a `PostOrderRequest`.
+ The request must be a `InstanceConfigurationMessage`.
**Response:**
- :status 200 OK:
- The backend has successfully created the proposal. The response is a
- :ts:type:`PostOrderResponse`.
- :status 404 Not found:
- The order given used products from the inventory, but those were not found
- in the inventory. Or the merchant instance is unknown (including possibly
the instance being not configured for new orders). Details in the
- error code. NOTE: no good way to find out which product is not in the
- inventory, we MAY want to specify that in the reply.
+ :status 204 No content:
+ The backend has successfully created the instance.
:status 409 Conflict:
- A different proposal already exists under the specified order ID.
- :status 410 Gone:
- The order given used products from the inventory that are out of stock.
- The response is a :ts:type:`OutOfStockResponse`.
-
-
- .. ts:def:: PostOrderRequest
-
- interface PostOrderRequest {
- // The order must at least contain the minimal
- // order detail, but can override all
- order: Order;
-
- // if set, the backend will then set the refund deadline to the current
- // time plus the specified delay. If it's not set, refunds will not be
- // possible.
- refund_delay?: RelativeTime;
+ This instance already exists, but with other configuration options.
+ Use "PATCH" to update an instance configuration.
- // specifies the payment target preferred by the client. Can be used
- // to select among the various (active) wire methods supported by the
instance.
- payment_target?: string;
+ .. ts:def:: InstanceConfigurationMessage
- // specifies that some products are to be included in the
- // order from the inventory. For these inventory management
- // is performed (so the products must be in stock) and
- // details are completed from the product data of the backend.
- inventory_products?: MinimalInventoryProduct[];
+ interface InstanceConfigurationMessage {
+ // The URI where the wallet will send coins. A merchant may have
+ // multiple accounts, thus this is an array. Note that by
+ // removing URIs from this list the respective account is set to
+ // inactive and thus unavailable for new contracts, but preserved
+ // in the database as existing offers and contracts may still refer
+ // to it.
+ payto_uris: string[];
- // Specifies a lock identifier that was used to
- // lock a product in the inventory. Only useful if
- // ``manage_inventory`` is set. Used in case a frontend
- // reserved quantities of the individual products while
- // the shopping card was being built. Multiple UUIDs can
- // be used in case different UUIDs were used for different
- // products (i.e. in case the user started with multiple
- // shopping sessions that were combined during checkout).
- lock_uuids?: UUID[];
+ // Name of the merchant instance to create (will become $INSTANCE).
+ id: string;
- // Should a token for claiming the order be generated?
- // False can make sense if the ORDER_ID is sufficiently
- // high entropy to prevent adversarial claims (like it is
- // if the backend auto-generates one). Default is 'true'.
- create_token?: boolean;
+ // Merchant name corresponding to this instance.
+ name: string;
- }
+ // The merchant's physical address (to be put into contracts).
+ address: Location;
- .. ts:def:: Order
+ // The jurisdiction under which the merchant conducts its business
+ // (to be put into contracts).
+ jurisdiction: Location;
- type Order : MinimalOrderDetail | ContractTerms;
+ // Maximum wire fee this instance is willing to pay.
+ // Can be overridden by the frontend on a per-order basis.
+ default_max_wire_fee: Amount;
- The following fields must be specified in the ``order`` field of the
request. Other fields from
- `ContractTerms` are optional, and will override the defaults in the merchant
configuration.
+ // Default factor for wire fee amortization calculations.
+ // Can be overridden by the frontend on a per-order basis.
+ default_wire_fee_amortization: Integer;
- .. ts:def:: MinimalOrderDetail
+ // Maximum deposit fee (sum over all coins) this instance is willing to
pay.
+ // Can be overridden by the frontend on a per-order basis.
+ default_max_deposit_fee: Amount;
- interface MinimalOrderDetail {
- // Amount to be paid by the customer
- amount: Amount;
+ // If the frontend does NOT specify an execution date, how long should
+ // we tell the exchange to wait to aggregate transactions before
+ // executing the wire transfer? This delay is added to the current
+ // time when we generate the advisory execution time for the exchange.
+ default_wire_transfer_delay: RelativeTime;
- // Short summary of the order
- summary: string;
+ // If the frontend does NOT specify a payment deadline, how long should
+ // offers we make be valid by default?
+ default_pay_delay: RelativeTime;
- // URL that will show that the order was successful after
- // it has been paid for. Optional. When POSTing to the
- // merchant, the placeholder "${ORDER_ID}" will be
- // replaced with the actual order ID (useful if the
- // order ID is generated server-side and needs to be
- // in the URL).
- fulfillment_url?: string;
}
- The following fields can be specified if the order is inventory-based.
- In this case, the backend can compute the amounts from the prices given
- in the inventory. Note that if the frontend does give more details
- (towards the ContractTerms), this will override those details
- (including total price) that would otherwise computed based on information
- from the inventory.
- .. ts:def:: ProductSpecification
+.. http:patch:: /private/instances/$INSTANCE
- type ProductSpecification : (MinimalInventoryProduct | Product);
+ Update the configuration of a merchant instance.
+ **Request**
- .. ts:def:: MinimalInventoryProduct
+ The request must be a `InstanceReconfigurationMessage`.
+ Removing an existing payto_uri deactivates
+ the account (it will no longer be used for future contracts).
- Note that if the frontend does give details beyond these,
- it will override those details (including price or taxes)
- that the backend would otherwise fill in via the inventory.
+ **Response:**
- interface MinimalInventoryProduct {
- // Which product is requested (here mandatory!)
- product_id: string;
+ :status 204 No content:
+ The backend has successfully created the instance.
+ :status 404 Not found:
+ This instance is unknown and thus cannot be reconfigured.
- // How many units of the product are requested
- quantity: Integer;
- }
+ .. ts:def:: InstanceReconfigurationMessage
+ interface InstanceReconfigurationMessage {
+ // The URI where the wallet will send coins. A merchant may have
+ // multiple accounts, thus this is an array. Note that by
+ // removing URIs from this list
+ payto_uris: string[];
- .. ts:def:: PostOrderResponse
+ // Merchant name corresponding to this instance.
+ name: string;
- interface PostOrderResponse {
- // Order ID of the response that was just created
- order_id: string;
+ // The merchant's physical address (to be put into contracts).
+ address: Location;
- // Token that authorizes the wallet to claim the order.
- // Provided only if "create_token" was set to 'true'
- // in the request.
- token?: ClaimToken;
- }
+ // The jurisdiction under which the merchant conducts its business
+ // (to be put into contracts).
+ jurisdiction: Location;
+ // Maximum wire fee this instance is willing to pay.
+ // Can be overridden by the frontend on a per-order basis.
+ default_max_wire_fee: Amount;
- .. ts:def:: OutOfStockResponse
+ // Default factor for wire fee amortization calculations.
+ // Can be overridden by the frontend on a per-order basis.
+ default_wire_fee_amortization: Integer;
- interface OutOfStockResponse {
- // Which items are out of stock?
- missing_products: OutOfStockEntry;
- }
+ // Maximum deposit fee (sum over all coins) this instance is willing to
pay.
+ // Can be overridden by the frontend on a per-order basis.
+ default_max_deposit_fee: Amount;
- .. ts:def:: OutOfStockEntry
-
- interface OutOfStockEntry {
- // Product ID of an out-of-stock item
- product_id: string;
-
- // Requested quantity
- requested_quantity: Integer;
+ // If the frontend does NOT specify an execution date, how long should
+ // we tell the exchange to wait to aggregate transactions before
+ // executing the wire transfer? This delay is added to the current
+ // time when we generate the advisory execution time for the exchange.
+ default_wire_transfer_delay: RelativeTime;
- // Available quantity (must be below ``requested_quanitity``)
- available_quantity: Integer;
+ // If the frontend does NOT specify a payment deadline, how long should
+ // offers we make be valid by default?
+ default_pay_delay: RelativeTime;
- // When do we expect the product to be again in stock?
- // Optional, not given if unknown.
- restock_expected?: Timestamp;
}
+Inspecting instances
+--------------------
-.. http:get:: /private/orders
-
- Returns known orders up to some point in the past.
-
- **Request:**
+.. _instances:
+.. http:get:: /private/instances
- :query paid: *Optional*. If set to yes, only return paid orders, if no only
unpaid orders. Do not give (or use "all") to see all orders regardless of
payment status.
- :query refunded: *Optional*. If set to yes, only return refunded orders, if
no only unrefunded orders. Do not give (or use "all") to see all orders
regardless of refund status.
- :query wired: *Optional*. If set to yes, only return wired orders, if no
only orders with missing wire transfers. Do not give (or use "all") to see all
orders regardless of wire transfer status.
- :query date: *Optional.* Time threshold, see ``delta`` for its
interpretation. Defaults to the oldest or most recent entry, depending on
``delta``.
- :query start: *Optional*. Row number threshold, see ``delta`` for its
interpretation. Defaults to ``UINT64_MAX``, namely the biggest row id possible
in the database.
- :query delta: *Optional*. takes value of the form ``N (-N)``, so that at
most ``N`` values strictly older (younger) than ``start`` and ``date`` are
returned. Defaults to ``-20`` to return the last 20 entries (before ``start``
and/or ``date``).
- :query timeout_ms: *Optional*. Timeout in milli-seconds to wait for
additional orders if the answer would otherwise be negative (long polling).
Only useful if delta is positive. Note that the merchant MAY still return a
response that contains fewer than delta orders.
+ This is used to return the list of all the merchant instances
**Response:**
:status 200 OK:
- The response is an `OrderHistory`.
+ The backend has successfully returned the list of instances stored. Returns
+ a `InstancesResponse`.
- .. ts:def:: OrderHistory
+ .. ts:def:: InstancesResponse
- interface OrderHistory {
- // timestamp-sorted array of all orders matching the query.
- // The order of the sorting depends on the sign of ``delta``.
- orders : OrderHistoryEntry[];
+ 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:: OrderHistoryEntry
+ .. ts:def:: Instance
- interface OrderHistoryEntry {
+ interface Instance {
+ // Merchant name corresponding to this instance.
+ name: string;
- // order ID of the transaction related to this entry.
- order_id: string;
+ // Merchant instance this response is about ($INSTANCE)
+ id: string;
- // row ID of the order in the database
- row_id: number;
+ // Public key of the merchant/instance, in Crockford Base32 encoding.
+ merchant_pub: EddsaPublicKey;
- // when the order was created
- timestamp: Timestamp;
+ // 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[];
- // the amount of money the order is for
- amount: Amount;
+ }
- // the summary of the order
- summary: string;
- // whether some part of the order is refundable,
- // that is the refund deadline has not yet expired
- // and the total amount refunded so far is below
- // the value of the original transaction.
- refundable: boolean;
+.. http:get:: /private/instances/$INSTANCE
- // whether the order has been paid or not
- paid: boolean;
- }
+ This is used to query a specific merchant instance.
+ **Response:**
+ :status 200 OK:
+ The backend has successfully returned the list of instances stored. Returns
+ a `QueryInstancesResponse`.
-.. http:post:: /orders/$ORDER_ID/claim
+ .. ts:def:: QueryInstancesResponse
- 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.
+ interface QueryInstancesResponse {
+ // The URI where the wallet will send coins. A merchant may have
+ // multiple accounts, thus this is an array.
+ accounts: MerchantAccount[];
- **Request:**
+ // Merchant name corresponding to this instance.
+ name: string;
- The request must be a `ClaimRequest`
+ // Public key of the merchant/instance, in Crockford Base32 encoding.
+ merchant_pub: EddsaPublicKey;
- .. ts:def:: ClaimRequest
+ // The merchant's physical address (to be put into contracts).
+ address: Location;
- interface ClaimRequest {
- // Nonce to identify the wallet that claimed the order.
- nonce: string;
+ // The jurisdiction under which the merchant conducts its business
+ // (to be put into contracts).
+ jurisdiction: Location;
+
+ // Maximum wire fee this instance is willing to pay.
+ // Can be overridden by the frontend on a per-order basis.
+ default_max_wire_fee: Amount;
+
+ // Default factor for wire fee amortization calculations.
+ // Can be overridden by the frontend on a per-order basis.
+ default_wire_fee_amortization: Integer;
+
+ // Maximum deposit fee (sum over all coins) this instance is willing to
pay.
+ // Can be overridden by the frontend on a per-order basis.
+ default_max_deposit_fee: Amount;
+
+ // If the frontend does NOT specify an execution date, how long should
+ // we tell the exchange to wait to aggregate transactions before
+ // executing the wire transfer? This delay is added to the current
+ // time when we generate the advisory execution time for the exchange.
+ default_wire_transfer_delay: RelativeTime;
+
+ // If the frontend does NOT specify a payment deadline, how long should
+ // offers we make be valid by default?
+ default_pay_deadline: RelativeTime;
- // 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:**
+ .. ts:def:: MerchantAccount
- :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.
+ interface MerchantAccount {
- .. ts:def:: ClaimResponse
+ // payto:// URI of the account.
+ payto_uri: string;
- interface ClaimResponse {
- // Contract terms of the claimed order
- contract_terms: ContractTerms;
+ // Hash over the wire details (including over the salt)
+ h_wire: HashCode;
- // Signature by the merchant over the contract terms.
- sig: EddsaSignature;
+ // salt used to compute h_wire
+ salt: HashCode;
+
+ // true if this account is active,
+ // false if it is historic.
+ active: boolean;
}
+
+Deleting instances
+------------------
-.. http:post:: /orders/$ORDER_ID/pay
+.. http:delete:: /private/instances/$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.
+ This request will be used to delete (permanently disable)
+ or purge merchant instance in the backend. Purging will
+ delete all offers and payments associated with the instance,
+ while disabling (the default) only deletes the private key
+ and makes the instance unusable for new orders or payments.
**Request:**
- The request must be a `pay request <PayRequest>`.
+ :query purge: *Optional*. If set to YES, the instance will be fully
+ deleted. Otherwise only the private key would be deleted.
- **Response:**
+ **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 204 No content:
+ The backend has successfully removed the instance. The body is empty.
: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.
+ The instance is unknown to the backend.
: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 instance cannot be deleted because it has pending offers, or
+ the instance cannot be purged because it has successfully processed
+ payments that have not passed the TAX_RECORD_EXPIRATION time.
+ The latter case only applies if ``purge`` was set.
- 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
+--------------------
+Inventory management
+--------------------
- interface PaymentResponse {
- // Signature on ``TALER_PaymentResponsePS`` with the public
- // key of the merchant instance.
- sig: EddsaSignature;
+.. _inventory:
- }
+Inventory management is an *optional* backend feature that can be used to
+manage limited stocks of products and to auto-complete product descriptions in
+contracts (such that the frontends have to do less work). You can use the
+Taler merchant backend to process payments *without* using its inventory
+management.
- .. ts:def:: PayRequest
- interface PayRequest {
- // The coins used to make the payment.
- coins: CoinPaySig[];
+Adding products to the inventory
+--------------------------------
- // The session for which the payment is made (or replayed).
- // Only set for session-based payments.
- session_id?: string;
+.. http:post:: /private/products
- }
+ This is used to add a product to the inventory.
- .. ts:def:: CoinPaySig
+ **Request:**
- export interface CoinPaySig {
- // Signature by the coin.
- coin_sig: EddsaSignature;
+ The request must be a `ProductAddDetail`.
- // Public key of the coin being spend.
- coin_pub: EddsaPublicKey;
+ **Response:**
- // Signature made by the denomination public key.
- ub_sig: RsaSignature;
+ :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.
- // 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;
+ .. ts:def:: ProductAddDetail
+
+ interface ProductAddDetail {
+
+ // product ID to use.
+ product_id: string;
+
+ // Human-readable product description.
+ description: string;
+
+ // Map from IETF BCP 47 language tags to localized descriptions
+ description_i18n: { [lang_tag: string]: string };
+
+ // unit in which the product is measured (liters, kilograms, packages,
etc.)
+ unit: string;
+
+ // The price for one ``unit`` of the product. Zero is used
+ // to imply that this product is not sold separately, or
+ // that the price is not fixed, and must be supplied by the
+ // front-end. If non-zero, this price MUST include applicable
+ // taxes.
+ price: Amount;
+
+ // An optional base64-encoded product image
+ image: ImageDataUrl;
+
+ // a list of taxes paid by the merchant for one unit of this product
+ taxes: Tax[];
+
+ // Number of units of the product in stock in sum in total,
+ // including all existing sales ever. Given in product-specific
+ // units.
+ // A value of -1 indicates "infinite" (i.e. for "electronic" books).
+ total_stock: Integer;
+
+ // Identifies where the product is in stock.
+ address: Location;
+
+ // Identifies when we expect the next restocking to happen.
+ next_restock?: Timestamp;
- // 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.
+.. 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 `paid request <PaidRequest>`.
+ The request must be a `ProductPatchDetail`.
**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.
+ The backend has successfully expanded the inventory.
+
+
+ .. ts:def:: ProductPatchDetail
+
+ interface ProductPatchDetail {
+
+ // Human-readable product description.
+ description: string;
+
+ // Map from IETF BCP 47 language tags to localized descriptions
+ description_i18n: { [lang_tag: string]: string };
+
+ // unit in which the product is measured (liters, kilograms, packages,
etc.)
+ unit: string;
+
+ // The price for one ``unit`` of the product. Zero is used
+ // to imply that this product is not sold separately, or
+ // that the price is not fixed, and must be supplied by the
+ // front-end. If non-zero, this price MUST include applicable
+ // taxes.
+ price: Amount;
+
+ // An optional base64-encoded product image
+ image: ImageDataUrl;
+
+ // a list of taxes paid by the merchant for one unit of this product
+ taxes: Tax[];
+
+ // Number of units of the product in stock in sum in total,
+ // including all existing sales ever. Given in product-specific
+ // units.
+ // 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;
+
+ // Identifies when we expect the next restocking to happen.
+ next_restock?: Timestamp;
+
+ }
+
+Inspecting inventory
+--------------------
+
+.. 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:
+
+ .. ts:def:: InventoryEntry
+
+ interface InventoryEntry {
+ // Product identifier, as found in the product.
+ product_id: string;
+
+ }
+
+
+.. 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;
+
+ // Map from IETF BCP 47 language tags to localized descriptions
+ description_i18n: { [lang_tag: string]: string };
+
+ // unit in which the product is measured (liters, kilograms, packages,
etc.)
+ unit: string;
+
+ // The price for one ``unit`` of the product. Zero is used
+ // to imply that this product is not sold separately, or
+ // that the price is not fixed, and must be supplied by the
+ // front-end. If non-zero, this price MUST include applicable
+ // taxes.
+ price: Amount;
+
+ // An optional base64-encoded product image
+ image: ImageDataUrl;
+
+ // a list of taxes paid by the merchant for one unit of this product
+ taxes: Tax[];
+
+ // Number of units of the product in stock in sum in total,
+ // including all existing sales ever. Given in product-specific
+ // units.
+ // 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;
+
+ // Identifies when we expect the next restocking to happen.
+ next_restock?: Timestamp;
+
+ }
+
+
+Reserving inventory
+-------------------
+
+.. http:post:: /private/products/$PRODUCT_ID/lock
+
+ This is used to lock a certain quantity of the product for a limited
+ duration while the customer assembles a complete order. Note that
+ frontends do not have to "unlock", they may rely on the timeout as
+ given in the ``duration`` field. Re-posting a lock with a different
+ ``duration`` or ``quantity`` updates the existing lock for the same UUID
+ and does not result in a conflict.
+
+ Unlocking by using a ``quantity`` of zero is is
+ optional but recommended if customers remove products from the
+ shopping cart. Note that actually POSTing to ``/orders`` with set
+ ``manage_inventory`` and using ``lock_uuid`` will **transition** the
+ lock to the newly created order (which may have a different ``duration``
+ and ``quantity`` than what was requested in the lock operation).
+ If an order is for fewer items than originally locked, the difference
+ is automatically unlocked.
+
+ **Request:**
+
+ The request must be a `LockRequest`.
+
+ **Response:**
+
+ :status 204 No content:
+ The backend has successfully locked (or unlocked) the requested
``quantity``.
: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.
+ The backend has does not know this product.
+ :status 410 Gone:
+ The backend does not have enough of product in stock.
- .. ts:def:: PaidRequest
+ .. ts:def:: LockRequest
- interface PaidRequest {
- // Signature on ``TALER_PaymentResponsePS`` with the public
- // key of the merchant instance.
- sig: EddsaSignature;
+ interface LockRequest {
- // 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;
+ // UUID that identifies the frontend performing the lock
+ lock_uuid: UUID;
+
+ // How long does the frontend intend to hold the lock
+ duration: RelativeTime;
+
+ // How many units should be locked?
+ quantity: Integer;
- // Session id for which the payment is proven.
- session_id: string;
}
-.. _order-abort:
-.. http:post:: /orders/$ORDER_ID/abort
+Removing products from inventory
+--------------------------------
- Abort paying for an order and obtain a refund for coins that
- were already deposited as part of a failed payment.
+.. http:delete:: /private/products/$PRODUCT_ID
+
+ Delete information about a product. Fails if the product is locked by
+ anyone.
+
+ **Response:**
+
+ :status 204 No content:
+ The backend has successfully deleted the product.
+ :status 404 Not found:
+ The backend does not know the instance or the product.
+ :status 409 Conflict:
+ The backend refuses to delete the product because it is locked.
+
+
+------------------
+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
+
+ Create a new order that a customer can pay for.
+
+ This request is **not** idempotent unless an ``order_id`` is explicitly
specified.
+ However, while repeating without an ``order_id`` will create another order,
that is
+ generally pretty harmless (as long as only one of the orders is returned to
the wallet).
+
+ .. note::
+
+ This endpoint does not return a URL to redirect your user to confirm the
+ payment. In order to get this URL use :http:get:/orders/$ORDER_ID. The
+ API is structured this way since the payment redirect URL is not unique
+ for every order, there might be varying parameters such as the session id.
**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.
+ The request must be a `PostOrderRequest`.
**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.
+ The backend has successfully created the proposal. The response is a
+ :ts:type:`PostOrderResponse`.
: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 order given used products from the inventory, but those were not found
+ in the inventory. Or the merchant instance is unknown (including possibly
the instance being not configured for new orders). Details in the
+ error code. NOTE: no good way to find out which product is not in the
+ inventory, we MAY want to specify that in the reply.
+ :status 409 Conflict:
+ A different proposal already exists under the specified order ID.
+ :status 410 Gone:
+ The order given used products from the inventory that are out of stock.
+ The response is a :ts:type:`OutOfStockResponse`.
- 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
+ .. ts:def:: PostOrderRequest
+
+ interface PostOrderRequest {
+ // The order must at least contain the minimal
+ // order detail, but can override all
+ order: Order;
+
+ // if set, the backend will then set the refund deadline to the current
+ // time plus the specified delay. If it's not set, refunds will not be
+ // possible.
+ refund_delay?: RelativeTime;
+
+ // specifies the payment target preferred by the client. Can be used
+ // to select among the various (active) wire methods supported by the
instance.
+ payment_target?: string;
+
+ // specifies that some products are to be included in the
+ // order from the inventory. For these inventory management
+ // is performed (so the products must be in stock) and
+ // details are completed from the product data of the backend.
+ inventory_products?: MinimalInventoryProduct[];
+
+ // Specifies a lock identifier that was used to
+ // lock a product in the inventory. Only useful if
+ // ``manage_inventory`` is set. Used in case a frontend
+ // reserved quantities of the individual products while
+ // the shopping card was being built. Multiple UUIDs can
+ // be used in case different UUIDs were used for different
+ // products (i.e. in case the user started with multiple
+ // shopping sessions that were combined during checkout).
+ lock_uuids?: UUID[];
+
+ // Should a token for claiming the order be generated?
+ // False can make sense if the ORDER_ID is sufficiently
+ // high entropy to prevent adversarial claims (like it is
+ // if the backend auto-generates one). Default is 'true'.
+ create_token?: boolean;
+
+ }
+
+ .. ts:def:: Order
+
+ type Order : MinimalOrderDetail | ContractTerms;
+
+ The following fields must be specified in the ``order`` field of the
request. Other fields from
+ `ContractTerms` are optional, and will override the defaults in the merchant
configuration.
+
+ .. ts:def:: MinimalOrderDetail
+
+ interface MinimalOrderDetail {
+ // Amount to be paid by the customer
+ amount: Amount;
+
+ // Short summary of the order
+ summary: string;
+
+ // URL that will show that the order was successful after
+ // it has been paid for. Optional. When POSTing to the
+ // merchant, the placeholder "${ORDER_ID}" will be
+ // replaced with the actual order ID (useful if the
+ // order ID is generated server-side and needs to be
+ // in the URL).
+ fulfillment_url?: string;
+ }
+
+ The following fields can be specified if the order is inventory-based.
+ In this case, the backend can compute the amounts from the prices given
+ in the inventory. Note that if the frontend does give more details
+ (towards the ContractTerms), this will override those details
+ (including total price) that would otherwise computed based on information
+ from the inventory.
- interface AbortRequest {
+ .. ts:def:: ProductSpecification
- // hash of the order's contract terms (this is used to authenticate the
- // wallet/customer in case $ORDER_ID is guessable).
- h_contract: HashCode;
+ type ProductSpecification : (MinimalInventoryProduct | Product);
- // 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
+ .. ts:def:: MinimalInventoryProduct
- interface AbortingCoin {
- // Public key of a coin for which the wallet is requesting an
abort-related refund.
- coin_pub: EddsaPublicKey;
+ Note that if the frontend does give details beyond these,
+ it will override those details (including price or taxes)
+ that the backend would otherwise fill in via the inventory.
- // The amount to be refunded (matches the original contribution)
- contribution: Amount;
+ interface MinimalInventoryProduct {
+ // Which product is requested (here mandatory!)
+ product_id: string;
- // URL of the exchange this coin was withdrawn from.
- exchange_url: string;
+ // How many units of the product are requested
+ quantity: Integer;
}
- .. ts:def:: AbortResponse
+ .. ts:def:: PostOrderResponse
- interface AbortResponse {
+ interface PostOrderResponse {
+ // Order ID of the response that was just created
+ order_id: string;
- // 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[];
+ // Token that authorizes the wallet to claim the order.
+ // Provided only if "create_token" was set to 'true'
+ // in the request.
+ token?: ClaimToken;
}
- .. ts:def:: MerchantAbortPayRefundStatus
- type MerchantAbortPayRefundStatus =
- | MerchantAbortPayRefundSuccessStatus
- | MerchantAbortPayRefundFailureStatus;
+ .. ts:def:: OutOfStockResponse
- .. ts:def:: MerchantAbortPayRefundFailureStatus
+ interface OutOfStockResponse {
- // Details about why a refund failed.
- interface MerchantAbortPayRefundFailureStatus {
- // Used as tag for the sum type RefundStatus sum type.
- type: "failure"
+ // Product ID of an out-of-stock item
+ product_id: string;
- // HTTP status of the exchange request, must NOT be 200.
- exchange_status: Integer;
+ // Requested quantity
+ requested_quantity: Integer;
- // Taler error code from the exchange reply, if available.
- exchange_code?: Integer;
+ // Available quantity (must be below ``requested_quanitity``)
+ available_quantity: Integer;
- // If available, HTTP reply from the exchange.
- exchange_reply?: Object;
+ // When do we expect the product to be again in stock?
+ // Optional, not given if unknown.
+ restock_expected?: Timestamp;
}
- .. 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"
+Inspecting orders
+-----------------
- // HTTP status of the exchange request, 200 (integer) required for
refund confirmations.
- exchange_status: 200;
+.. http:get:: /private/orders
- // 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;
+ Returns known orders up to some point in the past.
- // 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;
- }
+ **Request:**
+ :query paid: *Optional*. If set to yes, only return paid orders, if no only
unpaid orders. Do not give (or use "all") to see all orders regardless of
payment status.
+ :query refunded: *Optional*. If set to yes, only return refunded orders, if
no only unrefunded orders. Do not give (or use "all") to see all orders
regardless of refund status.
+ :query wired: *Optional*. If set to yes, only return wired orders, if no
only orders with missing wire transfers. Do not give (or use "all") to see all
orders regardless of wire transfer status.
+ :query date: *Optional.* Time threshold, see ``delta`` for its
interpretation. Defaults to the oldest or most recent entry, depending on
``delta``.
+ :query start: *Optional*. Row number threshold, see ``delta`` for its
interpretation. Defaults to ``UINT64_MAX``, namely the biggest row id possible
in the database.
+ :query delta: *Optional*. takes value of the form ``N (-N)``, so that at
most ``N`` values strictly older (younger) than ``start`` and ``date`` are
returned. Defaults to ``-20`` to return the last 20 entries (before ``start``
and/or ``date``).
+ :query timeout_ms: *Optional*. Timeout in milli-seconds to wait for
additional orders if the answer would otherwise be negative (long polling).
Only useful if delta is positive. Note that the merchant MAY still return a
response that contains fewer than delta orders.
-.. http:patch:: /private/orders/$ORDER_ID/forget
+ **Response:**
- Forget fields in an order's contract terms that the merchant no
- longer needs.
+ :status 200 OK:
+ The response is an `OrderHistory`.
- **Request:**
+ .. ts:def:: OrderHistory
- 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.
+ interface OrderHistory {
+ // timestamp-sorted array of all orders matching the query.
+ // The order of the sorting depends on the sign of ``delta``.
+ orders : OrderHistoryEntry[];
+ }
- 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:**
+ .. ts:def:: OrderHistoryEntry
- :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.
+ interface OrderHistoryEntry {
- .. ts:def:: ForgetRequest
+ // order ID of the transaction related to this entry.
+ order_id: string;
- interface ForgetRequest {
+ // row ID of the order in the database
+ row_id: number;
- // Array of valid JSON paths to forgettable fields in the order's
- // contract terms.
- fields: string[];
- }
+ // when the order was created
+ timestamp: Timestamp;
+ // the amount of money the order is for
+ amount: Amount;
+
+ // the summary of the order
+ summary: string;
+
+ // whether some part of the order is refundable,
+ // that is the refund deadline has not yet expired
+ // and the total amount refunded so far is below
+ // the value of the original transaction.
+ refundable: boolean;
+
+ // whether the order has been paid or not
+ paid: boolean;
+ }
.. http:get:: /private/orders/$ORDER_ID
@@ -1404,101 +1760,62 @@ Payment processing
coin_pub: CoinPublicKey;
}
+
+Private order data cleanup
+--------------------------
-.. 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
+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.
- 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;
+.. http:patch:: /private/orders/$ORDER_ID/forget
- // Amount that was refunded in total.
- refund_amount: Amount;
- }
+ Forget fields in an order's contract terms that the merchant no
+ longer needs.
- .. ts:def:: StatusGotoResponse
+ **Request:**
- interface StatusGotoResponse {
- // The client should go to the fulfillment URL, it may be ready or
- // might have some other interesting status.
- fulfillment_url: string;
- }
+ 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.
- .. ts:def:: StatusUnpaidResponse
+ 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).
- interface StatusUnpaidResponse {
- // URI that the wallet must process to complete the payment.
- taler_pay_uri: string;
+ **Response:**
- // Status URL, can be used as a redirect target for the browser
- // to show the order QR code / trigger the wallet.
- fulfillment_url?: string;
+ :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.
- // 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:
diff --git a/taler-merchant-manual.rst b/taler-merchant-manual.rst
index 1e6e72a..d93c12d 100644
--- a/taler-merchant-manual.rst
+++ b/taler-merchant-manual.rst
@@ -1278,6 +1278,8 @@ FIXME: which coin denominations are needed for the
benchmark?
FIXME: provide "minimum" configuration file!
+DOCUMENT how to run taler-merchant-benchmark: configuration MUST set exchange
bank password to 'x'!
+
FIXME: explain Postgres setup!
@@ -1357,10 +1359,6 @@ interesting, there are:
- ``--unaggregated-number=UN`` This option instructs the tool to
perform *UN* (one coin) payments that will be left unaggregated.
-- ``--alt-instance=AI`` This option instructs the tool to perform
- payments using the merchant instance *AI* (instead of the *default*
- instance)
-
As for the ``ordinary`` subcommand, it is worth explaining the following
options:
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-docs] branch master updated: restructure merchant API documentation (#6492),
gnunet <=