From 95f5214432d6f58ac374db481cc13e5542b2572c Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 13 Apr 2020 16:27:10 +0200 Subject: 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 22c090f9..43eed036 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 `. :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 `. - // 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 `. + 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 ` 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 `. - 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 `. + :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 ` 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 ` - 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 `. 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/ - 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 `. - **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/ + // 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 ` + 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/ +.. 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: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; + // 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 `. + 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 ` 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/ - .. 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/ - .. 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/ - 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 `. - - :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: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; + } - **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; } -- cgit v1.2.3