commit b7f1d602967a1a3130306219bb4ff338f2219439
parent f20c9b07f6ea0b54bcc630fb58d3ad8c4f6c4bce
Author: Antoine A <>
Date: Wed, 4 Mar 2026 10:27:19 +0100
Improve dd80, add new transfer API and update gateway API
Diffstat:
8 files changed, 973 insertions(+), 872 deletions(-)
diff --git a/core/api-bank-wire-gateway.rst b/core/api-bank-wire-gateway.rst
@@ -0,0 +1,713 @@
+..
+ This file is part of GNU TALER.
+ Copyright (C) 2019-2025, 2026 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 adapters. 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.
+
+.. http:get:: /config
+
+ Return the protocol version and configuration information about the bank.
+ This specification corresponds to ``current`` protocol being version **5**.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The adapter 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;
+
+ // Whether implementation support account existence check
+ support_account_check: boolean;
+ }
+
+--------------
+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
+
+ Initiate a new wire transfer from the exchange's bank account, typically to a
+ merchant.
+
+ The exchange's bank account is not included in the request, but instead
+ derived from the username in the ``Authorization`` 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 differs 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;
+
+ // Optional additional metadata to be stored in the transaction.
+ // Must match [a-zA-Z0-9-.:]{1, 40}
+ // @since **v5**
+ metadata?: 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 full 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`:
+ * ``TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED``: an operation with the same ``request_uid`` but different details has been submitted before.
+ * ``TALER_EC_BANK_TRANSFER_WTID_REUSED``: an operation with the same ``wtid`` but a different ``request_uid`` 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;
+ }
+
+.. http:get:: /transfers
+
+ Return a list of transfers initiated from the exchange.
+
+ The bank account of the exchange is determined via the base URL and/or the
+ user name in the ``Authorization`` header. The transfer history
+ might come from a "virtual" account, where multiple real bank accounts are
+ merged into one history.
+
+ Since protocol **v3**.
+
+ **Request:**
+
+ :query limit: *Optional.*
+ At most return the given number of results. Negative for descending by
+ ``row_id``, positive for ascending by ``row_id``. Defaults to ``-20``.
+ :query offset: *Optional.*
+ Starting ``row_id`` for :ref:`pagination <row-id-pagination>`.
+ :query status: *Optional*.
+ Filters by status.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ JSON object of type `TransferList`.
+ :http:statuscode:`204 No content`:
+ There are no transfers to report (under the given filter).
+ :http:statuscode:`400 Bad request`:
+ Request malformed.
+ :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.
+
+ **Details:**
+
+ .. ts:def:: TransferList
+
+ interface TransferList {
+ // Array of initiated transfers.
+ transfers: TransferListStatus[];
+
+ // Full 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:: TransferListStatus
+
+ interface TransferListStatus {
+ // Opaque ID of the wire transfer initiation performed by the bank.
+ // It is different from the /history endpoints row_id.
+ row_id: SafeUint64;
+
+ // Current status of the transfer
+ // pending: the transfer is in progress
+ // transient_failure: the transfer has failed but may succeed later
+ // permanent_failure: the transfer has failed permanently and will never appear in the outgoing history
+ // success: the transfer has succeeded and appears in the outgoing history
+ status: "pending" | "transient_failure" | "permanent_failure" | "success";
+
+ // Amount to transfer.
+ amount: Amount;
+
+ // The recipient's account identifier as a full payto:// URI.
+ credit_account: string;
+
+ // Timestamp that indicates when the wire transfer was 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;
+ }
+
+
+.. http:get:: /transfers/$ROW_ID
+
+ Return the status of a transfer initiated from the exchange, identified by the ``ROW_ID``.
+
+ Since protocol **v3**.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The transfer is known, and details are given in the `TransferStatus` response body.
+ :http:statuscode:`400 Bad request`:
+ Request malformed.
+ :http:statuscode:`401 Unauthorized`:
+ Authentication failed, likely the credentials are wrong.
+ :http:statuscode:`404 Not found`:
+ The transfer was not found.
+
+ **Details:**
+
+ .. ts:def:: TransferStatus
+
+ interface TransferStatus {
+ // Current status of the transfer
+ // pending: the transfer is in progress
+ // transient_failure: the transfer has failed but may succeed later
+ // permanent_failure: the transfer has failed permanently and will never appear in the outgoing history
+ // success: the transfer has succeeded and appears in the outgoing history
+ status: "pending" | "transient_failure" | "permanent_failure" | "success";
+
+ // Optional unstructured messages about the transfer's status. Can be used to document the reasons for failure or the state of progress.
+ status_msg?: string;
+
+ // 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;
+
+ // Optional additional metadata to be stored in the transaction.
+ // @since **v5**
+ metadata?: 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 full payto URI.
+ credit_account: string;
+
+ // Timestamp that indicates when the wire transfer was 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;
+ }
+
+--------------------------------
+Querying the transaction history
+--------------------------------
+
+The exchange's bank account is derived from the username in the
+``Authorization`` header and/or the request's base URL. In fact, the
+transaction history may come from a "virtual" account, where several real bank
+accounts are merged into a single 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.
+
+ **Request:**
+
+ :query limit: *Optional.*
+ At most return the given number of results. Negative for descending by ``row_id``, positive for ascending by ``row_id``. Defaults to ``-20``. Since protocol **v2**.
+ :query offset: *Optional.*
+ Starting ``row_id`` for :ref:`pagination <row-id-pagination>`. Since protocol **v2**.
+ :query timeout_ms: *Optional.*
+ Timeout in milliseconds, for :ref:`long-polling <long-polling>`, to wait for at least one element to be shown. Only useful if *limit* is positive. Since protocol **v2**.
+ :query delta: *Optional.*
+ Deprecated in protocol **v2**. Use *limit* instead.
+ :query start: *Optional.*
+ Deprecated in protocol **v2**. Use *offset* instead.
+ :query long_poll_ms: *Optional.*
+ Deprecated in protocol **v2**. Use *timeout_ms* instead.
+
+ **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[];
+
+ // Full 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 =
+ | IncomingKycAuthTransaction
+ | IncomingReserveTransaction
+ | IncomingWadTransaction;
+
+ .. ts:def:: IncomingKycAuthTransaction
+
+ // Since protocol **v1**.
+ interface IncomingKycAuthTransaction {
+ type: "KYCAUTH";
+
+ // Opaque identifier of the returned record.
+ row_id: SafeUint64;
+
+ // Date of the transaction.
+ date: Timestamp;
+
+ // Amount received before credit_fee.
+ amount: Amount;
+
+ // Fee paid by the creditor.
+ // If not null, creditor actually received amount - credit_fee
+ // @since **v3**
+ credit_fee?: Amount;
+
+ // Full payto URI to identify the sender of funds.
+ debit_account: string;
+
+ // The account public key extracted from the transaction details.
+ account_pub: EddsaPublicKey;
+
+ // The authorization public key used for mapping
+ // @since **v5**
+ authorization_pub?: EddsaPublicKey;
+
+ // Signature of the account public key using the authorization private key
+ // @since **v5**
+ authorization_signature?: EddsaSignature;
+ }
+
+ .. ts:def:: IncomingReserveTransaction
+
+ interface IncomingReserveTransaction {
+ type: "RESERVE";
+
+ // Opaque identifier of the returned record.
+ row_id: SafeUint64;
+
+ // Date of the transaction.
+ date: Timestamp;
+
+ // Amount received before credit_fee.
+ amount: Amount;
+
+ // Fee payed by the creditor.
+ // If not null, creditor actually received amount -
+ // @since **v3**
+ credit_fee?: Amount;
+
+ // Full payto URI to identify the sender of funds.
+ debit_account: string;
+
+ // The reserve public key extracted from the transaction details.
+ reserve_pub: EddsaPublicKey;
+
+ // The authorization public key used for mapping
+ // @since **v5**
+ authorization_pub?: EddsaPublicKey;
+
+ // Signature of the reserve public key using the authorization private key
+ // @since **v5**
+ authorization_signature?: EddsaSignature;
+ }
+
+ .. ts:def:: IncomingWadTransaction
+
+ interface IncomingWadTransaction {
+ type: "WAD";
+
+ // Opaque identifier of the returned record.
+ row_id: SafeUint64;
+
+ // Date of the transaction.
+ date: Timestamp;
+
+ // Amount received before credit_fee.
+ amount: Amount;
+
+ // Fee payed by the creditor.
+ // If not null, creditor actually received amount - credit_fee
+ // @since **v3**
+ credit_fee?: Amount;
+
+ // Full 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.
+
+ **Request:**
+
+ :query limit: *Optional.*
+ At most return the given number of results. Negative for descending by ``row_id``, positive for ascending by ``row_id``. Defaults to ``-20``. Since protocol **v2**.
+ :query offset: *Optional.*
+ Starting ``row_id`` for :ref:`pagination <row-id-pagination>`. Since protocol **v2**.
+ :query timeout_ms: *Optional.*
+ Timeout in milliseconds, for :ref:`long-polling <long-polling>`, to wait for at least one element to be shown. Only useful if *limit* is positive. Since protocol **v2**.
+ :query delta: *Optional.*
+ Deprecated in protocol **v2**. Use *limit* instead.
+ :query start: *Optional.*
+ Deprecated in protocol **v2**. Use *offset* instead.
+ :query long_poll_ms: *Optional.*
+ Deprecated in protocol **v2**. Use *timeout_ms* instead.
+
+ **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[];
+
+ // Full 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;
+
+ // Fee paid by the debtor.
+ // If not null, debtor actually paid amount + debit_fee
+ // @since **v3**
+ debit_fee?: Amount;
+
+ // Full 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;
+
+ // Optional additional metadata.
+ // @since **v5**
+ metadata?: string;
+ }
+
+
+-----------------
+Wire Account APIs
+-----------------
+
+.. http:get:: /account/check
+
+ Check account existence.
+
+ Since protocol **v4**.
+
+ **Request:**
+
+ :query account:
+ Payto URI of the account.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ JSON object of type `AccountInfo`.
+ :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`:
+ * ``TALER_EC_BANK_UNKNOWN_ACCOUNT``: unknown account.
+ :http:statuscode:`501 Not Implemented`:
+ This server does not support account check.
+
+ **Details:**
+
+ .. ts:def:: AccountInfo
+
+ interface AccountInfo {
+ }
+
+-----------------------
+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 full 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 "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;
+ }
+
+
+
+.. _twg-admin-add-kycauth:
+
+.. http:post:: /admin/add-kycauth
+
+ Simulate a transfer from a customer to the exchange. This API is *not*
+ idempotent since it's only used in testing.
+
+ **Request:**
+
+ .. ts:def:: AddKycauthRequest
+
+ interface AddKycauthRequest {
+ // Amount to transfer.
+ amount: Amount;
+
+ // Account public key that is included in the wire transfer details
+ // to associate this key with the originating bank account.
+ account_pub: EddsaPublicKey;
+
+ // Account (as full 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 "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 `AddKycauthResponse`.
+ :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.
+
+ **Details:**
+
+ .. ts:def:: AddKycauthResponse
+
+ interface AddKycauthResponse {
+ // 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.
diff --git a/core/api-bank-wire-transfer.rst b/core/api-bank-wire-transfer.rst
@@ -0,0 +1,227 @@
+..
+ This file is part of GNU TALER.
+ Copyright (C) 2026 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-transfer-http-api:
+
+============================
+Taler Wire Transfer HTTP API
+============================
+
+This section describes the API offered by Taler wire adapters. The API is
+used by clients such as wallets to prepare wire transfers. This allows the use
+of wire-specific subject format or optimized alternating wire transfer flows,
+and enables the use of recurring wire transfers.
+
+.. http:get:: /config
+
+ Return the protocol version and configuration information about the bank.
+ This specification corresponds to ``current`` protocol being version **1**.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The adapter responds with a `WireTransferConfig` object. This request
+ should virtually always be successful.
+
+ **Details:**
+
+ .. ts:def:: SubjectFormat
+
+ type SubjectFormat =
+ | "SIMPLE"
+ | "URI"
+ | "CH_QR_BILL";
+
+ .. ts:def:: WireTransferConfig
+
+ interface WireTransferConfig {
+ // Name of the API.
+ name: "taler-wire-transfer";
+
+ // 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;
+
+ // Supported formats for registration, there must at least one.
+ supported_formats: SubjectFormat[];
+ }
+
+-----------------------
+Prepared wire transfers
+-----------------------
+
+
+.. http:post:: /prepared-transfers
+
+ Register a public key for wire transfer use.
+
+ This endpoint generate appropriate subjects to link a transfer to the
+ registered public key.
+
+ Two public keys must be provided, the ``account_pub`` key is the key that
+ will forwarded to the exchange and the ``authorization_pub`` key will be
+ encoded inside the subject.
+
+ For simple one time wire transfers, use the same key for both ``account_pub``
+ and ``authorization_pub``. For recurrent transfers, use a single
+ ``authorization_pub`` for different ``account_pub``.
+
+ If registered as ``recurrent`` the wire adapters will keep incoming transfers
+ reusing the same subject until a registration is performed, else it will
+ bounce.
+
+ Registration with the same ``authorization_pu`` will replace the existing information registered for the key.
+
+ **Request:**
+
+ .. ts:def:: PreparedTransferRequest
+
+ interface PreparedTransferRequest {
+ // Amount to transfer
+ credit_amount: Amount;
+
+ // Transfer types
+ type: "reserve" | "kyc";
+
+ // Public key algorithm
+ alg: "EdDSA";
+
+ // Account public key for the exchange
+ account_pub: EddsaPublicKey;
+
+ // Public key encoded inside the subject
+ authorization_pub: EddsaPublicKey;
+
+ // Signature of the account_pub key using the authorization_pub private key
+ authorization_signature: EddsaSignature;
+
+ // Whether the authorization_pub will be reused for recurrent transfers
+ // Disable bounces in case of authorization_pub reuse
+ recurrent: boolean;
+ }
+
+ **Response:**
+
+ :http:statuscode:`200 Ok`:
+ Response is a `PreparedTransferResponse`.
+ :http:statuscode:`400 Bad request`:
+ Input data was invalid.
+ :http:statuscode:`409 Conflict`:
+ * ``TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT``: the same reserve public key is already registered, you should retry using another key.
+ * ``TALER_EC_BANK_DERIVATION_REUSE``: derived subject is already used, you should retry using another key.
+ * ``TALER_EC_BANK_BAD_SIGNATURE``: signature is invalid.
+
+ **Details:**
+
+ .. ts:def:: TransferSubject
+
+ // Union discriminated by the "type" field.
+ type TransferSubject =
+ | SimpleSubject
+ | UriSubject
+ | SwissQrBillSubject;
+
+ .. ts:def:: SimpleSubject
+
+ interface SimpleSubject {
+ // Subject for system accepting large subjects
+ type: "SIMPLE";
+
+ // Amount to transfer
+ credit_amount: Amount;
+
+ // Encoded string containing either the full key and transfer type or a
+ // derived short subject
+ subject: string;
+ }
+
+ .. ts:def:: UriSubject
+
+ interface UriSubject {
+ // Subject for system accepting prepared payments
+ type: "URI";
+
+ // Amount to transfer
+ credit_amount: Amount;
+
+ // Prepared payments confirmation URI
+ uri: string;
+ }
+
+ .. ts:def:: SwissQrBillSubject
+
+ interface SwissQrBillSubject {
+ // Subject for Swiss QR Bill
+ type: "CH_QR_BILL";
+
+ // Amount to transfer
+ credit_amount: Amount;
+
+ // 27-digit QR Reference number
+ qr_reference_number: string;
+ }
+
+ .. ts:def:: PreparedTransferResponse
+
+ interface PreparedTransferResponse {
+ // The transfer subject encoded in all supported formats
+ subjects: TransferSubject[];
+
+ // Expiration date after which this subject is expected to be reused
+ expiration: Timestamp;
+ }
+
+.. http:delete:: /prepared-transfers
+
+ Remove an existing registered public key for wire transfer use.
+
+ Use this endpoint to free a derived subject or cancel a recurrent paiment.
+
+
+ **Request:**
+
+ .. ts:def:: Unregistration
+
+ interface Unregistration {
+ // Current timestamp in the ISO 8601
+ timestamp: string;
+
+ // Public key used for registration
+ authorization_pub: EddsaPublicKey;
+
+ // Signature of the timestamp using the authorization_pub private key
+ // Prevent replay attack
+ authorization_signature: EddsaSignature;
+ }
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ The registration have been deleted.
+ :http:statuscode:`400 Bad request`:
+ Input data was invalid.
+ :http:statuscode:`404 Not found`:
+ Unknown registration.
+ :http:statuscode:`409 Conflict`:
+ * ``TALER_EC_BANK_OLD_TIMESTAMP``: the timestamp is too old.
+ * ``TALER_EC_BANK_BAD_SIGNATURE``: signature is invalid.
+\ No newline at end of file
diff --git a/core/api-bank-wire.rst b/core/api-bank-wire.rst
@@ -1,697 +0,0 @@
-..
- This file is part of GNU TALER.
- Copyright (C) 2019-2025, 2026 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.
-
-.. http:get:: /config
-
- Return the protocol version and configuration information about the bank.
- This specification corresponds to ``current`` protocol being version **5**.
-
- **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;
-
- // Whether implementation support account existence check
- support_account_check: boolean;
- }
-
---------------
-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
-
- Initiate a new wire transfer from the exchange's bank account, typically to a
- merchant.
-
- The exchange's bank account is not included in the request, but instead
- derived from the username in the ``Authorization`` 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 differs 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;
-
- // Optional additional metadata to be stored in the transaction.
- // Must match [a-zA-Z0-9-.:]{1, 40}
- // @since **v5**
- metadata?: 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 full 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`:
- * ``TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED``: an operation with the same ``request_uid`` but different details has been submitted before.
- * ``TALER_EC_BANK_TRANSFER_WTID_REUSED``: an operation with the same ``wtid`` but a different ``request_uid`` 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;
- }
-
-.. http:get:: /transfers
-
- Return a list of transfers initiated from the exchange.
-
- The bank account of the exchange is determined via the base URL and/or the
- user name in the ``Authorization`` header. The transfer history
- might come from a "virtual" account, where multiple real bank accounts are
- merged into one history.
-
- Since protocol **v3**.
-
- **Request:**
-
- :query limit: *Optional.*
- At most return the given number of results. Negative for descending by
- ``row_id``, positive for ascending by ``row_id``. Defaults to ``-20``.
- :query offset: *Optional.*
- Starting ``row_id`` for :ref:`pagination <row-id-pagination>`.
- :query status: *Optional*.
- Filters by status.
-
- **Response:**
-
- :http:statuscode:`200 OK`:
- JSON object of type `TransferList`.
- :http:statuscode:`204 No content`:
- There are no transfers to report (under the given filter).
- :http:statuscode:`400 Bad request`:
- Request malformed.
- :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.
-
- **Details:**
-
- .. ts:def:: TransferList
-
- interface TransferList {
- // Array of initiated transfers.
- transfers: TransferListStatus[];
-
- // Full 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:: TransferListStatus
-
- interface TransferListStatus {
- // Opaque ID of the wire transfer initiation performed by the bank.
- // It is different from the /history endpoints row_id.
- row_id: SafeUint64;
-
- // Current status of the transfer
- // pending: the transfer is in progress
- // transient_failure: the transfer has failed but may succeed later
- // permanent_failure: the transfer has failed permanently and will never appear in the outgoing history
- // success: the transfer has succeeded and appears in the outgoing history
- status: "pending" | "transient_failure" | "permanent_failure" | "success";
-
- // Amount to transfer.
- amount: Amount;
-
- // The recipient's account identifier as a full payto:// URI.
- credit_account: string;
-
- // Timestamp that indicates when the wire transfer was 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;
- }
-
-
-.. http:get:: /transfers/$ROW_ID
-
- Return the status of a transfer initiated from the exchange, identified by the ``ROW_ID``.
-
- Since protocol **v3**.
-
- **Response:**
-
- :http:statuscode:`200 OK`:
- The transfer is known, and details are given in the `TransferStatus` response body.
- :http:statuscode:`400 Bad request`:
- Request malformed.
- :http:statuscode:`401 Unauthorized`:
- Authentication failed, likely the credentials are wrong.
- :http:statuscode:`404 Not found`:
- The transfer was not found.
-
- **Details:**
-
- .. ts:def:: TransferStatus
-
- interface TransferStatus {
- // Current status of the transfer
- // pending: the transfer is in progress
- // transient_failure: the transfer has failed but may succeed later
- // permanent_failure: the transfer has failed permanently and will never appear in the outgoing history
- // success: the transfer has succeeded and appears in the outgoing history
- status: "pending" | "transient_failure" | "permanent_failure" | "success";
-
- // Optional unstructured messages about the transfer's status. Can be used to document the reasons for failure or the state of progress.
- status_msg?: string;
-
- // 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;
-
- // Optional additional metadata to be stored in the transaction.
- // @since **v5**
- metadata?: 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 full payto URI.
- credit_account: string;
-
- // Timestamp that indicates when the wire transfer was 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;
- }
-
---------------------------------
-Querying the transaction history
---------------------------------
-
-The exchange's bank account is derived from the username in the
-``Authorization`` header and/or the request's base URL. In fact, the
-transaction history may come from a "virtual" account, where several real bank
-accounts are merged into a single 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.
-
- **Request:**
-
- :query limit: *Optional.*
- At most return the given number of results. Negative for descending by ``row_id``, positive for ascending by ``row_id``. Defaults to ``-20``. Since protocol **v2**.
- :query offset: *Optional.*
- Starting ``row_id`` for :ref:`pagination <row-id-pagination>`. Since protocol **v2**.
- :query timeout_ms: *Optional.*
- Timeout in milliseconds, for :ref:`long-polling <long-polling>`, to wait for at least one element to be shown. Only useful if *limit* is positive. Since protocol **v2**.
- :query delta: *Optional.*
- Deprecated in protocol **v2**. Use *limit* instead.
- :query start: *Optional.*
- Deprecated in protocol **v2**. Use *offset* instead.
- :query long_poll_ms: *Optional.*
- Deprecated in protocol **v2**. Use *timeout_ms* instead.
-
- **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[];
-
- // Full 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 =
- | IncomingKycAuthTransaction
- | IncomingReserveTransaction
- | IncomingWadTransaction;
-
- .. ts:def:: IncomingKycAuthTransaction
-
- // Since protocol **v1**.
- interface IncomingKycAuthTransaction {
- type: "KYCAUTH";
-
- // Opaque identifier of the returned record.
- row_id: SafeUint64;
-
- // Date of the transaction.
- date: Timestamp;
-
- // Amount received before credit_fee.
- amount: Amount;
-
- // Fee paid by the creditor.
- // If not null, creditor actually received amount - credit_fee
- // @since **v3**
- credit_fee?: Amount;
-
- // Full payto URI to identify the sender of funds.
- debit_account: string;
-
- // The account public key extracted from the transaction details.
- account_pub: EddsaPublicKey;
- }
-
- .. ts:def:: IncomingReserveTransaction
-
- interface IncomingReserveTransaction {
- type: "RESERVE";
-
- // Opaque identifier of the returned record.
- row_id: SafeUint64;
-
- // Date of the transaction.
- date: Timestamp;
-
- // Amount received before credit_fee.
- amount: Amount;
-
- // Fee payed by the creditor.
- // If not null, creditor actually received amount -
- // @since **v3**
- credit_fee?: Amount;
-
- // Full 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 received before credit_fee.
- amount: Amount;
-
- // Fee payed by the creditor.
- // If not null, creditor actually received amount - credit_fee
- // @since **v3**
- credit_fee?: Amount;
-
- // Full 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.
-
- **Request:**
-
- :query limit: *Optional.*
- At most return the given number of results. Negative for descending by ``row_id``, positive for ascending by ``row_id``. Defaults to ``-20``. Since protocol **v2**.
- :query offset: *Optional.*
- Starting ``row_id`` for :ref:`pagination <row-id-pagination>`. Since protocol **v2**.
- :query timeout_ms: *Optional.*
- Timeout in milliseconds, for :ref:`long-polling <long-polling>`, to wait for at least one element to be shown. Only useful if *limit* is positive. Since protocol **v2**.
- :query delta: *Optional.*
- Deprecated in protocol **v2**. Use *limit* instead.
- :query start: *Optional.*
- Deprecated in protocol **v2**. Use *offset* instead.
- :query long_poll_ms: *Optional.*
- Deprecated in protocol **v2**. Use *timeout_ms* instead.
-
- **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[];
-
- // Full 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;
-
- // Fee paid by the debtor.
- // If not null, debtor actually paid amount + debit_fee
- // @since **v3**
- debit_fee?: Amount;
-
- // Full 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;
-
- // Optional additional metadata.
- // @since **v5**
- metadata?: string;
- }
-
-
------------------
-Wire Account APIs
------------------
-
-.. http:get:: /account/check
-
- Check account existence.
-
- Since protocol **v4**.
-
- **Request:**
-
- :query account:
- Payto URI of the account.
-
- **Response:**
-
- :http:statuscode:`200 OK`:
- JSON object of type `AccountInfo`.
- :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`:
- * ``TALER_EC_BANK_UNKNOWN_ACCOUNT``: unknown account.
- :http:statuscode:`501 Not Implemented`:
- This server does not support account check.
-
- **Details:**
-
- .. ts:def:: AccountInfo
-
- interface AccountInfo {
- }
-
------------------------
-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 full 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 "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;
- }
-
-
-
-.. _twg-admin-add-kycauth:
-
-.. http:post:: /admin/add-kycauth
-
- Simulate a transfer from a customer to the exchange. This API is *not*
- idempotent since it's only used in testing.
-
- **Request:**
-
- .. ts:def:: AddKycauthRequest
-
- interface AddKycauthRequest {
- // Amount to transfer.
- amount: Amount;
-
- // Account public key that is included in the wire transfer details
- // to associate this key with the originating bank account.
- account_pub: EddsaPublicKey;
-
- // Account (as full 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 "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 `AddKycauthResponse`.
- :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.
-
- **Details:**
-
- .. ts:def:: AddKycauthResponse
-
- interface AddKycauthResponse {
- // 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.
diff --git a/core/api-corebank.rst b/core/api-corebank.rst
@@ -31,7 +31,7 @@ it provides features for local/regional currencies.
Version History
---------------
-The current protocol version is **v11**.
+The current protocol version is **v12**.
* Android cashier app is currently targeting **v9**.
@@ -39,6 +39,7 @@ The current protocol version is **v11**.
* ``v10``: Update two factor authentication API to match Merchant Backend API
* ``v11``: Add observability API
+* ``v12``: Add wire transfer API
**Upcoming versions:**
@@ -57,7 +58,7 @@ Config
.. http:get:: /config
Return the protocol version and configuration information about the bank.
- This specification corresponds to ``current`` protocol being version **v11**.
+ This specification corresponds to ``current`` protocol being version **v12**.
**Response:**
@@ -1066,7 +1067,8 @@ Account withdrawals
:http:statuscode:`409 Conflict`:
* ``TALER_EC_BANK_CONFIRM_ABORT_CONFLICT`` : the withdrawal has been aborted previously and can't be confirmed.
* ``TALER_EC_BANK_CONFIRM_INCOMPLETE`` : the withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before.
- * ``TALER_EC_BANK_UNALLOWED_DEBIT`` : the account does not have sufficient funds or the amount is too low or too high.
+ * ``TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT``: the reserve public key is already used.
+ * ``TALER_EC_BANK_UNALLOWED_DEBIT`` : the account does not have sufficient funds or the amount is too low or too high
* ``TALER_EC_BANK_AMOUNT_DIFFERS`` : the specified amount will not work for this withdrawal (since **v6**).
* ``TALER_EC_BANK_AMOUNT_REQUIRED`` : the backend requires an amount to be specified (since **v6**).
@@ -1877,10 +1879,18 @@ Endpoints for Integrated Sub-APIs
.. http:any:: /accounts/$USERNAME/taler-wire-gateway/*
All endpoints under this prefix are specified
- by the :doc:`GNU Taler wire gateway API </core/api-bank-wire>`.
+ by the :doc:`GNU Taler wire gateway API </core/api-bank-wire-gateway>`.
The endpoints are only available for accounts configured with ``is_taler_exchange=true``.
+.. http:any:: /accounts/$USERNAME/taler-wire-transfer/*
+
+ Since **v12**
+
+ All endpoints under this prefix are specified
+ by the :doc:`GNU Taler wire transfer API </core/api-bank-wire-transfer>`.
+
+ The endpoints are only available for accounts configured with ``is_taler_exchange=true``.
.. http:any:: /accounts/$USERNAME/taler-revenue/*
diff --git a/core/api-overview.rst b/core/api-overview.rst
@@ -78,16 +78,26 @@ Overview
* **Summary**: Allows the Taler Exchange to query incoming transactions and initiate payments with a protocol that abstracts away details of the underlying banking system.
-* **Providers**: Taler fakebank, LibEuFin Nexus, Depoloymerization wire gateway
+* **Providers**: Taler fakebank, LibEuFin Nexus, Cyclos adapter, Depolymerization adapters
* **Consumers**: GNU Taler Exchange, Wire Auditor
-* :doc:`Docs <api-bank-wire>`
+* :doc:`Docs <api-bank-wire-gateway>`
+
+.. rubric:: Taler Wire Transfer API
+
+* **Summary**: Allows Taler clients to prepared wire transfers, enabling recurring wire transfers and optimized transfer flow.
+
+* **Providers**: LibEuFin Nexus, Cyclos adapter, Depolymerization adapters.
+
+* **Consumers**: Taler Wallet
+
+* :doc:`Docs <api-bank-wire-transfer>`
.. rubric:: Taler Bank Revenue API
* **Summary**: Offered by banks to provide clients the ability to download credit transaction histories.
-* **Providers**: Taler fakebank, LibEuFin bank, Banks (that provide extra Taler support)
+* **Providers**: Taler fakebank, LibEuFin bank, LibEuFin Nexus, Cyclos adapter, Depolymerization adapters, Banks (that provide extra Taler support)
* **Consumers**: Taler Merchant, GNU Anastasis
* :doc:`Docs <api-bank-revenue>`
diff --git a/core/api-terminal.rst b/core/api-terminal.rst
@@ -378,6 +378,6 @@ Endpoints for Integrated Sub-APIs
.. http:any:: /taler-wire-gateway/*
All endpoints under this prefix are specified
- by the :doc:`GNU Taler wire gateway API </core/api-bank-wire>`.
+ by the :doc:`GNU Taler wire gateway API </core/api-bank-wire-gateway>`.
The endpoints are only available for accounts configured with ``is_taler_exchange=true``.
diff --git a/core/index-bank-apis.rst b/core/index-bank-apis.rst
@@ -29,7 +29,8 @@ Bank RESTful APIs
intro-bank-apis
api-corebank
- api-bank-wire
+ api-bank-wire-gateway
+ api-bank-wire-transfer
api-bank-revenue
api-bank-integration
api-bank-conversion-info
diff --git a/design-documents/080-short-wire-subject.rst b/design-documents/080-short-wire-subject.rst
@@ -159,176 +159,12 @@ Encode the remainder into a string and add the 27th checksum character according
Auditor
-------
-By running the subject derivation logic itself, the auditor can match corresponding transfers.
+By running the subject derivation logic itself and using the new authorization public key and signature fields in the wire gateway API, the auditor can match corresponding transfers.
Taler Wire Transfer HTTP API
----------------------------
-.. http:get:: /config
-
- **Response:**
-
- :http:statuscode:`200 OK`:
- The exchange responds with a `WireConfig` object. This request should
- virtually always be successful.
-
- **Details:**
-
- .. ts:def:: SubjectFormat
-
- type SubjectFormat =
- | "SIMPLE"
- | "URI"
- | "CH_QR_BILL";
-
- .. ts:def:: WireTransferConfig
-
- interface WireTransferConfig {
- // Supported formats for registration, there must at least one.
- supported_formats: SubjectFormat[];
- }
-
-
-.. http:post:: /registration
-
- Register a public key for wire transfer use.
-
- This endpoint generate an appropriate subject to link a transfer to the
- registered public key.
-
- A mapping public key must be used to allow recurrent wire transfers.
- Reusing a mapping public key replace previous mapping.
-
- **Request:**
-
- .. ts:def:: SubjectRequest
-
- interface SubjectRequest {
- // Amount to transfer
- credit_amount: Amount;
-
- // Transfer types
- type: "reserve" | "kyc";
-
- // Public key algorithm
- alg: "EdDSA";
-
- // Account public key
- account_pub: EddsaPublicKey;
-
- // Public key encoded inside the subject
- authorization_pub: EddsaPublicKey;
-
- // Signature of the account_pub public key using the authorization_pub private key
- authorization_signature: EddsaSignature;
-
- // Whether the authorization_pub will be reused for recurrent transfers
- // Disable bounces in case of authorization_pub reuse
- recurrent: boolean;
- }
-
- **Response:**
-
- :http:statuscode:`200 Ok`:
- Response is a `SubjectResult`.
- :http:statuscode:`400 Bad request`:
- Input data was invalid.
- :http:statuscode:`409 Conflict`:
- * ``TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT``: the same reserve public key is already registered, you should retry using another key.
- * ``TALER_EC_BANK_DERIVATION_REUSE``: derived subject is already used, you should retry using another key.
- * ``TALER_EC_BANK_BAD_SIGNATURE``: signature is invalid.
-
-
- **Details:**
-
-
- .. ts:def:: TransferSubject
-
- // Union discriminated by the "type" field.
- type TransferSubject =
- | SimpleSubject
- | UriSubject
- | SwissQrBillSubject;
-
- .. ts:def:: SimpleSubject
-
- interface SimpleSubject {
- // Subject for system accepting large subjects
- type: "SIMPLE";
-
- // Amount to transfer
- credit_amount: Amount;
-
- // Encoded string containing either the full key and transfer type or a derived short subject
- subject: string;
- }
-
- .. ts:def:: UriSubject
-
- interface UriSubject {
- // Subject for system accepting prepared payments
- type: "URI";
-
- // Prepared payments confirmation URI
- uri: string;
- }
-
- .. ts:def:: SwissQrBillSubject
-
- interface SwissQrBillSubject {
- // Subject for Swiss QR Bill
- type: "CH_QR_BILL";
-
- // Amount to transfer
- credit_amount: Amount;
-
- // 27-digit QR Reference number
- qr_reference_number: string;
- }
-
- .. ts:def:: SubjectResult
-
- interface SubjectResult {
- // The transfer subject encoded in all supported formats
- subjects: TransferSubject[];
-
- // Expiration date after which this subject will be reused
- expiration: Timestamp;
- }
-
-.. http:delete:: /registration
-
- Remove an existing registration.
-
- Use this endpoint to free a derived subject or cancel a recurrent paiment.
-
- **Request:**
-
- .. ts:def:: Unregistration
-
- interface Unregistration {
- // Current timestamp in the ISO 8601
- timestamp: string;
-
- // Public key used for registration
- authorization_pub: EddsaPublicKey;
-
- // Signature of the timestamp using the authorization_pub private key
- // Necessary to prevent replay attack
- authorization_signature: EddsaSignature;
- }
-
- **Response:**
-
- :http:statuscode:`204 No content`:
- The registration have been deleted.
- :http:statuscode:`400 Bad request`:
- Input data was invalid.
- :http:statuscode:`404 Not found`:
- Unknown registration.
- :http:statuscode:`409 Conflict`:
- * ``TALER_EC_BANK_OLD_TIMESTAMP``: the timestamp is too old.
- * ``TALER_EC_BANK_BAD_SIGNATURE``: signature is invalid.
+See the :ref:`Taler Wire Transfer <taler-wire-transfer-http-api>` documentation.
Test Plan
=========