diff options
author | Marcello Stanisci <marcello.stanisci@inria.fr> | 2017-05-31 16:25:32 +0200 |
---|---|---|
committer | Marcello Stanisci <marcello.stanisci@inria.fr> | 2017-05-31 16:25:32 +0200 |
commit | ee1bff39423ca0679fd15906056372751e1b03e2 (patch) | |
tree | 7441f237577c21508b77ca1b8c3c121b401e610d /api-merchant.rst | |
parent | 6edadef7840f2104ad40dfa086641bccee8b2c57 (diff) | |
download | docs-ee1bff39423ca0679fd15906056372751e1b03e2.tar.gz docs-ee1bff39423ca0679fd15906056372751e1b03e2.tar.bz2 docs-ee1bff39423ca0679fd15906056372751e1b03e2.zip |
move content away to proper repos, plus killing obsolete stuff
Diffstat (limited to 'api-merchant.rst')
-rw-r--r-- | api-merchant.rst | 727 |
1 files changed, 727 insertions, 0 deletions
diff --git a/api-merchant.rst b/api-merchant.rst new file mode 100644 index 00000000..ede33700 --- /dev/null +++ b/api-merchant.rst @@ -0,0 +1,727 @@ +.. + This file is part of GNU TALER. + Copyright (C) 2014, 2015, 2016 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + @author Marcello Stanisci + @author Florian Dold + @author Christian Grothoff + +.. _merchant-api: + +============ +Merchant API +============ + +Before reading the API reference documentation, see the +`merchant architecture <https://docs.taler.net/dev-merchant.html#merchant-arch>`_ +and the `payment protocol <https://docs.taler.net/integration-merchant.html#payprot>`_ + +--------------------- +The Frontend HTTP API +--------------------- + + + The merchant frontend API described here describes the minimal set of HTTP requests that a web shop + needs to understand in order to support Taler payments. The names `proposal_url`, `pay_url` and `fulfillment_url` + are placeholders for the actual URLs that the merchant frontend uses. + + Please refer to the `glossary <https://docs.taler.net/glossary.html>`_ for terms + like `order`, `proposal`, `contract`, and others. + + +.. http:get:: proposal_url + + Requesting this URL generates a proposal. Note that the wallet will get properly triggered by the merchant in order + to issue this GET request. The merchant will also instruct the wallet whether or + not to provide the optional `nonce` parameter. `Payment protocol <https://docs.taler.net/integration-merchant.html#payprot>`_ explains how the wallet is triggered to + fetch the proposal. + + **Request:** + + :query nonce: Any string value. This value will be + included in the proposal, so that when the wallet receives the proposal it can + easily check whether it was the genuine receiver of the proposal it got. + This value is needed to avoid proposals' replications. + + **Response** + + :status 200 OK: The request was successful. The body contains a :ref:`proposal <proposal>`. + :status 400 Bad Request: Request not understood. + :status 500 Internal Server Error: + In most cases, some error occurred while the backend was generating the + proposal. For example, it failed to store it into its database. + +.. _pay: +.. http:post:: pay_url + + + Send the deposit permission to the merchant. The client should POST a `DepositPermission`_ + object. If the payment was processed successfully by the merchant, this URL will set session + state that allows the fulfillment URL to show the final product. + + .. _DepositPermission: + .. code-block:: tsref + + interface DepositPermission { + // a free-form identifier identifying the order that is being payed for + order_id: string; + + // Public key of the merchant. Used to identify the merchant instance. + merchant_pub: EddsaSignature; + + // the chosen exchange's base URL + exchange: string; + + // the coins used to sign the proposal + coins: DepositedCoin[]; + } + + .. _`tsref-type-DepositedCoin`: + + .. code-block:: tsref + + interface DepositedCoin { + // the amount this coin is paying for + amount: Amount; + + // coin's public key + coin_pub: RsaPublicKey; + + // denomination key + denom_pub: RsaPublicKey; + + // exchange's signature over this `coin's public key <eddsa-coin-pub>`_ + ub_sig: RsaSignature; + + // Signature of `TALER_DepositRequestPS`_ + coin_sig: EddsaSignature; + } + + **Success Response:** + + :status 301 Redirection: the merchant should redirect the client to his fullfillment page, where the good outcome of the purchase must be shown to the user. + + **Failure Responses:** + + The error codes and data sent to the wallet are a mere copy of those gotten from the exchange when attempting to pay. The section about :ref:`deposit <deposit>` explains them in detail. + + +.. http:post:: fulfillment_url + + URL that shows the product after it has been purchased. Going to the a fulfillment URL + before the payment was completed must trigger the payment process. + + For products that are intended to be purchased only once (such as online news + articles), the fulfillment URL should map one-to-one to an article, so that + when the user visits the page after they cleared their cookies, the purchase + can be replayed. + + For purchases that can be repeated, the fulfillment URL should map one-to-one to + a proposal, e.g. by including the order id. + + Following these rules allows sharing of links and bookmarking to work correctly, + and produces nicely looking semantic URLs. + + .. note:: + By "replaying" a payment, we mean that the user reuses the same coins he + used the first time he/she bought those items, thus not spending new coins + (and therefore not spending additional money). + + +------------------------------ +The Merchant Backend HTTP API +------------------------------ + +The following API are made available by the merchant's `backend` to the merchant's `frontend`. + +.. http:post:: /proposal + + Generate a new proposal, based on the `order` given in the request. This request is idempotent. + + **Request:** + +.. _proposal: + + The backend expects an `order` as input. The order is a `ProposalData`_ + object **without** the fields: + + * `exchanges` + * `auditors` + * `H_wire` + * `merchant_pub` + * `timestamp` + + The following fields from `ProposalData`_ are optional and will be filled + in by the backend if not present: + + * `merchant.instance` (default instance will be used) + * `order_id` (random alphanumeric identifier will be used) + * `refund_deadline` (instance's default will be used) + * `pay_deadline` (instance's default will be used) + + **Response** + + :status 200 OK: + The backend has successfully created the proposal. It responds with a :ref:`proposal <proposal>`. On success, the `frontend` should pass this response verbatim to the wallet. + + :status 403 Forbidden: + The frontend used the same order ID with different content in the order. + +.. http:post:: /pay + + Asks the `backend` to execute the transaction with the exchange and deposit the coins. + + **Request:** + + The `frontend` passes the :ref:`deposit permission <DepositPermission>` + received from the wallet, and optionally adds a field named `wire_transfer_deadline`, + indicating a deadline by which he would expect to receive the bank transfer + for this deal. Note that the `wire_transfer_deadline` must be after the `refund_deadline`. + The backend calculates the `wire_transfer_deadline` by adding the `wire_transfer_delay` + value found in the configuration to the current time. + + **Response:** + + :status 200 OK: + The exchange accepted all of the coins. The body is a `PaymentResponse`_. + The `frontend` should now fullfill the contract. + :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 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. + + 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. + + .. _PaymentResponse: + .. code-block:: tsref + + interface PaymentResponse { + // Signature on `TALER_PaymentResponsePS`_ with the public + // key of the instance in the proposal. + sig: EddsaSignature; + + // Proposal data hash being signed over + h_proposal_data: HashCode; + + // Proposal, send for convenience so the frontend + // can do order processing without a second lookup on + // a successful payment + proposal: Proposal; + } + +.. http:get:: /track/transfer + + Provides deposits associated with a given wire transfer. + + **Request:** + + :query wtid: raw wire transfer identifier identifying the wire transfer (a base32-encoded value) + :query exchange: base URI of the exchange that made the wire transfer + :query instance: (optional) identificative token of the merchant `instance <https://docs.taler.net/operate-merchant.html#instances-lab>`_ which is being tracked. + + **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 `MerchantTrackTransferResponse`_. Note that + the similarity to the response given by the exchange for a /track/transfer + is completely intended. + + :status 404 Not Found: + The wire transfer identifier is unknown to the exchange. + + :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`_. + + .. _MerchantTrackTransferResponse: + .. _tsref-type-TrackTransferResponse: + .. code-block:: tsref + + interface TrackTransferResponse { + // Total amount transferred + total: Amount; + + // Applicable wire fee that was charged + wire_fee: Amount; + + // public key of the merchant (identical for all deposits) + merchant_pub: EddsaPublicKey; + + // 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; + } + + .. _tsref-type-TrackTransferDetail: + .. code-block:: tsref + + interface TrackTransferDetail { + // Business activity associated with the wire tranfered 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; + } + + + **Details:** + + .. _tsref-type-TrackTransferConflictDetails: + .. _TrackTransferConflictDetails: + .. code-block:: tsref + + 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; + + } + + +.. http:get:: /track/order + + Provide the wire transfer identifier associated with an (existing) deposit operation. + + **Request:** + + :query id: ID of the transaction we want to trace (an integer) + :query instance: identificative token for the merchant instance which is to be tracked (optional). See `<https://docs.taler.net/operate-merchant.html#instances-lab>`_. This information is needed because the request has to be signed by the merchant, thus we need to pick the instance's private key. + + **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 :ref:`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:** + + .. _tsref-type-TransactionWireTransfer: + .. _TransactionWireTransfer: + .. code-block:: tsref + + interface TransactionWireTransfer { + + // Responsible exchange + exchange_uri: string; + + // 32-byte wire transfer identifier + wtid: Base32; + + // execution time of the wire transfer + execution_time: Timestamp; + + // Total amount that has been wire transfered + // to the merchant + amount: Amount; + } + + .. _tsref-type-CoinWireTransfer: + .. _CoinWireTransfer: + .. code-block:: tsref + + 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; + } + + .. _TransactionConflictProof: + .. _tsref-type-TransactionConflictProof: + .. code-block:: tsref + + interface TransactionConflictProof { + // Numerical `error code <error-codes>`_ + code: number; + + // Human-readable error description + hint: string; + + // A claim by the exchange about the transactions associated + // with a given wire transfer; it does not list the + // transaction that `transaction_tracking_claim` says is part + // of the aggregate. This is + // a `/track/transfer` response from the exchange. + wtid_tracking_claim: TrackTransferResponse; + + // The current claim by the exchange that the given + // transaction is included in the above WTID. + // (A response from `/track/order`). + transaction_tracking_claim: TrackTransactionResponse; + + // Public key of the coin for which we got conflicting information. + coin_pub: CoinPublicKey; + + } + + +.. http:get:: /contract/lookup + + Retrieve a proposal, given its order ID. + + **Request** + + :query order_id: transaction ID of the proposal to retrieve. + + **Response** + + :status 200 OK: + The body contains the `proposal`_ pointed to by `order_id`. + + :status 404 Not Found: + No proposal corresponds to `order_id`. + +.. http:get:: /history + + Returns transactions up to some point in the past + + **Request** + + :query date: only transactions *older* than this parameter will be returned. It's a timestamp, given in seconds. + Being optional, it defaults to the current time if not given. + :query start: only transactions having `row_id` less than `start` will be returned. Being optional, it defaults to the + highest `row_id` contained in the DB (namely, the youngest entry). + :query delta: at most `delta` entries will be returned. Being optional, it defaults to 20. + :query instance: on behalf of which merchant instance the query should be accomplished. + + A typical usage is to firstly call this API without `start` and `date` parameter, then fetch the oldest + `row_id` from the results, and then keep calling the API by using the oldest row ID as `start` parameter. + This way we simply "scroll" results from the youngest to the oldest, `delta` entries at time. + + **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`. + + .. _tsref-type-TransactionHistory: + .. _TransactionHistory: + .. code-block:: tsref + + interface TransactionHistory { + // The serial number this entry has in the merchant's DB. + row_id: number; + + // order ID of the transaction related to this entry. + order_id: string; + + // Transaction's timestamp + timestamp: Timestamp; + + // Total amount associated to this transaction. + amount: Amount; + } + +.. _proposal: + +------------ +The proposal +------------ + +The `proposal` is obtained by filling some missing information +in the `order`, and then by signing it. See below. + + .. _tsref-type-Proposal: + .. code-block:: tsref + + interface Proposal { + // The proposal data, effectively the frontend's order with some data filled in + // by the merchant backend. + data: ProposalData; + + // Contract's hash, provided as a convenience. All components that do + // not fully trust the merchant must verify this field. + H_proposal: HashCode; + + // Signature over the hashcode of `proposal` made by the merchant. + merchant_sig: EddsaSignature; + } + +.. note:: + When the proposal is signed by the merchant or the wallet, the + signature is made over the hash of the JSON text, as the proposal may + be confidential between merchant and customer and should not be + exposed to the exchange. The hashcode is generated by hashing the + encoding of the proposal's JSON obtained by using the flags + ``JSON_COMPACT | JSON_PRESERVE_ORDER``, as described in the `libjansson + documentation + <https://jansson.readthedocs.org/en/2.7/apiref.html?highlight=json_dumps#c.json_dumps>`_. + +The `proposal data` must have the following structure: + + .. _tsref-type-ProposalData: + .. code-block:: tsref + + interface ProposalData { + // Human-readable description of the whole purchase + // NOTE: still not implemented + summary: string; + + // 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; + + // Total price for the transaction. + // The exchange will subtract deposit fees from that amount + // before transfering it to the merchant. + amount: Amount; + + // The URL where the wallet has to send coins. + pay_url: string; + + // The URI 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; + + // Maximum total deposit fee accepted by the merchant for this contract + max_fee: Amount; + + // 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; + + // 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: Integer; + + // List of products that are part of the purchase (see `below <Product>`_) + products: Product[]; + + // Time when this contract was generated + timestamp: Timestamp; + + // 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; + + // 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; + + // More info about the merchant, see below + merchant: Merchant; + + // 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[]; + + // Exchanges that the merchant accepts even if it does not accept any auditors that audit them. + exchanges: Exchange[]; + + // Map from labels to locations + locations: { [label: string]: [location: Location], ... }; + + // Nonce generated by the wallet and echoed by the merchant + // in this field when the proposal is generated. + nonce: string; + + // 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; + } + + The wallet must select a exchange that either the mechant accepts directly by + listing it in the exchanges arry, or for which the merchant accepts an auditor + that audits that exchange by listing it in the auditors array. + + The `product` object describes the product being purchased from the merchant. It has the following structure: + + .. _Product: + .. _tsref-type-Product: + .. code-block:: tsref + + interface Product { + // Human-readable product description. + description: string; + + // The quantity of the product to deliver to the customer (optional, if applicable) + quantity?: number; + + // The price of the product; this is the total price for the amount specified by `quantity` + price: Amount; + + // merchant's 53-bit internal identification number for the product (optional) + product_id?: number; + + // a list of objects indicating a `taxname` and its amount. Again, italics denotes the object field's name. + taxes?: any[]; + + // time indicating when this product should be delivered + delivery_date: Timestamp; + + // where to deliver this product. This may be an URI for online delivery + // (i.e. `http://example.com/download` or `mailto:customer@example.com`), + // or a location label defined inside the proposition's `locations`. + // The presence of a colon (`:`) indicates the use of an URL. + delivery_location: string; + } + + .. _tsref-type-Merchant: + .. code-block:: ts + + interface Merchant { + // label for a location with the business address of the merchant + address: string; + + // the merchant's legal name of business + name: string; + + // 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; + + // Which instance is working this proposal. + // See `Merchant Instances <https://docs.taler.net/operate-merchant.html#instances-lab>`_. + // This field is optional, as the "default" instance is not forced to provide any + // `instance` identificator. + instance: string; + } + + + .. _tsref-type-Location: + .. _Location: + .. code-block:: ts + + interface Location { + country?: string; + city?: string; + state?: string; + region?: string; + province?: string; + zip_code?: string; + street?: string; + street_number?: string; + } + + .. _tsref-type-Auditor: + .. code-block:: tsref + + interface Auditor { + // official name + name: string; + + // Auditor's public key + auditor_pub: EddsaPublicKey; + + // Base URL of the auditor + url: string; + } + + .. _tsref-type-Exchange: + .. code-block:: tsref + + interface Exchange { + // the exchange's base URL + url: string; + + // master public key of the exchange + master_pub: EddsaPublicKey; + } |