api-bank-wire.rst (23540B)
1 .. 2 This file is part of GNU TALER. 3 Copyright (C) 2019-2025 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 2.1, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 16 .. _taler-wire-gateway-http-api: 17 18 =========================== 19 Taler Wire Gateway HTTP API 20 =========================== 21 22 This section describes the API offered by the Taler wire gateway. The API is 23 used by the exchange to trigger transactions and query incoming transactions, as 24 well as by the auditor to query incoming and outgoing transactions. 25 26 This API is currently implemented by the Taler Demo Bank, as well as by 27 LibEuFin. 28 29 .. http:get:: /config 30 31 Return the protocol version and configuration information about the bank. 32 This specification corresponds to ``current`` protocol being version **4**. 33 34 **Response:** 35 36 :http:statuscode:`200 OK`: 37 The exchange responds with a `WireConfig` object. This request should 38 virtually always be successful. 39 40 **Details:** 41 42 .. ts:def:: WireConfig 43 44 interface WireConfig { 45 // Name of the API. 46 name: "taler-wire-gateway"; 47 48 // libtool-style representation of the Bank protocol version, see 49 // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning 50 // The format is "current:revision:age". 51 version: string; 52 53 // Currency used by this gateway. 54 currency: string; 55 56 // URN of the implementation (needed to interpret 'revision' in version). 57 // @since v0, may become mandatory in the future. 58 implementation?: string; 59 60 // Wether implementation support account existence check 61 support_account_check: boolean; 62 } 63 64 -------------- 65 Authentication 66 -------------- 67 68 The bank library authenticates requests to the wire gateway via 69 `HTTP basic auth <https://tools.ietf.org/html/rfc7617>`_. 70 71 ------------------- 72 Making Transactions 73 ------------------- 74 75 .. http:post:: /transfer 76 77 Initiate a new wire transfer from the exchange's bank account, typically to a 78 merchant. 79 80 The exchange's bank account is not included in the request, but instead 81 derived from the username in the ``Authorization`` header and/or the request 82 base URL. 83 84 To make the API idempotent, the client must include a nonce. Requests with 85 the same nonce are rejected unless the request is the same. 86 87 **Request:** 88 89 .. ts:def:: TransferRequest 90 91 interface TransferRequest { 92 // Nonce to make the request idempotent. Requests with the same 93 // ``request_uid`` that differs in any of the other fields 94 // are rejected. 95 request_uid: HashCode; 96 97 // Amount to transfer. 98 amount: Amount; 99 100 // Base URL of the exchange. Shall be included by the bank gateway 101 // in the appropriate section of the wire transfer details. 102 exchange_base_url: string; 103 104 // Wire transfer identifier chosen by the exchange, 105 // used by the merchant to identify the Taler order(s) 106 // associated with this wire transfer. 107 wtid: ShortHashCode; 108 109 // The recipient's account identifier as a full payto URI. 110 credit_account: string; 111 } 112 113 **Response:** 114 115 :http:statuscode:`200 OK`: 116 The request has been correctly handled, so the funds have been transferred to 117 the recipient's account. The body is a `TransferResponse`. 118 :http:statuscode:`400 Bad request`: 119 Request malformed. The bank replies with an `ErrorDetail` object. 120 :http:statuscode:`401 Unauthorized`: 121 Authentication failed, likely the credentials are wrong. 122 :http:statuscode:`404 Not found`: 123 The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object. 124 :http:statuscode:`409 Conflict`: 125 * ``TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED``: an operation with the same ``request_uid`` but different details has been submitted before. 126 * ``TALER_EC_BANK_TRANSFER_WTID_REUSED``: an operation with the same ``wtid`` but a different ``request_uid`` has been submitted before. 127 128 **Details:** 129 130 .. ts:def:: TransferResponse 131 132 interface TransferResponse { 133 // Timestamp that indicates when the wire transfer will be executed. 134 // In cases where the wire transfer gateway is unable to know when 135 // the wire transfer will be executed, the time at which the request 136 // has been received and stored will be returned. 137 // The purpose of this field is for debugging (humans trying to find 138 // the transaction) as well as for taxation (determining which 139 // time period a transaction belongs to). 140 timestamp: Timestamp; 141 142 // Opaque ID of the wire transfer initiation performed by the bank. 143 // It is different from the /history endpoints row_id. 144 row_id: SafeUint64; 145 } 146 147 .. http:get:: /transfers 148 149 Return a list of transfers initiated from the exchange. 150 151 The bank account of the exchange is determined via the base URL and/or the 152 user name in the ``Authorization`` header. The transfer history 153 might come from a "virtual" account, where multiple real bank accounts are 154 merged into one history. 155 156 Since protocol **v3**. 157 158 **Request:** 159 160 :query limit: *Optional.* 161 At most return the given number of results. Negative for descending by 162 ``row_id``, positive for ascending by ``row_id``. Defaults to ``-20``. 163 :query offset: *Optional.* 164 Starting ``row_id`` for :ref:`pagination <row-id-pagination>`. 165 :query status: *Optional*. 166 Filters by status. 167 168 **Response:** 169 170 :http:statuscode:`200 OK`: 171 JSON object of type `TransferList`. 172 :http:statuscode:`204 No content`: 173 There are no transfers to report (under the given filter). 174 :http:statuscode:`400 Bad request`: 175 Request malformed. 176 :http:statuscode:`401 Unauthorized`: 177 Authentication failed, likely the credentials are wrong. 178 :http:statuscode:`404 Not found`: 179 The endpoint is wrong or the user name is unknown. 180 181 **Details:** 182 183 .. ts:def:: TransferList 184 185 interface TransferList { 186 // Array of initiated transfers. 187 transfers: TransferListStatus[]; 188 189 // Full payto:// URI to identify the sender of funds. 190 // This must be one of the exchange's bank accounts. 191 // Credit account is shared by all incoming transactions 192 // as per the nature of the request. 193 debit_account: string; 194 } 195 196 .. ts:def:: TransferListStatus 197 198 interface TransferListStatus { 199 // Opaque ID of the wire transfer initiation performed by the bank. 200 // It is different from the /history endpoints row_id. 201 row_id: SafeUint64; 202 203 // Current status of the transfer 204 // pending: the transfer is in progress 205 // transient_failure: the transfer has failed but may succeed later 206 // permanent_failure: the transfer has failed permanently and will never appear in the outgoing history 207 // success: the transfer has succeeded and appears in the outgoing history 208 status: "pending" | "transient_failure" | "permanent_failure" | "success"; 209 210 // Amount to transfer. 211 amount: Amount; 212 213 // The recipient's account identifier as a full payto:// URI. 214 credit_account: string; 215 216 // Timestamp that indicates when the wire transfer was executed. 217 // In cases where the wire transfer gateway is unable to know when 218 // the wire transfer will be executed, the time at which the request 219 // has been received and stored will be returned. 220 // The purpose of this field is for debugging (humans trying to find 221 // the transaction) as well as for taxation (determining which 222 // time period a transaction belongs to). 223 timestamp: Timestamp; 224 } 225 226 227 .. http:get:: /transfers/$ROW_ID 228 229 Return the status of a transfer initiated from the exchange, identified by the ``ROW_ID``. 230 231 Since protocol **v3**. 232 233 **Response:** 234 235 :http:statuscode:`200 OK`: 236 The transfer is known, and details are given in the `TransferStatus` response body. 237 :http:statuscode:`400 Bad request`: 238 Request malformed. 239 :http:statuscode:`401 Unauthorized`: 240 Authentication failed, likely the credentials are wrong. 241 :http:statuscode:`404 Not found`: 242 The transfer was not found. 243 244 **Details:** 245 246 .. ts:def:: TransferStatus 247 248 interface TransferStatus { 249 // Current status of the transfer 250 // pending: the transfer is in progress 251 // transient_failure: the transfer has failed but may succeed later 252 // permanent_failure: the transfer has failed permanently and will never appear in the outgoing history 253 // success: the transfer has succeeded and appears in the outgoing history 254 status: "pending" | "transient_failure" | "permanent_failure" | "success"; 255 256 // Optional unstructured messages about the transfer's status. Can be used to document the reasons for failure or the state of progress. 257 status_msg?: string; 258 259 // Amount to transfer. 260 amount: Amount; 261 262 // Base URL of the exchange. Shall be included by the bank gateway 263 // in the appropriate section of the wire transfer details. 264 exchange_base_url: string; 265 266 // Wire transfer identifier chosen by the exchange, 267 // used by the merchant to identify the Taler order(s) 268 // associated with this wire transfer. 269 wtid: ShortHashCode; 270 271 // The recipient's account identifier as a full payto URI. 272 credit_account: string; 273 274 // Timestamp that indicates when the wire transfer was executed. 275 // In cases where the wire transfer gateway is unable to know when 276 // the wire transfer will be executed, the time at which the request 277 // has been received and stored will be returned. 278 // The purpose of this field is for debugging (humans trying to find 279 // the transaction) as well as for taxation (determining which 280 // time period a transaction belongs to). 281 timestamp: Timestamp; 282 } 283 284 -------------------------------- 285 Querying the transaction history 286 -------------------------------- 287 288 The exchange's bank account is derived from the username in the 289 ``Authorization`` header and/or the request's base URL. In fact, the 290 transaction history may come from a "virtual" account, where several real bank 291 accounts are merged into a single history. 292 293 .. http:get:: /history/incoming 294 295 Return a list of transactions made from or to the exchange. 296 297 Incoming transactions must contain a valid reserve public key. If a bank 298 transaction does not conform to the right syntax, the wire gateway must not 299 report it to the exchange, and send funds back to the sender if possible. 300 301 **Request:** 302 303 :query limit: *Optional.* 304 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**. 305 :query offset: *Optional.* 306 Starting ``row_id`` for :ref:`pagination <row-id-pagination>`. Since protocol **v2**. 307 :query timeout_ms: *Optional.* 308 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**. 309 :query delta: *Optional.* 310 Deprecated in protocol **v2**. Use *limit* instead. 311 :query start: *Optional.* 312 Deprecated in protocol **v2**. Use *offset* instead. 313 :query long_poll_ms: *Optional.* 314 Deprecated in protocol **v2**. Use *timeout_ms* instead. 315 316 **Response:** 317 318 :http:statuscode:`200 OK`: 319 JSON object of type `IncomingHistory`. 320 :http:statuscode:`204 No content`: 321 There are not transactions to report (under the given filter). 322 :http:statuscode:`400 Bad request`: 323 Request malformed. The bank replies with an `ErrorDetail` object. 324 :http:statuscode:`401 Unauthorized`: 325 Authentication failed, likely the credentials are wrong. 326 :http:statuscode:`404 Not found`: 327 The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object. 328 329 **Details:** 330 331 .. ts:def:: IncomingHistory 332 333 interface IncomingHistory { 334 // Array of incoming transactions. 335 incoming_transactions: IncomingBankTransaction[]; 336 337 // Full payto URI to identify the receiver of funds. 338 // This must be one of the exchange's bank accounts. 339 // Credit account is shared by all incoming transactions 340 // as per the nature of the request. 341 credit_account: string; 342 } 343 344 .. ts:def:: IncomingBankTransaction 345 346 // Union discriminated by the "type" field. 347 type IncomingBankTransaction = 348 | IncomingKycAuthTransaction 349 | IncomingReserveTransaction 350 | IncomingWadTransaction; 351 352 .. ts:def:: IncomingKycAuthTransaction 353 354 // Since protocol **v1**. 355 interface IncomingKycAuthTransaction { 356 type: "KYCAUTH"; 357 358 // Opaque identifier of the returned record. 359 row_id: SafeUint64; 360 361 // Date of the transaction. 362 date: Timestamp; 363 364 // Amount received before credit_fee. 365 amount: Amount; 366 367 // Fee paid by the creditor. 368 // If not null, creditor actually received amount - credit_fee 369 // @since **v3** 370 credit_fee?: Amount; 371 372 // Full payto URI to identify the sender of funds. 373 debit_account: string; 374 375 // The account public key extracted from the transaction details. 376 account_pub: EddsaPublicKey; 377 } 378 379 .. ts:def:: IncomingReserveTransaction 380 381 interface IncomingReserveTransaction { 382 type: "RESERVE"; 383 384 // Opaque identifier of the returned record. 385 row_id: SafeUint64; 386 387 // Date of the transaction. 388 date: Timestamp; 389 390 // Amount received before credit_fee. 391 amount: Amount; 392 393 // Fee payed by the creditor. 394 // If not null, creditor actually received amount - 395 // @since **v3** 396 credit_fee?: Amount; 397 398 // Full payto URI to identify the sender of funds. 399 debit_account: string; 400 401 // The reserve public key extracted from the transaction details. 402 reserve_pub: EddsaPublicKey; 403 } 404 405 .. ts:def:: IncomingWadTransaction 406 407 interface IncomingWadTransaction { 408 type: "WAD"; 409 410 // Opaque identifier of the returned record. 411 row_id: SafeUint64; 412 413 // Date of the transaction. 414 date: Timestamp; 415 416 // Amount received before credit_fee. 417 amount: Amount; 418 419 // Fee payed by the creditor. 420 // If not null, creditor actually received amount - credit_fee 421 // @since **v3** 422 credit_fee?: Amount; 423 424 // Full payto URI to identify the sender of funds. 425 debit_account: string; 426 427 // Base URL of the exchange that originated the wad. 428 origin_exchange_url: string; 429 430 // The reserve public key extracted from the transaction details. 431 wad_id: WadId; 432 } 433 434 435 .. http:get:: /history/outgoing 436 437 Return a list of transactions made by the exchange, typically to a merchant. 438 439 **Request:** 440 441 :query limit: *Optional.* 442 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**. 443 :query offset: *Optional.* 444 Starting ``row_id`` for :ref:`pagination <row-id-pagination>`. Since protocol **v2**. 445 :query timeout_ms: *Optional.* 446 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**. 447 :query delta: *Optional.* 448 Deprecated in protocol **v2**. Use *limit* instead. 449 :query start: *Optional.* 450 Deprecated in protocol **v2**. Use *offset* instead. 451 :query long_poll_ms: *Optional.* 452 Deprecated in protocol **v2**. Use *timeout_ms* instead. 453 454 **Response:** 455 456 :http:statuscode:`200 OK`: 457 JSON object of type `OutgoingHistory`. 458 :http:statuscode:`204 No content`: 459 There are not transactions to report (under the given filter). 460 :http:statuscode:`400 Bad request`: 461 Request malformed. The bank replies with an `ErrorDetail` object. 462 :http:statuscode:`401 Unauthorized`: 463 Authentication failed, likely the credentials are wrong. 464 :http:statuscode:`404 Not found`: 465 The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object. 466 467 **Details:** 468 469 .. ts:def:: OutgoingHistory 470 471 interface OutgoingHistory { 472 // Array of outgoing transactions. 473 outgoing_transactions: OutgoingBankTransaction[]; 474 475 // Full payto URI to identify the sender of funds. 476 // This must be one of the exchange's bank accounts. 477 // Credit account is shared by all incoming transactions 478 // as per the nature of the request. 479 debit_account: string; 480 } 481 482 .. ts:def:: OutgoingBankTransaction 483 484 interface OutgoingBankTransaction { 485 // Opaque identifier of the returned record. 486 row_id: SafeUint64; 487 488 // Date of the transaction. 489 date: Timestamp; 490 491 // Amount transferred. 492 amount: Amount; 493 494 // Fee paid by the debtor. 495 // If not null, debtor actually paid amount + debit_fee 496 // @since **v3** 497 debit_fee?: Amount; 498 499 // Full payto URI to identify the receiver of funds. 500 credit_account: string; 501 502 // The wire transfer ID in the outgoing transaction. 503 wtid: ShortHashCode; 504 505 // Base URL of the exchange. 506 exchange_base_url: string; 507 } 508 509 510 ----------------- 511 Wire Account APIs 512 ----------------- 513 514 .. http:get:: /account/check 515 516 Check account existence. 517 518 Since protocol **v4**. 519 520 **Request:** 521 522 :query account: 523 Payto URI of the account. 524 525 **Response:** 526 527 :http:statuscode:`200 OK`: 528 JSON object of type `AccountInfo`. 529 :http:statuscode:`400 Bad request`: 530 Request malformed. The bank replies with an `ErrorDetail` object. 531 :http:statuscode:`401 Unauthorized`: 532 Authentication failed, likely the credentials are wrong. 533 :http:statuscode:`404 Not found`: 534 * ``TALER_EC_BANK_UNKNOWN_ACCOUNT``: unknown account. 535 :http:statuscode:`501 Not Implemented`: 536 This server does not support account check. 537 538 **Details:** 539 540 .. ts:def:: AccountInfo 541 542 interface AccountInfo { 543 } 544 545 ----------------------- 546 Wire Transfer Test APIs 547 ----------------------- 548 549 Endpoints in this section are only used for integration tests and never 550 exposed by bank gateways in production. 551 552 .. _twg-admin-add-incoming: 553 554 .. http:post:: /admin/add-incoming 555 556 Simulate a transfer from a customer to the exchange. This API is *not* 557 idempotent since it's only used in testing. 558 559 **Request:** 560 561 .. ts:def:: AddIncomingRequest 562 563 interface AddIncomingRequest { 564 // Amount to transfer. 565 amount: Amount; 566 567 // Reserve public key that is included in the wire transfer details 568 // to identify the reserve that is being topped up. 569 reserve_pub: EddsaPublicKey; 570 571 // Account (as full payto URI) that makes the wire transfer to the exchange. 572 // Usually this account must be created by the test harness before this 573 // API is used. An exception is the "fakebank", where any debit account 574 // can be specified, as it is automatically created. 575 debit_account: string; 576 } 577 578 **Response:** 579 580 :http:statuscode:`200 OK`: 581 The request has been correctly handled, so the funds have been transferred to 582 the recipient's account. The body is a `AddIncomingResponse`. 583 :http:statuscode:`400 Bad request`: 584 The request is malformed. The bank replies with an `ErrorDetail` object. 585 :http:statuscode:`401 Unauthorized`: 586 Authentication failed, likely the credentials are wrong. 587 :http:statuscode:`404 Not found`: 588 The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object. 589 :http:statuscode:`409 Conflict`: 590 The 'reserve_pub' argument was used previously in another transfer, and the specification mandates that reserve public keys must not be reused. 591 592 **Details:** 593 594 .. ts:def:: AddIncomingResponse 595 596 interface AddIncomingResponse { 597 // Timestamp that indicates when the wire transfer will be executed. 598 // In cases where the wire transfer gateway is unable to know when 599 // the wire transfer will be executed, the time at which the request 600 // has been received and stored will be returned. 601 // The purpose of this field is for debugging (humans trying to find 602 // the transaction) as well as for taxation (determining which 603 // time period a transaction belongs to). 604 timestamp: Timestamp; 605 606 // Opaque ID of the wire transfer initiation performed by the bank. 607 // It is different from the /history endpoints row_id. 608 row_id: SafeUint64; 609 } 610 611 612 613 .. _twg-admin-add-kycauth: 614 615 .. http:post:: /admin/add-kycauth 616 617 Simulate a transfer from a customer to the exchange. This API is *not* 618 idempotent since it's only used in testing. 619 620 **Request:** 621 622 .. ts:def:: AddKycauthRequest 623 624 interface AddKycauthRequest { 625 // Amount to transfer. 626 amount: Amount; 627 628 // Account public key that is included in the wire transfer details 629 // to associate this key with the originating bank account. 630 account_pub: EddsaPublicKey; 631 632 // Account (as full payto URI) that makes the wire transfer to the exchange. 633 // Usually this account must be created by the test harness before this 634 // API is used. An exception is the "fakebank", where any debit account 635 // can be specified, as it is automatically created. 636 debit_account: string; 637 } 638 639 **Response:** 640 641 :http:statuscode:`200 OK`: 642 The request has been correctly handled, so the funds have been transferred to 643 the recipient's account. The body is a `AddKycauthResponse`. 644 :http:statuscode:`400 Bad request`: 645 The request is malformed. The bank replies with an `ErrorDetail` object. 646 :http:statuscode:`401 Unauthorized`: 647 Authentication failed, likely the credentials are wrong. 648 :http:statuscode:`404 Not found`: 649 The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object. 650 651 **Details:** 652 653 .. ts:def:: AddKycauthResponse 654 655 interface AddKycauthResponse { 656 // Timestamp that indicates when the wire transfer will be executed. 657 // In cases where the wire transfer gateway is unable to know when 658 // the wire transfer will be executed, the time at which the request 659 // has been received and stored will be returned. 660 // The purpose of this field is for debugging (humans trying to find 661 // the transaction) as well as for taxation (determining which 662 // time period a transaction belongs to). 663 timestamp: Timestamp; 664 665 // Opaque ID of the wire transfer initiation performed by the bank. 666 // It is different from the /history endpoints row_id. 667 row_id: SafeUint64; 668 } 669 670 671 Security Considerations 672 ======================= 673 674 For implementors: 675 676 * The withdrawal operation ID must contain enough entropy to be unguessable. 677 678 Design: 679 680 * The user must complete the 2FA step of the withdrawal in the context of their banking 681 app or online banking Website. 682 We explicitly reject any design where the user would have to enter a confirmation code 683 they get from their bank in the context of the wallet, as this would teach and normalize 684 bad security habits.