diff options
Diffstat (limited to 'core/api-bank-wire.rst')
-rw-r--r-- | core/api-bank-wire.rst | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/core/api-bank-wire.rst b/core/api-bank-wire.rst new file mode 100644 index 00000000..84184ec9 --- /dev/null +++ b/core/api-bank-wire.rst @@ -0,0 +1,454 @@ +.. + This file is part of GNU TALER. + Copyright (C) 2019-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + +.. _taler-wire-gateway-http-api: + +=========================== +Taler Wire Gateway HTTP API +=========================== + +This section describes the API offered by the Taler wire gateway. The API is +used by the exchange to trigger transactions and query incoming transactions, as +well as by the auditor to query incoming and outgoing transactions. + +This API is currently implemented by the Taler Demo Bank, as well as by +LibEuFin (work in progress). + +.. http:get:: /config + + Return the protocol version and configuration information about the bank. + This specification corresponds to ``current`` protocol being version **0**. + + **Response:** + + :http:statuscode:`200 OK`: + The exchange responds with a `WireConfig` object. This request should + virtually always be successful. + + **Details:** + + .. ts:def:: WireConfig + + interface WireConfig { + // Name of the API. + name: "taler-wire-gateway"; + + // libtool-style representation of the Bank protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + // Currency used by this gateway. + currency: string; + + // URN of the implementation (needed to interpret 'revision' in version). + // @since v0, may become mandatory in the future. + implementation?: string; + } + +-------------- +Authentication +-------------- + +The bank library authenticates requests to the wire gateway via +`HTTP basic auth <https://tools.ietf.org/html/rfc7617>`_. + +------------------- +Making Transactions +------------------- + +.. http:post:: /transfer + + This API allows the exchange to make a transaction, typically to a merchant. The bank account + of the exchange is not included in the request, but instead derived from the user name in the + authentication header and/or the request base URL. + + To make the API idempotent, the client must include a nonce. Requests with the same nonce + are rejected unless the request is the same. + + **Request:** + + .. ts:def:: TransferRequest + + interface TransferRequest { + // Nonce to make the request idempotent. Requests with the same + // ``request_uid`` that differ in any of the other fields + // are rejected. + request_uid: HashCode; + + // Amount to transfer. + amount: Amount; + + // Base URL of the exchange. Shall be included by the bank gateway + // in the appropriate section of the wire transfer details. + exchange_base_url: string; + + // Wire transfer identifier chosen by the exchange, + // used by the merchant to identify the Taler order(s) + // associated with this wire transfer. + wtid: ShortHashCode; + + // The recipient's account identifier as a payto URI. + credit_account: string; + } + + **Response:** + + :http:statuscode:`200 OK`: + The request has been correctly handled, so the funds have been transferred to + the recipient's account. The body is a `TransferResponse`. + :http:statuscode:`400 Bad request`: + Request malformed. The bank replies with an `ErrorDetail` object. + :http:statuscode:`401 Unauthorized`: + Authentication failed, likely the credentials are wrong. + :http:statuscode:`404 Not found`: + The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object. + :http:statuscode:`409 Conflict`: + A transaction with the same ``request_uid`` but different transaction details + has been submitted before. + + **Details:** + + .. ts:def:: TransferResponse + + interface TransferResponse { + // Timestamp that indicates when the wire transfer will be executed. + // In cases where the wire transfer gateway is unable to know when + // the wire transfer will be executed, the time at which the request + // has been received and stored will be returned. + // The purpose of this field is for debugging (humans trying to find + // the transaction) as well as for taxation (determining which + // time period a transaction belongs to). + timestamp: Timestamp; + + // Opaque ID of the wire transfer initiation performed by the bank. + // It is different from the /history endpoints row_id. + row_id: SafeUint64; + } + + +-------------------------------- +Querying the transaction history +-------------------------------- + + +.. http:get:: /history/incoming + + Return a list of transactions made from or to the exchange. + + Incoming transactions must contain a valid reserve public key. If a bank + transaction does not conform to the right syntax, the wire gateway must not + report it to the exchange, and send funds back to the sender if possible. + + The bank account of the exchange is determined via the base URL and/or the + user name in the ``Authorization`` header. In fact the transaction history + might come from a "virtual" account, where multiple real bank accounts are + merged into one history. + + Transactions are identified by an opaque numeric identifier, referred to here + as *row ID*. The semantics of the row ID (including its sorting order) are + determined by the bank server and completely opaque to the client. + + The list of returned transactions is determined by a row ID *starting point* + and a signed non-zero integer *delta*: + + * If *delta* is positive, return a list of up to *delta* transactions (all matching + the filter criteria) strictly **after** the starting point. The transactions are sorted + in **ascending** order of the row ID. + * If *delta* is negative, return a list of up to *-delta* transactions (all matching + the filter criteria) strictly **before** the starting point. The transactions are sorted + in **descending** order of the row ID. + + If *starting point* is not explicitly given, it defaults to: + + * A value that is **smaller** than all other row IDs if *delta* is **positive**. + * A value that is **larger** than all other row IDs if *delta* is **negative**. + + **Request:** + + :query start: *Optional.* + Row identifier to explicitly set the *starting point* of the query. + :query delta: + The *delta* value that determines the range of the query. + :query long_poll_ms: *Optional.* If this parameter is specified and the + result of the query would be empty, the bank will wait up to ``long_poll_ms`` + milliseconds for new transactions that match the query to arrive and only + then send the HTTP response. A client must never rely on this behavior, as + the bank may return a response immediately or after waiting only a fraction + of ``long_poll_ms``. + + **Response:** + + :http:statuscode:`200 OK`: + JSON object of type `IncomingHistory`. + :http:statuscode:`204 No content`: + There are not transactions to report (under the given filter). + :http:statuscode:`400 Bad request`: + Request malformed. The bank replies with an `ErrorDetail` object. + :http:statuscode:`401 Unauthorized`: + Authentication failed, likely the credentials are wrong. + :http:statuscode:`404 Not found`: + The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object. + + **Details:** + + .. ts:def:: IncomingHistory + + interface IncomingHistory { + // Array of incoming transactions. + incoming_transactions : IncomingBankTransaction[]; + + // Payto URI to identify the receiver of funds. + // This must be one of the exchange's bank accounts. + // Credit account is shared by all incoming transactions + // as per the nature of the request. + credit_account: string; + + } + + .. ts:def:: IncomingBankTransaction + + // Union discriminated by the "type" field. + type IncomingBankTransaction = + | IncomingReserveTransaction + | IncomingWadTransaction; + + .. ts:def:: IncomingReserveTransaction + + interface IncomingReserveTransaction { + type: "RESERVE"; + + // Opaque identifier of the returned record. + row_id: SafeUint64; + + // Date of the transaction. + date: Timestamp; + + // Amount transferred. + amount: Amount; + + // Payto URI to identify the sender of funds. + debit_account: string; + + // The reserve public key extracted from the transaction details. + reserve_pub: EddsaPublicKey; + + } + + .. ts:def:: IncomingWadTransaction + + interface IncomingWadTransaction { + type: "WAD"; + + // Opaque identifier of the returned record. + row_id: SafeUint64; + + // Date of the transaction. + date: Timestamp; + + // Amount transferred. + amount: Amount; + + // Payto URI to identify the receiver of funds. + // This must be one of the exchange's bank accounts. + credit_account: string; + + // Payto URI to identify the sender of funds. + debit_account: string; + + // Base URL of the exchange that originated the wad. + origin_exchange_url: string; + + // The reserve public key extracted from the transaction details. + wad_id: WadId; + } + + +.. http:get:: /history/outgoing + + Return a list of transactions made by the exchange, typically to a merchant. + + The bank account of the exchange is determined via the base URL and/or the + user name in the ``Authorization`` header. In fact the transaction history + might come from a "virtual" account, where multiple real bank accounts are + merged into one history. + + Transactions are identified by an opaque integer, referred to here as *row + ID*. The semantics of the row ID (including its sorting order) are + determined by the bank server and completely opaque to the client. + + The list of returned transactions is determined by a row ID *starting point* + and a signed non-zero integer *delta*: + + * If *delta* is positive, return a list of up to *delta* transactions (all matching + the filter criteria) strictly **after** the starting point. The transactions are sorted + in **ascending** order of the row ID. + * If *delta* is negative, return a list of up to *-delta* transactions (all matching + the filter criteria) strictly **before** the starting point. The transactions are sorted + in **descending** order of the row ID. + + If *starting point* is not explicitly given, it defaults to: + + * A value that is **smaller** than all other row IDs if *delta* is **positive**. + * A value that is **larger** than all other row IDs if *delta* is **negative**. + + **Request:** + + :query start: *Optional.* + Row identifier to explicitly set the *starting point* of the query. + :query delta: + The *delta* value that determines the range of the query. + :query long_poll_ms: *Optional.* If this parameter is specified and the + result of the query would be empty, the bank will wait up to ``long_poll_ms`` + milliseconds for new transactions that match the query to arrive and only + then send the HTTP response. A client must never rely on this behavior, as + the bank may return a response immediately or after waiting only a fraction + of ``long_poll_ms``. + + **Response:** + + :http:statuscode:`200 OK`: + JSON object of type `OutgoingHistory`. + :http:statuscode:`204 No content`: + There are not transactions to report (under the given filter). + :http:statuscode:`400 Bad request`: + Request malformed. The bank replies with an `ErrorDetail` object. + :http:statuscode:`401 Unauthorized`: + Authentication failed, likely the credentials are wrong. + :http:statuscode:`404 Not found`: + The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object. + + **Details:** + + .. ts:def:: OutgoingHistory + + interface OutgoingHistory { + // Array of outgoing transactions. + outgoing_transactions : OutgoingBankTransaction[]; + + // Payto URI to identify the sender of funds. + // This must be one of the exchange's bank accounts. + // Credit account is shared by all incoming transactions + // as per the nature of the request. + debit_account: string; + + } + + .. ts:def:: OutgoingBankTransaction + + interface OutgoingBankTransaction { + // Opaque identifier of the returned record. + row_id: SafeUint64; + + // Date of the transaction. + date: Timestamp; + + // Amount transferred. + amount: Amount; + + // Payto URI to identify the receiver of funds. + credit_account: string; + + // The wire transfer ID in the outgoing transaction. + wtid: ShortHashCode; + + // Base URL of the exchange. + exchange_base_url: string; + } + + +----------------------- +Wire Transfer Test APIs +----------------------- + +Endpoints in this section are only used for integration tests and never +exposed by bank gateways in production. + +.. _twg-admin-add-incoming: + +.. http:post:: /admin/add-incoming + + Simulate a transfer from a customer to the exchange. This API is *not* + idempotent since it's only used in testing. + + **Request:** + + .. ts:def:: AddIncomingRequest + + interface AddIncomingRequest { + // Amount to transfer. + amount: Amount; + + // Reserve public key that is included in the wire transfer details + // to identify the reserve that is being topped up. + reserve_pub: EddsaPublicKey; + + // Account (as payto URI) that makes the wire transfer to the exchange. + // Usually this account must be created by the test harness before this API is + // used. An exception is the "exchange-fakebank", where any debit account can be + // specified, as it is automatically created. + debit_account: string; + } + + **Response:** + + :http:statuscode:`200 OK`: + The request has been correctly handled, so the funds have been transferred to + the recipient's account. The body is a `AddIncomingResponse`. + :http:statuscode:`400 Bad request`: + The request is malformed. The bank replies with an `ErrorDetail` object. + :http:statuscode:`401 Unauthorized`: + Authentication failed, likely the credentials are wrong. + :http:statuscode:`404 Not found`: + The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object. + :http:statuscode:`409 Conflict`: + The 'reserve_pub' argument was used previously in another transfer, and the specification mandates that reserve public keys must not be reused. + + **Details:** + + .. ts:def:: AddIncomingResponse + + interface AddIncomingResponse { + // Timestamp that indicates when the wire transfer will be executed. + // In cases where the wire transfer gateway is unable to know when + // the wire transfer will be executed, the time at which the request + // has been received and stored will be returned. + // The purpose of this field is for debugging (humans trying to find + // the transaction) as well as for taxation (determining which + // time period a transaction belongs to). + timestamp: Timestamp; + + // Opaque ID of the wire transfer initiation performed by the bank. + // It is different from the /history endpoints row_id. + row_id: SafeUint64; + } + + +Security Considerations +======================= + +For implementors: + +* The withdrawal operation ID must contain enough entropy to be unguessable. + +Design: + +* The user must complete the 2FA step of the withdrawal in the context of their banking + app or online banking Website. + We explicitly reject any design where the user would have to enter a confirmation code + they get from their bank in the context of the wallet, as this would teach and normalize + bad security habits. + + |