[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-docs] branch master updated: spec v1 of merchant protocol
From: |
gnunet |
Subject: |
[taler-docs] branch master updated: spec v1 of merchant protocol |
Date: |
Mon, 13 Apr 2020 16:27:12 +0200 |
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 95f5214 spec v1 of merchant protocol
95f5214 is described below
commit 95f5214432d6f58ac374db481cc13e5542b2572c
Author: Christian Grothoff <address@hidden>
AuthorDate: Mon Apr 13 16:27:10 2020 +0200
spec v1 of merchant protocol
---
core/api-merchant.rst | 1862 +++++++++++++++++++++++++++----------------------
1 file changed, 1016 insertions(+), 846 deletions(-)
diff --git a/core/api-merchant.rst b/core/api-merchant.rst
index 22c090f..43eed03 100644
--- a/core/api-merchant.rst
+++ b/core/api-merchant.rst
@@ -23,9 +23,74 @@
Merchant Backend API
====================
+WARNING: This document describes the version 1 of the merchant backend
+API, which is NOT yet implemented at all!
+
+The ``*/public/*`` endpoints are publicly exposed on the Internet and accessed
+both by the user's browser and their wallet.
+
+Most endpoints given here can be prefixed by a base URL that includes the
+specific instance selected (BASE_URL/instances/$INSTANCE/). If
+``/instances/`` is missing, the default instance is to be used.
+
.. contents:: Table of Contents
+-------------------------
+Getting the configuration
+-------------------------
+
+.. http:get:: /public/config
+
+ Return the protocol version and currency supported by this merchant backend.
+
+ **Response:**
+
+ :status 200 OK:
+ The exchange accepted all of the coins. The body is a `VersionResponse`.
+
+ .. ts:def:: VersionResponse
+
+ interface VersionResponse {
+ // libtool-style representation of the Merchant protocol version, see
+ //
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
+ // The format is "current:revision:age".
+ version: string;
+
+ // Currency supported by this backend.
+ currency: string;
+
+ // optional array with information about the instances running at this
backend
+ // FIXME: remove, use/provide http:get:: /instances instead!
+ instances: InstanceInformation[];
+ }
+
+ .. ts:def:: InstanceInformation
+
+ interface InstanceInformation {
+
+ // Human-readable legal business name served by this instance
+ name: string;
+
+ // Base URL of the instance. Can be of the form "/PizzaShop/" or
+ // a fully qualified URL (i.e. "https://backend.example.com/PizzaShop/").
+ instance_baseurl: string;
+
+ // Public key of the merchant/instance, in Crockford Base32 encoding.
+ merchant_pub: EddsaPublicKey;
+
+ // List of the payment targets supported by this instance. Clients can
+ // specify the desired payment target in /order requests. Note that
+ // front-ends do not have to support wallets selecting payment targets.
+ payment_targets: string[];
+
+ // Base URL of the exchange this instance uses for tipping.
+ // Optional, only present if the instance supports tipping.
+ // FIXME: obsolete with current tipping API!
+ tipping_exchange_baseurl?: string;
+
+ }
+
------------------
Receiving Payments
@@ -33,7 +98,7 @@ Receiving Payments
.. _post-order:
-.. http:post:: /order
+.. http:post:: /create-order
Create a new order that a customer can pay for.
@@ -41,10 +106,10 @@ Receiving Payments
.. 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:`/check-payment`. 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 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:**
@@ -79,8 +144,8 @@ Receiving Payments
summary: string;
// URL that will show that the order was successful after
- // it has been paid for. The wallet will always automatically append
- // the order_id as a query parameter.
+ // it has been paid for. The wallet must always automatically append
+ // the order_id as a query parameter to this URL when using it.
fulfillment_url: string;
}
@@ -92,363 +157,291 @@ Receiving Payments
}
-.. http:get:: /check-payment
- Check the payment status of an order. If the order exists but is not payed
yet,
- the response provides a redirect URL.
- When the user goes to this URL, they will be prompted for payment.
+.. http:get:: /orders
- **Request:**
-
- :query order_id: order id that should be used for the payment
- :query session_id: *Optional*. Session ID that the payment must be bound to.
If not specified, the payment is not session-bound.
- :query timeout: *Optional*. Timeout in seconds to wait for a payment if the
answer would otherwise be negative (long polling).
+ Returns known orders up to some point in the past
- **Response:**
+ **Request**
- Returns a `CheckPaymentResponse`, whose format can differ based on the
status of the payment.
+ :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 aborted: *Optional*. If set to yes, only return aborted orders, if no
only unaborted orders. Do not give (or use "all") to see all orders regardless
of abort 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 younger (older) than ``start`` and ``date`` are
returned. Defaults to ``-20``.
+ :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.
- .. ts:def:: CheckPaymentResponse
+ **Response**
- type CheckPaymentResponse = CheckPaymentPaidResponse |
CheckPaymentUnpaidResponse
+ :status 200 OK:
+ The response is a JSON ``array`` of `OrderHistory`. The array is
+ sorted such that entry ``i`` is younger than entry ``i+1``.
- .. ts:def:: CheckPaymentPaidResponse
+ .. ts:def:: OrderHistory
- interface CheckPaymentPaidResponse {
- paid: true;
+ interface OrderHistory {
+ // The serial number this entry has in the merchant's DB.
+ row_id: number;
- // Was the payment refunded (even partially)
- refunded: boolean;
+ // order ID of the transaction related to this entry.
+ order_id: string;
- // Amount that was refunded, only present if refunded is true.
- refund_amount?: Amount;
+ // Transaction's timestamp
+ timestamp: Timestamp;
- // Contract terms
- contract_terms: ContractTerms;
- }
+ // Total amount the customer should pay for this order.
+ total: Amount;
- .. ts:def:: CheckPaymentUnpaidResponse
+ // Total amount the customer did pay for this order.
+ paid: Amount;
- interface CheckPaymentUnpaidResponse {
- paid: false;
+ // Total amount the customer was refunded for this order.
+ // (includes abort-refund and refunds, boolean flag
+ // below can help determine which case it is).
+ refunded: Amount;
- // URI that the wallet must process to complete the payment.
- taler_pay_uri: string;
+ // Was the order ever fully paid?
+ is_paid: boolean;
- // 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;
}
---------------
-Giving Refunds
---------------
-.. http:post:: /refund
+.. http:post:: /public/orders/$ORDER_ID/claim
- Increase the refund amount associated with a given order. The user should be
- redirected to the ``taler_refund_url`` to trigger refund processing in the
wallet.
+ Wallet claims ownership (via nonce) over an order. By claiming
+ an order, the wallet obtains the full contract terms, and thereby
+ implicitly also the hash of the contract terms it needs for the
+ other ``/public/`` APIs to authenticate itself as the wallet that
+ is indeed eligible to inspect this particular order's status.
**Request**
- The request body is a `RefundRequest` object.
+ The request must be a `ClaimRequest`
+
+ .. ts:def:: ClaimRequest
+
+ interface ClaimRequest {
+ // Nonce to identify the wallet that claimed the order.
+ nonce: string;
+ }
**Response**
:status 200 OK:
- The refund amount has been increased, the backend responds with a
`MerchantRefundResponse`
+ The client has successfully claimed the order.
+ The response contains the :ref:`contract terms <ContractTerms>`.
:status 404 Not found:
- The order is unknown to the merchant
+ The backend is unaware of the instance or order.
:status 409 Conflict:
- The refund amount exceeds the amount originally paid
-
- .. ts:def:: RefundRequest
+ The someone else claimed the same order ID with different nonce before.
- interface RefundRequest {
- // Order id of the transaction to be refunded
- order_id: string;
- // Amount to be refunded
- refund: Amount;
+.. http:post:: /public/orders/$ORDER_ID/pay
- // Human-readable refund justification
- reason: string;
- }
+ 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.
- .. ts:def:: MerchantRefundResponse
+ **Request:**
- interface MerchantRefundResponse {
+ The request must be a `pay request <PayRequest>`.
- // Hash of the contract terms of the contract that is being refunded.
- h_contract_terms: HashCode;
+ **Response:**
- // URL (handled by the backend) that the wallet should access to
- // trigger refund processing.
- taler_refund_url: string;
- }
+ :status 200 OK:
+ The exchange accepted all of the coins.
+ The body is a `payment response <PaymentResponse>`.
+ The ``frontend`` should now fullfill the contract.
+ :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:
+ 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 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 412 Precondition Failed:
+ The given exchange is not acceptable for this merchant, as it is not in the
+ list of accepted exchanges and not audited by an approved auditor.
+ :status 424 Failed Dependency:
+ The merchant's interaction with the exchange failed in some way.
+ The client might want to try later again.
+ This includes failures like the denomination key of a coin not being
+ known to the exchange as far as the merchant can tell.
+ The backend will return verbatim the error codes received from the exchange's
+ :ref:`deposit <deposit>` API. If the wallet made a mistake, like by
+ double-spending for example, the frontend should pass the reply verbatim to
+ the browser/wallet. If the payment was successful, the frontend MAY use
+ this to trigger some business logic.
---------------------
-Giving Customer Tips
---------------------
+ .. ts:def:: PaymentResponse
+ interface PaymentResponse {
+ // Signature on ``TALER_PaymentResponsePS`` with the public
+ // key of the merchant instance.
+ sig: EddsaSignature;
-.. http:post:: /tip-authorize
+ }
- Authorize a tip that can be picked up by the customer's wallet by POSTing to
- ``/tip-pickup``. Note that this is simply the authorization step the back
- office has to trigger first. The user should be navigated to the
``tip_redirect_url``
- to trigger tip processing in the wallet.
+ .. ts:def:: PayRequest
- **Request**
+ interface PayRequest {
+ coins: CoinPaySig[];
+ }
- The request body is a `TipCreateRequest` object.
+ .. ts:def:: CoinPaySig
- **Response**
+ export interface CoinPaySig {
+ // Signature by the coin.
+ coin_sig: string;
- :status 200 OK:
- A tip has been created. The backend responds with a `TipCreateConfirmation`
- :status 404 Not Found:
- The instance is unknown to the backend.
- :status 412 Precondition Failed:
- The tip amount requested exceeds the available reserve balance for
tipping, or
- the instance was never configured for tipping.
- :status 424 Failed Dependency:
- We are unable to process the request because of a problem with the
exchange.
- Likely returned with an "exchange_code" in addition to a "code" and
- an "exchange_http_status" in addition to our own HTTP status. Also may
- include the full exchange reply to our request under "exchange_reply".
- Naturally, those diagnostics may be omitted if the exchange did not reply
- at all, or send a completely malformed response.
- :status 503 Service Unavailable:
- We are unable to process the request, possibly due to misconfiguration or
- disagreement with the exchange (it is unclear which party is to blame).
- Likely returned with an "exchange_code" in addition to a "code" and
- an "exchange_http_status" in addition to our own HTTP status. Also may
- include the full exchange reply to our request under "exchange_reply".
+ // Public key of the coin being spend.
+ coin_pub: string;
- .. ts:def:: TipCreateRequest
+ // Signature made by the denomination public key.
+ ub_sig: string;
- interface TipCreateRequest {
- // Amount that the customer should be tipped
- amount: Amount;
+ // The denomination public key associated with this coin.
+ denom_pub: string;
- // Justification for giving the tip
- justification: string;
+ // The amount that is subtracted from this coin with this payment.
+ contribution: Amount;
- // URL that the user should be directed to after tipping,
- // will be included in the tip_token.
- next_url: string;
+ // URL of the exchange this coin was withdrawn from.
+ exchange_url: string;
}
- .. ts:def:: TipCreateConfirmation
- interface TipCreateConfirmation {
- // Token that will be handed to the wallet,
- // contains all relevant information to accept
- // a tip.
- tip_token: string;
+.. http:post:: /public/orders/$ORDER_ID/abort
- // URL that will directly trigger procesing
- // the tip when the browser is redirected to it
- tip_redirect_url: string;
- }
+ Abort paying for an order and obtain a refund for coins that
+ were already deposited as part of a failed payment.
+ **Request:**
-.. http:post:: /tip-query
+ The request must be an `abort request <AbortRequest>`.
- Query the status of a tipping reserve.
+ :query h_contract: hash of the order's contract terms (this is used to
authenticate the wallet/customer in case $ORDER_ID is guessable). *Mandatory!*
- **Response**
+ **Response:**
:status 200 OK:
- A tip has been created. The backend responds with a `TipQueryResponse`
- :status 404 Not Found:
- The instance is unknown to the backend.
+ The exchange accepted all of the coins. The body is a
+ a `merchant refund response <MerchantRefundResponse>`.
+ :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.
+ :status 404 Not found:
+ The merchant backend could not find the order or the instance
+ and thus cannot process the abort request.
:status 412 Precondition Failed:
- The merchant backend instance does not have a tipping reserve configured.
+ Aborting the payment is not allowed, as the original payment did succeed.
:status 424 Failed Dependency:
- We are unable to process the request because of a problem with the
exchange.
- Likely returned with an "exchange_code" in addition to a "code" and
- an "exchange_http_status" in addition to our own HTTP status. Also may
- include the full exchange reply to our request under "exchange_reply".
- Naturally, those diagnostics may be omitted if the exchange did not reply
- at all, or send a completely malformed response.
- :status 503 Service Unavailable:
- We are unable to process the request, possibly due to misconfiguration or
- disagreement with the exchange (it is unclear which party is to blame).
- Likely returned with an "exchange_code" in addition to a "code" and
- an "exchange_http_status" in addition to our own HTTP status. Also may
- include the full exchange reply to our request under "exchange_reply".
-
- .. ts:def:: TipQueryResponse
-
- interface TipQueryResponse {
- // Amount still available
- amount_available: Amount;
+ The merchant's interaction with the exchange failed in some way.
+ The error from the exchange is included.
- // Amount that we authorized for tips
- amount_authorized: Amount;
+ The backend will return verbatim the error codes received from the exchange's
+ :ref:`refund <refund>` API. The frontend should pass the replies verbatim to
+ the browser/wallet.
- // Amount that was picked up by users already
- amount_picked_up: Amount;
+ .. ts:def:: AbortRequest
- // Timestamp indicating when the tipping reserve will expire
- expiration: Timestamp;
+ interface AbortRequest {
+ // 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: AbortedCoin[];
+ }
- // Reserve public key of the tipping reserve
- reserve_pub: EddsaPublicKey;
+ interface AbortedCoin {
+ // Public key of a coin for which the wallet is requesting an
abort-related refund.
+ coin_pub: EddsaPublicKey;
}
-------------------------
-Tracking Wire Transfers
-------------------------
-.. http:get:: /track/transfer
+.. http:get:: /orders/$ORDER_ID/
- Provides deposits associated with a given wire transfer.
+ Merchant checks the payment status of an order. If the order exists but is
not payed
+ yet, the response provides a redirect URL. When the user goes to this URL,
+ they will be prompted for payment. Differs from the ``/public/`` API both
+ in terms of what information is returned and in that the wallet must provide
+ the contract hash to authenticate, while for this API we assume that the
+ merchant is authenticated (as the endpoint is not ``/public/``).
- **Request**
+ **Request:**
- :query wtid: raw wire transfer identifier identifying the wire transfer (a
base32-encoded value)
- :query wire_method: name of the wire transfer method used for the wire
transfer
- :query exchange: base URL of the exchange that made the wire transfer
+ :query session_id: *Optional*. Session ID that the payment must be bound to.
If not specified, the payment is not session-bound.
+ :query transfer: *Optional*. If set to "YES", try to obtain the wire
transfer status for this order from the exchange. Otherwise, the wire transfer
status MAY be returned if it is available.
+ :query timeout_ms: *Optional*. Timeout in milli-seconds to wait for a
payment if the answer would otherwise be negative (long polling).
**Response:**
:status 200 OK:
- The wire transfer is known to the exchange, details about it follow in the
body.
- The body of the response is a `TrackTransferResponse`. Note that
- the similarity to the response given by the exchange for a /track/transfer
- is completely intended.
-
+ Returns a `MerchantOrderStatusResponse`, whose format can differ based on
the status of the payment.
:status 404 Not Found:
- The wire transfer identifier is unknown to the exchange.
+ The order or instance is unknown to the backend.
+ :status 409 Conflict:
+ The exchange previously claimed that a deposit was not included in a wire
+ transfer, and now claims that it is. This means that the exchange is
+ dishonest. The response contains the cryptographic proof that the exchange
+ is misbehaving in the form of a `TransactionConflictProof`.
+ :status 424 Failed dependency:
+ We failed to obtain a response from the exchange about the
+ wire transfer status.
- :status 424 Failed Dependency: The exchange provided conflicting information
about the transfer. Namely,
- there is at least one deposit among the deposits aggregated by ``wtid``
that accounts for a coin whose
- details don't match the details stored in merchant's database about the
same keyed coin.
- The response body contains the `TrackTransferConflictDetails`.
+ .. ts:def:: MerchantOrderStatusResponse
- .. ts:def:: TrackTransferResponse
+ type MerchantOrderStatusResponse = CheckPaymentPaidResponse |
CheckPaymentUnpaidResponse
- interface TrackTransferResponse {
- // Total amount transferred
- total: Amount;
+ .. ts:def:: CheckPaymentPaidResponse
- // Applicable wire fee that was charged
- wire_fee: Amount;
+ interface CheckPaymentPaidResponse {
+ paid: true;
- // public key of the merchant (identical for all deposits)
- merchant_pub: EddsaPublicKey;
+ // Was the payment refunded (even partially)
+ refunded: boolean;
- // hash of the wire details (identical for all deposits)
- h_wire: HashCode;
-
- // Time of the execution of the wire transfer by the exchange
- execution_time: Timestamp;
-
- // details about the deposits
- deposits_sums: TrackTransferDetail[];
-
- // signature from the exchange made with purpose
- // ``TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT``
- 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. Again
given
- // explicitly as the client might otherwise be confused by clock skew as
to
- // which signing key was used.
- exchange_pub: EddsaSignature;
- }
-
- .. ts:def:: TrackTransferDetail
-
- interface TrackTransferDetail {
- // Business activity associated with the wire transferred amount
- // ``deposit_value``.
- order_id: string;
+ // Amount that was refunded, only present if refunded is true.
+ refund_amount?: Amount;
- // The total amount the exchange paid back for ``order_id``.
- deposit_value: Amount;
+ // Contract terms
+ contract_terms: ContractTerms;
- // applicable fees for the deposit
- deposit_fee: Amount;
+ // If available, the wire transfer status from the exchange for this
order
+ wire_details?: TransactionWireTransfer;
}
+ .. ts:def:: CheckPaymentUnpaidResponse
- **Details:**
-
- .. ts:def:: TrackTransferConflictDetails
-
- interface TrackTransferConflictDetails {
- // Numerical `error code <error-codes>`
- code: number;
-
- // Text describing the issue for humans.
- hint: string;
-
- // A /deposit response matching ``coin_pub`` showing that the
- // exchange accepted ``coin_pub`` for ``amount_with_fee``.
- exchange_deposit_proof: DepositSuccess;
-
- // Offset in the ``exchange_transfer_proof`` where the
- // exchange's response fails to match the ``exchange_deposit_proof``.
- conflict_offset: number;
-
- // The response from the exchange which tells us when the
- // coin was returned to us, except that it does not match
- // the expected value of the coin.
- exchange_transfer_proof: TrackTransferResponse;
-
- // Public key of the coin for which we have conflicting information.
- coin_pub: EddsaPublicKey;
-
- // Merchant transaction in which ``coin_pub`` was involved for which
- // we have conflicting information.
- transaction_id: number;
+ interface CheckPaymentUnpaidResponse {
+ paid: false;
- // Expected value of the coin.
- amount_with_fee: Amount;
+ // URI that the wallet must process to complete the payment.
+ taler_pay_uri: string;
- // Expected deposit fee of the coin.
- deposit_fee: Amount;
+ // 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;
+ // FIXME: why do we NOT return the contract terms here?
}
-
-.. http:get:: /track/transaction
-
- Provide the wire transfer identifier associated with an (existing) deposit
operation.
-
- **Request:**
-
- :query id: ID of the transaction we want to trace (an integer)
-
- **Response:**
-
- :status 200 OK:
- The deposit has been executed by the exchange and we have a wire transfer
identifier.
- The response body is a JSON array of `TransactionWireTransfer` objects.
- :status 202 Accepted:
- The deposit request has been accepted for processing, but was not yet
- executed. Hence the exchange does not yet have a wire transfer identifier.
- The merchant should come back later and ask again.
- The response body is a `TrackTransactionAcceptedResponse
<TrackTransactionAcceptedResponse>`. Note that
- the similarity to the response given by the exchange for a /track/order
- is completely intended.
- :status 404 Not Found: The transaction is unknown to the backend.
- :status 424 Failed Dependency:
- The exchange previously claimed that a deposit was not included in a wire
- transfer, and now claims that it is. This means that the exchange is
- dishonest. The response contains the cryptographic proof that the exchange
- is misbehaving in the form of a `TransactionConflictProof`.
-
- **Details:**
-
.. ts:def:: TransactionWireTransfer
interface TransactionWireTransfer {
@@ -467,19 +460,6 @@ Tracking Wire Transfers
amount: Amount;
}
- .. ts:def:: CoinWireTransfer
-
- interface CoinWireTransfer {
- // public key of the coin that was deposited
- coin_pub: EddsaPublicKey;
-
- // Amount the coin was worth (including deposit fee)
- amount_with_fee: Amount;
-
- // Deposit fee retained by the exchange for the coin
- deposit_fee: Amount;
- }
-
.. ts:def:: TransactionConflictProof
interface TransactionConflictProof {
@@ -507,843 +487,1033 @@ Tracking Wire Transfers
}
--------------------
-Transaction history
--------------------
+.. http:get:: /public/orders/$ORDER_ID/
-.. http:get:: /history
+ 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.
- Returns transactions up to some point in the past
+ // FIXME: note that this combines the previous APIs
+ // to check-payment and to obtain refunds.
**Request**
- :query date: time threshold, see ``delta`` for its interpretation.
- :query start: row number threshold, see ``delta`` for its interpretation.
Defaults to ``UINT64_MAX``, namely the biggest row id possible in the database.
- :query delta: takes value of the form ``N (-N)``, so that at most ``N``
values strictly younger (older) than ``start`` and ``date`` are returned.
Defaults to ``-20``.
- :query ordering: takes value ``"descending"`` or ``"ascending"`` according
to the results wanted from younger to older or vice versa. Defaults to
``"descending"``.
+ :query h_contract: hash of the order's contract terms (this is used to
authenticate the wallet/customer in case $ORDER_ID is guessable). *Mandatory!*
+ :query session_id: *Optional*. Session ID that the payment must be bound to.
If not specified, the payment is not session-bound.
+ :query timeout_ms: *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.
**Response**
:status 200 OK:
- The response is a JSON ``array`` of `TransactionHistory`. The array is
- sorted such that entry ``i`` is younger than entry ``i+1``.
-
- .. ts:def:: TransactionHistory
+ The response is a `PublicPayStatusResponse`, with ``paid`` true.
+ FIXME: what about refunded?
+ :status 402 Payment required:
+ The response is a `PublicPayStatusResponse`, with ``paid`` false.
+ FIXME: what about refunded?
+ :status 403 Forbidden:
+ The ``h_contract`` does not match the order.
+ :status 404 Not found:
+ The merchant backend is unaware of the order.
- interface TransactionHistory {
- // The serial number this entry has in the merchant's DB.
- row_id: number;
+ .. ts:def:: PublicPayStatusResponse
- // order ID of the transaction related to this entry.
- order_id: string;
+ interface PublicPayStatusResponse {
+ // Has the payment for this order (ever) been completed?
+ paid: boolean;
- // Transaction's timestamp
- timestamp: Timestamp;
+ // Was the payment refunded (even partially, via refund or abort)?
+ refunded: boolean;
- // Total amount associated to this transaction.
- amount: Amount;
- }
+ // Amount that was refunded in total.
+ refund_amount: Amount;
-.. _proposal:
+ // Refunds for this payment, empty array for none.
+ refunds: RefundDetail[];
+ // URI that the wallet must process to complete the payment.
+ taler_pay_uri: string;
--------------------------
-Dynamic Merchant Instance
--------------------------
+ // 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;
-.. note::
+ }
- The endpoints to dynamically manage merchant instances has not been
- implemented yet. The bug id for this refernce is 5349.
-.. http:get:: /instances
+.. http:delete:: /orders/$ORDER_ID
- This is used to return the list of all the merchant instances
+ Delete information about an order. Fails if the order was paid in the
+ last 10 years (or whatever TAX_RECORD_EXPIRATION is set to) or was
+ claimed but is unpaid and thus still a valid offer.
**Response**
- :status 200 OK:
- The backend has successfully returned the list of instances stored. Returns
- a `InstancesResponse`.
-
- .. ts:def:: InstancesResponse
-
- interface InstancesResponse {
- // List of instances that are present in the backend (see `Instance`)
- instances: Instance[];
- }
-
- The `Instance` object describes the instance registered with the backend. It
has the following structure:
-
- .. ts:def:: Instance
-
- interface Instance {
- // Merchant name corresponding to this instance.
- name: string;
-
- // The URL where the wallet will send coins.
- payto: string;
+ :status 204 No content:
+ The backend has successfully deleted the order.
+ :status 404 Not found:
+ The backend does not know the instance or the order.
+ :status 409 Conflict:
+ The backend refuses to delete the order.
- // Merchant instance of the response to create
- instance: string;
- //unique key for each merchant
- merchant_id: string;
- }
+--------------
+Giving Refunds
+--------------
-.. http:put:: /instances/
+.. http:post:: /orders/$ORDER_ID/refund
- This request will be used to create a new merchant instance in the backend.
+ Increase the refund amount associated with a given order. The user should be
+ redirected to the ``taler_refund_url`` to trigger refund processing in the
wallet.
**Request**
- The request must be a `CreateInstanceRequest`.
+ The request body is a `RefundRequest` object.
**Response**
:status 200 OK:
- The backend has successfully created the instance. The response is a
- `CreateInstanceResponse`.
-
- .. ts:def:: CreateInstanceRequest
-
- interface CreateInstanceRequest {
- // The URL where the wallet has to send coins.
- // payto://-URL of the merchant's bank account. Required.
- payto: string;
+ The refund amount has been increased, the backend responds with a
`MerchantRefundResponse`
+ :status 404 Not found:
+ The order is unknown to the merchant
+ :status 409 Conflict:
+ The refund amount exceeds the amount originally paid
- // Merchant instance of the response to create
- // This field is optional. If it is not specified
- // then it will automatically be created.
- instance?: string;
+ .. ts:def:: RefundRequest
- // Merchant name corresponding to this instance.
- name: string;
+ interface RefundRequest {
+ // Amount to be refunded
+ refund: Amount;
+ // Human-readable refund justification
+ reason: string;
}
- .. ts:def:: CreateInstanceResponse
+ .. ts:def:: MerchantRefundResponse
- interface CreateInstanceResponse {
- // Merchant instance of the response that was created
- instance: string;
+ interface MerchantRefundResponse {
- //unique key for each merchant
- merchant_id: string;
+ // Hash of the contract terms of the contract that is being refunded.
+ // FIXME: why do we return this?
+ h_contract_terms: HashCode;
+
+ // URL (handled by the backend) that the wallet should access to
+ // trigger refund processing.
+ // FIXME: isn't this basically now always ``/public/orders/$ORDER_ID/``?
+ // If so, why return this?
+ taler_refund_url: string;
}
-.. http:get:: /instances/<instance-id>
- This is used to query a specific merchant instance.
+------------------------
+Tracking Wire Transfers
+------------------------
+
+.. http:post:: /check-transfer
+
+ Inform the backend over an incoming wire transfer. The backend should
inquire about the details with the exchange and mark the respective orders as
wired.
**Request:**
- :query instance_id: instance id that should be used for the instance
+ The request must provide `transfer information <TransferInformation>`.
- **Response**
+ **Response:**
:status 200 OK:
- The backend has successfully returned the list of instances stored. Returns
- a `QueryInstancesResponse`.
-
- .. ts:def:: QueryInstancesResponse
+ The wire transfer is known to the exchange, details about it follow in the
body.
+ The body of the response is a `TrackTransferResponse`. Note that
+ the similarity to the response given by the exchange for a /track/transfer
+ is completely intended.
- interface QueryInstancesResponse {
- // The URL where the wallet has to send coins.
- // payto://-URL of the merchant's bank account. Required.
- payto: string;
+ :status 404 Not Found:
+ The wire transfer identifier is unknown to the exchange.
- // Merchant instance of the response to create
- // This field is optional. If it is not specified
- // then it will automatically be created.
- instance?: string;
+ :status 424 Failed Dependency: The exchange provided conflicting information
about the transfer. Namely,
+ there is at least one deposit among the deposits aggregated by ``wtid``
that accounts for a coin whose
+ details don't match the details stored in merchant's database about the
same keyed coin.
+ The response body contains the `TrackTransferConflictDetails`.
- // Merchant name corresponding to this instance.
- name: string;
+ .. ts:def:: TransferInformation
- }
+ interface TransferInformation {
+ // how much was wired to the merchant (minus fees)
+ credit_amount: Amount;
+ // raw wire transfer identifier identifying the wire transfer (a
base32-encoded value)
+ wtid: FIXME;
-.. http:post:: /instances/<instance-id>
+ // name of the wire transfer method used for the wire transfer
+ // FIXME: why not a payto URI?
+ wire_method;
- This request will be used to update merchant instance in the backend.
+ // base URL of the exchange that made the wire transfer
+ exchange: string;
+ }
+ .. ts:def:: TrackTransferResponse
- **Request**
+ interface TrackTransferResponse {
+ // Total amount transferred
+ total: Amount;
- The request must be a `PostInstanceUpdateRequest`.
+ // Applicable wire fee that was charged
+ wire_fee: Amount;
- **Response**
+ // public key of the merchant (identical for all deposits)
+ // FIXME: why return this?
+ merchant_pub: EddsaPublicKey;
- :status 200 OK:
- The backend has successfully updated the instance. The response is a
- `PostInstanceUpdateResponse`.
+ // hash of the wire details (identical for all deposits)
+ // FIXME: why return this? Isn't this the WTID!?
+ h_wire: HashCode;
- .. ts:def:: PostInstanceUpdateRequest
+ // Time of the execution of the wire transfer by the exchange, according
to the exchange
+ execution_time: Timestamp;
- interface PostInstanceUpdateRequest {
- // Merchant instance that is to be updaated. Required.
- instance: string;
+ // details about the deposits
+ deposits_sums: TrackTransferDetail[];
- // New URL where the wallet has to send coins.
- // payto://-URL of the merchant's bank account. Required.
- payto: string;
+ // signature from the exchange made with purpose
+ // ``TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT``
+ // FIXME: why return this?
+ exchange_sig: EddsaSignature;
- // Merchant name coreesponding to this instance.
- name: string;
+ // public EdDSA key of the exchange that was used to generate the
signature.
+ // Should match one of the exchange's signing keys from /keys. Again
given
+ // explicitly as the client might otherwise be confused by clock skew as
to
+ // which signing key was used.
+ // FIXME: why return this?
+ exchange_pub: EddsaSignature;
+ }
+ .. ts:def:: TrackTransferDetail
+
+ interface TrackTransferDetail {
+ // Business activity associated with the wire transferred amount
+ // ``deposit_value``.
+ order_id: string;
+
+ // The total amount the exchange paid back for ``order_id``.
+ deposit_value: Amount;
+
+ // applicable fees for the deposit
+ deposit_fee: Amount;
}
- .. ts:def:: PostInstanceUpdateResponse
- interface PostInstanceUpdateResponse {
- // Merchant instance of the response that was updated
- instance: string;
+ **Details:**
+
+ .. ts:def:: TrackTransferConflictDetails
+
+ interface TrackTransferConflictDetails {
+ // Numerical `error code <error-codes>`
+ code: number;
+
+ // Text describing the issue for humans.
+ hint: string;
+
+ // A /deposit response matching ``coin_pub`` showing that the
+ // exchange accepted ``coin_pub`` for ``amount_with_fee``.
+ exchange_deposit_proof: DepositSuccess;
+
+ // Offset in the ``exchange_transfer_proof`` where the
+ // exchange's response fails to match the ``exchange_deposit_proof``.
+ conflict_offset: number;
+
+ // The response from the exchange which tells us when the
+ // coin was returned to us, except that it does not match
+ // the expected value of the coin.
+ exchange_transfer_proof: TrackTransferResponse;
+
+ // Public key of the coin for which we have conflicting information.
+ coin_pub: EddsaPublicKey;
+
+ // Merchant transaction in which ``coin_pub`` was involved for which
+ // we have conflicting information.
+ transaction_id: number;
+
+ // Expected value of the coin.
+ amount_with_fee: Amount;
+
+ // Expected deposit fee of the coin.
+ deposit_fee: Amount;
- //unique key for each merchant
- merchant_id: string;
}
-.. http:delete:: /instances/<instance-id>
+.. http:get:: /transfers
- This request will be used to delete merchant instance in the backend.
+ Obtain a list of all wire transfers the backend has checked.
**Request:**
- :query instance_id: instance id that should be used for the instance
+ :query filter: FIXME: should have a way to filter, maybe even long-poll?
- **Response**
+ **Response:**
+
+ FIXME: to be specified.
+
+
+
+--------------------
+Giving Customer Tips
+--------------------
+
+
+.. http:post:: /create-reserve
+
+ Create a reserve for tipping.
+
+ **Request:**
+
+ The request body is a `ReserveCreateRequest` object.
+
+ **Response:**
:status 200 OK:
- The backend has successfully removed the instance. The response is a
- `PostInstanceRemoveResponse`.
+ The backend is waiting for the reserve to be established. The merchant
+ must now perform the wire transfer indicated in the
`ReserveCreateConfirmation`.
+ :status 424 Failed Depencency:
+ We could not obtain /wire details from the specified exchange base URL.
- .. ts:def:: PostInstanceRemoveResponse
+ .. ts:def:: ReserveCreateRequest
+
+ interface ReserveCreateRequest {
+ // Amount that the merchant promises to put into the reserve
+ initial_amount: Amount;
+
+ // Exchange the merchant intends to use for tipping
+ exchange_base_url: string;
- interface PostInstanceRemoveResponse {
- deleted: true;
}
+ .. ts:def:: ReserveCreateConfirmation
-------------------
-The Contract Terms
-------------------
+ interface ReserveCreateConfirmation {
+ // Public key identifying the reserve
+ reserve_pub: EddsaPublicKey;
-The contract terms must have the following structure:
+ // Wire account of the exchange where to transfer the funds
+ payto_url: string;
- .. ts:def:: ContractTerms
+ }
- interface ContractTerms {
- // Human-readable description of the whole purchase
- summary: string;
+.. http:get:: /reserves
- // Map from IETF BCP 47 language tags to localized summaries
- summary_i18n?: { [lang_tag: string]: string };
+ Obtain list of reserves that have been created for tipping.
- // Unique, free-form identifier for the proposal.
- // Must be unique within a merchant instance.
- // For merchants that do not store proposals in their DB
- // before the customer paid for them, the order_id can be used
- // by the frontend to restore a proposal from the information
- // encoded in it (such as a short product identifier and timestamp).
- order_id: string;
+ **Request:**
- // Total price for the transaction.
- // The exchange will subtract deposit fees from that amount
- // before transferring it to the merchant.
+ :query after: *Optional*. Only return reserves created after the given
timestamp [FIXME: unit?]
+
+ **Response:**
+
+ :status 200 OK:
+ Returns a list of known tipping reserves.
+ The body is a `TippingReserveStatus`.
+
+ .. ts:def:: TippingReserveStatus
+
+ interface TippingReserveStatus {
+
+ // Array of all known reserves (possibly empty!)
+ reserves: ReserveStatusEntry[];
+
+ }
+
+ .. ts:def:: ReserveStatusEntry
+
+ interface ReserveStatusEntry {
+
+ // Public key of the reserve
+ reserve_pub: EddsaPublicKey;
+
+ // Timestamp when it was established
+ creation_time: Timestamp;
+
+ // Timestamp when it expires
+ expiration_time: Timestamp;
+
+ // Initial amount as per reserve creation call
+ merchant_initial_amount: Amount;
+
+ // Initial amount as per exchange, 0 if exchange did
+ // not confirm reserve creation yet.
+ exchange_initial_amount: Amount;
+
+ // Amount picked up so far.
+ pickup_amount: Amount;
+
+ // Amount approved for tips that exceeds the pickup_amount.
+ committed_amount: Amount;
+
+ }
+
+
+.. http:get:: /reserves/$RESERVE_PUB
+
+ Obtain information about a specific reserve that have been created for
tipping.
+
+ **Request:**
+
+ :query tips: *Optional*. If set to "yes", returns also information about
all of the tips created
+
+ **Response:**
+
+ :status 200 OK:
+ Returns the `ReserveDetail`.
+ :status 404 Not found:
+ The tipping reserve is not known.
+ :status 424 Failed Dependency:
+ We are having trouble with the request because of a problem with the
exchange.
+ Likely returned with an "exchange_code" in addition to a "code" and
+ an "exchange_http_status" in addition to our own HTTP status. Also usually
+ includes the full exchange reply to our request under "exchange_reply".
+ This is only returned if there was actual trouble with the exchange, not
+ if the exchange merely did not respond yet or if it responded that the
+ reserve was not yet filled.
+
+ .. ts:def:: ReserveDetail
+
+ interface ReserveDetail {
+
+ // Timestamp when it was established
+ creation_time: Timestamp;
+
+ // Timestamp when it expires
+ expiration_time: Timestamp;
+
+ // Initial amount as per reserve creation call
+ merchant_initial_amount: Amount;
+
+ // Initial amount as per exchange, 0 if exchange did
+ // not confirm reserve creation yet.
+ exchange_initial_amount: Amount;
+
+ // Amount picked up so far.
+ pickup_amount: Amount;
+
+ // Amount approved for tips that exceeds the pickup_amount.
+ committed_amount: Amount;
+
+ // Array of all tips created by this reserves (possibly empty!).
+ // Only present if asked for explicitly.
+ tips?: TipStatusEntry[];
+
+ }
+
+ .. ts:def:: TipStatusEntry
+
+ interface TipStatusEntry {
+
+ // Unique identifier for the tip
+ tip_id: HashCode;
+
+ // Total amount of the tip that can be withdrawn.
+ total_amount: Amount;
+
+ // Human-readable reason for why the tip was granted.
+ reason: String;
+
+ }
+
+
+.. http:post:: /reserves/$RESERVE_PUB/authorize-tip
+
+ Authorize creation of a tip from the given reserve.
+
+ **Request:**
+
+ The request body is a `TipCreateRequest` object.
+
+ **Response**
+
+ :status 200 OK:
+ A tip has been created. The backend responds with a `TipCreateConfirmation`
+ :status 404 Not Found:
+ The instance or the reserve is unknown to the backend.
+ :status 412 Precondition Failed:
+ The tip amount requested exceeds the available reserve balance for tipping.
+
+ .. ts:def:: TipCreateRequest
+
+ interface TipCreateRequest {
+ // Amount that the customer should be tipped
amount: Amount;
- // The URL for this purchase. Every time is is visited, the merchant
- // will send back to the customer the same proposal. Clearly, this URL
- // can be bookmarked and shared by users.
- fulfillment_url: string;
+ // Justification for giving the tip
+ justification: string;
- // Maximum total deposit fee accepted by the merchant for this contract
- max_fee: Amount;
+ // URL that the user should be directed to after tipping,
+ // will be included in the tip_token.
+ next_url: string;
+ }
- // Maximum wire fee accepted by the merchant (customer share to be
- // divided by the 'wire_fee_amortization' factor, and further reduced
- // if deposit fees are below 'max_fee'). Default if missing is zero.
- max_wire_fee: Amount;
+ .. ts:def:: TipCreateConfirmation
- // Over how many customer transactions does the merchant expect to
- // amortize wire fees on average? If the exchange's wire fee is
- // above 'max_wire_fee', the difference is divided by this number
- // to compute the expected customer's contribution to the wire fee.
- // The customer's contribution may further be reduced by the difference
- // between the 'max_fee' and the sum of the actual deposit fees.
- // Optional, default value if missing is 1. 0 and negative values are
- // invalid and also interpreted as 1.
- wire_fee_amortization: number;
+ interface TipCreateConfirmation {
+ // Unique tip identifier for the tip that was created.
+ tip_id: HashCode;
- // List of products that are part of the purchase (see `Product`).
- products: Product[];
+ // Token that will be handed to the wallet,
+ // contains all relevant information to accept
+ // a tip.
+ tip_token: string;
- // Time when this contract was generated
- timestamp: Timestamp;
+ // URL that will directly trigger processing
+ // the tip when the browser is redirected to it
+ tip_redirect_url: string;
- // After this deadline has passed, no refunds will be accepted.
- refund_deadline: Timestamp;
+ }
- // After this deadline, the merchant won't accept payments for the
contact
- pay_deadline: Timestamp;
- // Transfer deadline for the exchange. Must be in the
- // deposit permissions of coins used to pay for this order.
- wire_transfer_deadline: Timestamp;
+.. http:delete:: /reserves/$RESERVE_PUB
- // Merchant's public key used to sign this proposal; this information
- // is typically added by the backend Note that this can be an ephemeral
key.
- merchant_pub: EddsaPublicKey;
+ Delete information about a reserve. Fails if the reserve still has
+ committed to tips that were not yet picked up and that have not yet
+ expired.
- // Base URL of the (public!) merchant backend API.
- // Must be an absolute URL that ends with a slash.
- merchant_base_url: string;
+ **Response**
- // More info about the merchant, see below
- merchant: Merchant;
+ :status 204 No content:
+ The backend has successfully deleted the reserve.
+ :status 404 Not found:
+ The backend does not know the instance or the reserve.
+ :status 409 Conflict:
+ The backend refuses to delete the reserve (committed tips).
- // The hash of the merchant instance's wire details.
- h_wire: HashCode;
- // Wire transfer method identifier for the wire method associated with
h_wire.
- // The wallet may only select exchanges via a matching auditor if the
- // exchange also supports this wire method.
- // The wire transfer fees must be added based on this wire transfer
method.
- wire_method: string;
- // Any exchanges audited by these auditors are accepted by the merchant.
- auditors: Auditor[];
+.. http:get:: /tips/$TIP_ID
- // Exchanges that the merchant accepts even if it does not accept any
auditors that audit them.
- exchanges: Exchange[];
+ Obtain information about a particular tip.
- // Map from labels to locations
- locations: { [label: string]: [location: Location], ... };
+ **Request:**
- // Nonce generated by the wallet and echoed by the merchant
- // in this field when the proposal is generated.
- nonce: string;
+ :query pickups: if set to "yes", returns also information about all of the
pickups
- // Specifies for how long the wallet should try to get an
- // automatic refund for the purchase. If this field is
- // present, the wallet should wait for a few seconds after
- // the purchase and then automatically attempt to obtain
- // a refund. The wallet should probe until "delay"
- // after the payment was successful (i.e. via long polling
- // or via explicit requests with exponential back-off).
- //
- // In particular, if the wallet is offline
- // at that time, it MUST repeat the request until it gets
- // one response from the merchant after the delay has expired.
- // If the refund is granted, the wallet MUST automatically
- // recover the payment. This is used in case a merchant
- // knows that it might be unable to satisfy the contract and
- // desires for the wallet to attempt to get the refund without any
- // customer interaction. Note that it is NOT an error if the
- // merchant does not grant a refund.
- auto_refund?: RelativeTime;
+ **Response**
- // Extra data that is only interpreted by the merchant frontend.
- // Useful when the merchant needs to store extra information on a
- // contract without storing it separately in their database.
- extra?: any;
+ :status 200 OK:
+ The tip is known. The backend responds with a `TipDetails` message
+ :status 404 Not Found:
+ The tip is unknown to the backend.
+
+ .. ts:def:: TipDetails
+
+ interface TipDetails {
+
+ // Amount that we authorized for this tip.
+ total_authorized: Amount;
+
+ // Amount that was picked up by the user already.
+ total_picked_up: Amount;
+
+ // Human-readable reason given when authorizing the tip.
+ reason: String;
+
+ // Timestamp indicating when the tip is set to expire (may be in the
past).
+ expiration: Timestamp;
+
+ // Reserve public key from which the tip is funded
+ reserve_pub: EddsaPublicKey;
+
+ // Array showing the pickup operations of the wallet (possibly empty!).
+ // Only present if asked for explicitly.
+ pickups?: PickupDetail[];
}
- The wallet must select a exchange that either the merchant accepts directly
by
- listing it in the exchanges array, or for which the merchant accepts an
auditor
- that audits that exchange by listing it in the auditors array.
+ .. ts:def:: PickupDetail
- The `Product` object describes the product being purchased from the
merchant. It has the following structure:
+ interface PickupDetail {
- .. ts:def:: Product
+ // Unique identifier for the pickup operation.
+ pickup_id: HashCode;
- interface Product {
- // Human-readable product description.
- description: string;
+ // Number of planchets involved.
+ num_planchets: integer;
- // Map from IETF BCP 47 language tags to localized descriptions
- description_i18n?: { [lang_tag: string]: string };
+ // Total amount requested for this pickup_id.
+ requested_amount: Amount;
- // The quantity of the product to deliver to the customer (optional, if
applicable)
- quantity?: string;
+ // Total amount processed by the exchange for this pickup.
+ exchange_amount: Amount;
- // The price of the product; this is the total price for the amount
specified by 'quantity'
- price: Amount;
+ }
+
+
+.. http:post:: /public/tips/$TIP_ID/pickup
+
+ Handle request from wallet to pick up a tip.
+
+ **Request**
+
+ The request body is a `TipPickupRequest` object.
+
+ **Response**
- // merchant-internal identifier for the product
- product_id?: string;
+ :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).
- // An optional base64-encoded product image
- image?: ImageDataUrl;
+ .. ts:def:: TipPickupRequest
- // a list of objects indicating a 'taxname' and its amount. Again,
italics denotes the object field's name.
- taxes?: any[];
+ interface TipPickupRequest {
- // time indicating when this product should be delivered
- delivery_date: Timestamp;
+ // Identifier of the tip.
+ tip_id: HashCode;
- // where to deliver this product. This may be an URL for online delivery
- // (i.e. 'http://example.com/download' or 'mailto:address@hidden'),
- // or a location label defined inside the proposition's 'locations'.
- // The presence of a colon (':') indicates the use of an URL.
- delivery_location: string;
+ // List of planches the wallet wants to use for the tip
+ planchets: PlanchetDetail[];
}
- .. ts:def:: Merchant
+ .. ts:def:: PlanchetDetail
- interface Merchant {
- // label for a location with the business address of the merchant
- address: string;
+ interface PlanchetDetail {
+ // Hash of the denomination's public key (hashed to reduce
+ // bandwidth consumption)
+ denom_pub_hash: HashCode;
- // the merchant's legal name of business
- name: string;
+ // coin's blinded public key
+ coin_ev: CoinEnvelope;
- // label for a location that denotes the jurisdiction for disputes.
- // Some of the typical fields for a location (such as a street address)
may be absent.
- jurisdiction: string;
}
+ .. ts:def:: TipResponse
- .. ts:def:: Location
+ interface TipResponse {
- interface Location {
- country?: string;
- city?: string;
- state?: string;
- region?: string;
- province?: string;
- zip_code?: string;
- street?: string;
- street_number?: string;
+ // Blind RSA signatures over the planchets.
+ // The order of the signatures matches the planchets list.
+ blind_sigs: BlindSignature[];
}
- .. ts:def:: Auditor
-
- interface Auditor {
- // official name
- name: string;
-
- // Auditor's public key
- auditor_pub: EddsaPublicKey;
+ interface BlindSignature {
- // Base URL of the auditor
- url: string;
+ // The (blind) RSA signature. Still needs to be unblinded.
+ blind_sig: RsaSignature;
}
- .. ts:def:: Exchange
- interface Exchange {
- // the exchange's base URL
- url: string;
- // master public key of the exchange
- master_pub: EddsaPublicKey;
- }
--------------------
-Customer-facing API
--------------------
+-------------------------
+Dynamic Merchant Instance
+-------------------------
-The ``/public/*`` endpoints are publicly exposed on the internet and accessed
-both by the user's browser and their wallet.
+.. note::
+ The endpoints to dynamically manage merchant instances has not been
+ implemented yet. The bug id for this reference is #5349.
-.. http:get:: /public/config
+.. http:get:: /instances
- Return the protocol version and currency supported by this merchant backend.
+ This is used to return the list of all the merchant instances
- **Response:**
+ **Response**
:status 200 OK:
- The exchange accepted all of the coins. The body is a `VersionResponse`.
-
- .. ts:def:: VersionResponse
-
- interface VersionResponse {
- // libtool-style representation of the Merchant protocol version, see
- //
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
- // The format is "current:revision:age".
- version: string;
+ The backend has successfully returned the list of instances stored. Returns
+ a `InstancesResponse`.
- // Currency supported by this backend.
- currency: string;
+ .. ts:def:: InstancesResponse
- // optional array with information about the instances running at this
backend
- instances: InstanceInformation[];
+ interface InstancesResponse {
+ // List of instances that are present in the backend (see `Instance`)
+ instances: Instance[];
}
- .. ts:def:: InstanceInformation
+ The `Instance` object describes the instance registered with the backend. It
has the following structure:
- interface InstanceInformation {
+ .. ts:def:: Instance
- // Human-readable legal business name served by this instance
+ interface Instance {
+ // Merchant name corresponding to this instance.
name: string;
- // Base URL of the instance. Can be of the form "/PizzaShop/" or
- // a fully qualified URL (i.e. "https://backend.example.com/PizzaShop/").
- instance_baseurl: string;
-
- // Public key of the merchant/instance, in Crockford Base32 encoding.
- merchant_pub: EddsaPublicKey;
-
- // List of the payment targets supported by this instance. Clients can
- // specify the desired payment target in /order requests. Note that
- // front-ends do not have to support wallets selecting payment targets.
- payment_targets: string[];
+ // The URL where the wallet will send coins.
+ payto: string;
- // Base URL of the exchange this instance uses for tipping.
- // Optional, only present if the instance supports tipping.
- tipping_exchange_baseurl?: string;
+ // Merchant instance of the response to create
+ instance: string;
+ //unique key for each merchant
+ merchant_id: string;
}
-.. http:post:: /public/pay
+.. http:put:: /instances/$INSTANCE
- Pay for a proposal by giving a deposit permission for coins. Typically used
by
- the customer's wallet. Can also be used in ``abort-refund`` mode to refund
coins
- that were already deposited as part of a failed payment.
+ This request will be used to create a new merchant instance in the backend.
- **Request:**
+ **Request**
- The request must be a `pay request <PayRequest>`.
+ The request must be a `CreateInstanceRequest`.
- **Response:**
+ **Response**
:status 200 OK:
- The exchange accepted all of the coins. The body is a `PaymentResponse` if
- the request used the mode "pay", or a `MerchantRefundResponse` if the
- request used was the mode "abort-refund".
- The ``frontend`` should now fullfill the contract.
- :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 401 Unauthorized:
- One of the coin signatures was not valid.
- :status 403 Forbidden:
- 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 404 Not found:
- The merchant backend could not find the proposal or the instance
- and thus cannot process the payment.
- :status 412 Precondition Failed:
- The given exchange is not acceptable for this merchant, as it is not in the
- list of accepted exchanges and not audited by an approved auditor.
- :status 424 Failed Dependency:
- The merchant's interaction with the exchange failed in some way.
- The client might want to try later again.
- This includes failures like the denomination key of a coin not being
- known to the exchange as far as the merchant can tell.
+ The backend has successfully created the instance. The response is a
+ `CreateInstanceResponse`.
- 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. This should be the expected case, as the ``frontend``
- cannot really make mistakes; the only reasonable exception is if the
- ``backend`` is unavailable, in which case the customer might appreciate some
- reassurance that the merchant is working on getting his systems back online.
+ .. ts:def:: CreateInstanceRequest
- .. ts:def:: PaymentResponse
+ interface CreateInstanceRequest {
+ // The URL where the wallet has to send coins.
+ // payto://-URL of the merchant's bank account. Required.
+ // FIXME: need an array, and to distinguish between
+ // supported and active (see taler.conf options on accounts!)
+ payto: string;
- interface PaymentResponse {
- // Signature on `TALER_PaymentResponsePS` with the public
- // key of the merchant instance.
- sig: EddsaSignature;
+ // Merchant instance of the response to create
+ // This field is optional. If it is not specified
+ // then it will automatically be created.
+ // FIXME: I do not understand this argument. -CG
+ instance?: string;
+
+ // Merchant name corresponding to this instance.
+ name: string;
- // Contract terms hash being signed over.
- h_contract_terms: HashCode;
}
- .. ts:def:: PayRequest
+ .. ts:def:: CreateInstanceResponse
- interface PayRequest {
- coins: CoinPaySig[];
+ interface CreateInstanceResponse {
+ // Merchant instance of the response that was created
+ // FIXME: I do not understand this value, isn't it implied?
+ instance: string;
- // The merchant public key, used to uniquely
- // identify the merchant instance.
- merchant_pub: string;
+ //unique key for each merchant
+ // FIXME: I do not understand this value.
+ merchant_id: string;
+ }
- // Order ID that's being payed for.
- order_id: string;
- // Mode for /pay ("pay" or "abort-refund")
- mode: "pay" | "abort-refund";
- }
+.. http:get:: /instances/<instance-id>
- .. ts:def:: CoinPaySig
+ This is used to query a specific merchant instance.
- export interface CoinPaySig {
- // Signature by the coin.
- coin_sig: string;
+ **Request:**
- // Public key of the coin being spend.
- coin_pub: string;
+ :query instance_id: instance id that should be used for the instance
- // Signature made by the denomination public key.
- ub_sig: string;
+ **Response**
- // The denomination public key associated with this coin.
- denom_pub: string;
+ :status 200 OK:
+ The backend has successfully returned the list of instances stored. Returns
+ a `QueryInstancesResponse`.
- // The amount that is subtracted from this coin with this payment.
- contribution: Amount;
+ .. ts:def:: QueryInstancesResponse
- // URL of the exchange this coin was withdrawn from.
- exchange_url: string;
- }
+ interface QueryInstancesResponse {
+ // The URL where the wallet has to send coins.
+ // payto://-URL of the merchant's bank account. Required.
+ payto: string;
+ // Merchant instance of the response to create
+ // This field is optional. If it is not specified
+ // then it will automatically be created.
+ instance?: string;
-.. http:get:: /public/pay
+ // Merchant name corresponding to this instance.
+ name: string;
- Query the payment status of an order.
+ // Public key of the merchant/instance, in Crockford Base32 encoding.
+ merchant_pub: EddsaPublicKey;
- **Request**
+ // 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[];
- :query hc: hash of the order's contract terms
- :query long_poll_ms: *Optional.* If specified, the merchant backend will
- wait up to ``long_poll_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.
+ }
- **Response**
- :status 200 OK:
- The response is a `PublicPayStatusResponse`.
+.. http:post:: /instances/<instance-id>
- .. ts:def:: PublicPayStatusResponse
+ This request will be used to update merchant instance in the backend.
- interface PublicPayStatusResponse {
- // Has the payment for this order been completed?
- paid: boolean;
- // Refunds for this payment, if any.
- refunds: RefundInfo[];
- }
+ **Request**
+
+ The request must be a `PostInstanceUpdateRequest`.
+ **Response**
+
+ :status 200 OK:
+ The backend has successfully updated the instance. The response is a
+ `PostInstanceUpdateResponse`.
- .. ts:def:: RefundInfo
+ .. ts:def:: PostInstanceUpdateRequest
- interface RefundInfo {
+ interface PostInstanceUpdateRequest {
+ // Merchant instance that is to be updaated. Required.
+ instance: string;
- // Coin from which the refund is going to be taken
- coin_pub: EddsaPublicKey;
+ // New URL where the wallet has to send coins.
+ // payto://-URL of the merchant's bank account. Required.
+ payto: string;
- // Refund amount taken from coin_pub
- refund_amount: Amount;
+ // Merchant name coreesponding to this instance.
+ name: string;
- // Refund fee
- refund_fee: Amount;
+ }
- // Identificator of the refund
- rtransaction_id: number;
+ .. ts:def:: PostInstanceUpdateResponse
- // Merchant public key
- merchant_pub: EddsaPublicKey
+ interface PostInstanceUpdateResponse {
+ // Merchant instance of the response that was updated
+ instance: string;
- // Merchant signature of a TALER_RefundRequestPS object
- merchant_sig: EddsaSignature;
+ //unique key for each merchant
+ merchant_id: string;
}
-.. http:get:: /public/proposal
+.. http:delete:: /instances/<instance-id>
- Retrieve and take ownership (via nonce) over a proposal.
+ This request will be used to delete merchant instance in the backend.
- **Request**
+ **Request:**
- :query order_id: the order id whose refund situation is being queried
- :query nonce: the nonce for the proposal
+ :query instance_id: instance id that should be used for the instance
**Response**
:status 200 OK:
- The backend has successfully retrieved the proposal. It responds with a
:ref:`proposal <proposal>`.
-
- :status 403 Forbidden:
- The frontend used the same order ID with different content in the order.
-
+ The backend has successfully removed the instance. The response is a
+ `PostInstanceRemoveResponse`.
-.. http:get:: /public/[$INSTANCE]/$ORDER/refund
+ .. ts:def:: PostInstanceRemoveResponse
- Obtain a refund issued by the merchant.
+ interface PostInstanceRemoveResponse {
+ deleted: true;
+ }
- **Response:**
- :status 200 OK:
- The merchant processed the approved refund. The body is a `RefundResponse`.
- Note that a successful response from the merchant does not imply that the
- exchange successfully processed the refund. Clients must inspect the
- body to check which coins were successfully refunded. It is possible for
- only a subset of the refund request to have been processed successfully.
- Re-issuing the request will cause the merchant to re-try such unsuccessful
- sub-requests.
-
- .. ts:def:: RefundResponse
-
- interface RefundResponse {
- // hash of the contract terms
- h_contract_terms: HashCode;
+------------------
+The Contract Terms
+------------------
- // merchant's public key
- merchant_pub: EddsaPublicKey;
+The contract terms must have the following structure:
- // array with information about the refunds obtained
- refunds: RefundDetail[];
- }
+ .. ts:def:: ContractTerms
- .. ts:def:: RefundDetail
+ interface ContractTerms {
+ // Human-readable description of the whole purchase
+ summary: string;
- interface RefundDetail {
+ // Map from IETF BCP 47 language tags to localized summaries
+ summary_i18n?: { [lang_tag: string]: string };
- // public key of the coin to be refunded
- coin_pub: EddsaPublicKey;
+ // Unique, free-form identifier for the proposal.
+ // Must be unique within a merchant instance.
+ // For merchants that do not store proposals in their DB
+ // before the customer paid for them, the order_id can be used
+ // by the frontend to restore a proposal from the information
+ // encoded in it (such as a short product identifier and timestamp).
+ order_id: string;
- // Amount approved for refund for this coin
- refund_amount: Amount;
+ // Total price for the transaction.
+ // The exchange will subtract deposit fees from that amount
+ // before transferring it to the merchant.
+ amount: Amount;
- // Refund fee the exchange will charge for the refund
- refund_fee: Amount;
+ // The URL for this purchase. Every time is is visited, the merchant
+ // will send back to the customer the same proposal. Clearly, this URL
+ // can be bookmarked and shared by users.
+ fulfillment_url: string;
- // HTTP status from the exchange. 200 if successful.
- exchange_http_status: integer;
+ // Maximum total deposit fee accepted by the merchant for this contract
+ max_fee: Amount;
- // Refund transaction ID.
- rtransaction_id: integer;
+ // Maximum wire fee accepted by the merchant (customer share to be
+ // divided by the 'wire_fee_amortization' factor, and further reduced
+ // if deposit fees are below 'max_fee'). Default if missing is zero.
+ max_wire_fee: Amount;
- // Taler error code from the exchange. Only given if the
- // exchange_http_status is not 200.
- exchange_code?: integer;
+ // Over how many customer transactions does the merchant expect to
+ // amortize wire fees on average? If the exchange's wire fee is
+ // above 'max_wire_fee', the difference is divided by this number
+ // to compute the expected customer's contribution to the wire fee.
+ // The customer's contribution may further be reduced by the difference
+ // between the 'max_fee' and the sum of the actual deposit fees.
+ // Optional, default value if missing is 1. 0 and negative values are
+ // invalid and also interpreted as 1.
+ wire_fee_amortization: number;
- // Full exchange response. Only given if the
- // exchange_http_status is not 200 and the exchange
- // did return JSON.
- exchange_reply?: integer;
+ // List of products that are part of the purchase (see `Product`).
+ products: Product[];
- // Public key of the exchange used for the exchange_sig.
- // Only given if the exchange_http_status is 200.
- exchange_pub?: EddsaPublicKey;
+ // Time when this contract was generated
+ timestamp: Timestamp;
- // Signature the exchange confirming the refund.
- // Only given if the exchange_http_status is 200.
- exchange_sig?: EddsaSignature;
+ // After this deadline has passed, no refunds will be accepted.
+ refund_deadline: Timestamp;
- }
+ // After this deadline, the merchant won't accept payments for the
contact
+ pay_deadline: Timestamp;
- :status 404 Not found:
- The merchant is unaware of having granted a refund, or even of
- the order specified.
+ // Transfer deadline for the exchange. Must be in the
+ // deposit permissions of coins used to pay for this order.
+ wire_transfer_deadline: Timestamp;
+ // Merchant's public key used to sign this proposal; this information
+ // is typically added by the backend Note that this can be an ephemeral
key.
+ merchant_pub: EddsaPublicKey;
-.. http:post:: /public/tip-pickup
+ // Base URL of the (public!) merchant backend API.
+ // Must be an absolute URL that ends with a slash.
+ merchant_base_url: string;
- Handle request from wallet to pick up a tip.
+ // More info about the merchant, see below
+ merchant: Merchant;
- **Request**
+ // The hash of the merchant instance's wire details.
+ h_wire: HashCode;
- The request body is a `TipPickupRequest` object.
+ // Wire transfer method identifier for the wire method associated with
h_wire.
+ // The wallet may only select exchanges via a matching auditor if the
+ // exchange also supports this wire method.
+ // The wire transfer fees must be added based on this wire transfer
method.
+ wire_method: string;
- **Response**
+ // Any exchanges audited by these auditors are accepted by the merchant.
+ auditors: Auditor[];
- :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).
+ // Exchanges that the merchant accepts even if it does not accept any
auditors that audit them.
+ exchanges: Exchange[];
- .. ts:def:: TipPickupRequest
+ // Map from labels to locations
+ locations: { [label: string]: [location: Location], ... };
- interface TipPickupRequest {
+ // Nonce generated by the wallet and echoed by the merchant
+ // in this field when the proposal is generated.
+ nonce: string;
- // Identifier of the tip.
- tip_id: HashCode;
+ // Specifies for how long the wallet should try to get an
+ // automatic refund for the purchase. If this field is
+ // present, the wallet should wait for a few seconds after
+ // the purchase and then automatically attempt to obtain
+ // a refund. The wallet should probe until "delay"
+ // after the payment was successful (i.e. via long polling
+ // or via explicit requests with exponential back-off).
+ //
+ // In particular, if the wallet is offline
+ // at that time, it MUST repeat the request until it gets
+ // one response from the merchant after the delay has expired.
+ // If the refund is granted, the wallet MUST automatically
+ // recover the payment. This is used in case a merchant
+ // knows that it might be unable to satisfy the contract and
+ // desires for the wallet to attempt to get the refund without any
+ // customer interaction. Note that it is NOT an error if the
+ // merchant does not grant a refund.
+ auto_refund?: RelativeTime;
- // List of planches the wallet wants to use for the tip
- planchets: PlanchetDetail[];
+ // Extra data that is only interpreted by the merchant frontend.
+ // Useful when the merchant needs to store extra information on a
+ // contract without storing it separately in their database.
+ extra?: any;
}
- .. ts:def:: PlanchetDetail
+ The wallet must select a exchange that either the merchant accepts directly
by
+ listing it in the exchanges array, or for which the merchant accepts an
auditor
+ that audits that exchange by listing it in the auditors array.
- interface PlanchetDetail {
- // Hash of the denomination's public key (hashed to reduce
- // bandwidth consumption)
- denom_pub_hash: HashCode;
+ The `Product` object describes the product being purchased from the
merchant. It has the following structure:
- // coin's blinded public key
- coin_ev: CoinEnvelope;
+ .. ts:def:: Product
- }
+ interface Product {
+ // Human-readable product description.
+ description: string;
- .. ts:def:: TipResponse
+ // Map from IETF BCP 47 language tags to localized descriptions
+ description_i18n?: { [lang_tag: string]: string };
- interface TipResponse {
+ // The quantity of the product to deliver to the customer (optional, if
applicable)
+ quantity?: string;
- // Blind RSA signatures over the planchets.
- // The order of the signatures matches the planchets list.
- blind_sigs: BlindSignature[];
- }
+ // The price of the product; this is the total price for the amount
specified by 'quantity'
+ price: Amount;
- interface BlindSignature {
+ // merchant-internal identifier for the product
+ product_id?: string;
- // The (blind) RSA signature. Still needs to be unblinded.
- blind_sig: RsaSignature;
- }
+ // An optional base64-encoded product image
+ image?: ImageDataUrl;
+ // a list of objects indicating a 'taxname' and its amount. Again,
italics denotes the object field's name.
+ taxes?: any[];
-.. http:get:: /public/poll-payment
+ // time indicating when this product should be delivered
+ delivery_date: Timestamp;
- Check the payment status of an order.
+ // where to deliver this product. This may be an URL for online delivery
+ // (i.e. 'http://example.com/download' or 'mailto:address@hidden'),
+ // or a location label defined inside the proposition's 'locations'.
+ // The presence of a colon (':') indicates the use of an URL.
+ delivery_location: string;
+ }
- **Request:**
+ .. ts:def:: Merchant
- :query order_id: order id that should be used for the payment
- :query h_contract: hash of the contract (used to authenticate customer)
- :query session_id: *Optional*. Session ID that the payment must be bound to.
If not specified, the payment is not session-bound.
- :query timeout: *Optional*. Timeout in seconds to wait for a payment if the
answer would otherwise be negative (long polling).
- :query refund=AMOUNT: *Optional*. Indicates that we are polling for a refund
above the given AMOUNT. Only useful in combination with timeout.
+ interface Merchant {
+ // label for a location with the business address of the merchant
+ address: string;
- **Response:**
+ // the merchant's legal name of business
+ name: string;
- Returns a `PollPaymentResponse`, whose format can differ based on the status
of the payment.
+ // label for a location that denotes the jurisdiction for disputes.
+ // Some of the typical fields for a location (such as a street address)
may be absent.
+ jurisdiction: string;
+ }
- .. ts:def:: PollPaymentResponse
- type CheckPaymentResponse = PollPaymentPaidResponse |
PollPaymentUnpaidResponse
+ .. ts:def:: Location
- .. ts:def:: PollPaymentPaidResponse
+ interface Location {
+ country?: string;
+ city?: string;
+ state?: string;
+ region?: string;
+ province?: string;
+ zip_code?: string;
+ street?: string;
+ street_number?: string;
+ }
- interface PollPaymentPaidResponse {
- // value is always true;
- paid: boolean;
+ .. ts:def:: Auditor
- // Was the payment refunded (even partially)
- refunded: boolean;
+ interface Auditor {
+ // official name
+ name: string;
- // Amount that was refunded, only present if refunded is true.
- refund_amount?: Amount;
+ // Auditor's public key
+ auditor_pub: EddsaPublicKey;
+ // Base URL of the auditor
+ url: string;
}
- .. ts:def:: PollPaymentUnpaidResponse
-
- interface PollPaymentUnpaidResponse {
- // value is always false;
- paid: boolean;
-
- // URI that the wallet must process to complete the payment.
- taler_pay_uri: string;
+ .. ts:def:: Exchange
- // 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;
+ interface Exchange {
+ // the exchange's base URL
+ url: string;
+ // master public key of the exchange
+ master_pub: EddsaPublicKey;
}
--
To stop receiving notification emails like this one, please contact
address@hidden.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-docs] branch master updated: spec v1 of merchant protocol,
gnunet <=