adding support for transaction history to wire plugin API (#4959)

This commit is contained in:
Christian Grothoff 2017-05-04 18:35:53 +02:00
parent 234dbcc7b7
commit f4df63e448
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
23 changed files with 795 additions and 134 deletions

View File

@ -22,25 +22,22 @@ DB_CONN_STR = "postgres:///talercheck"
# Change here to enable SEPA wire transfers. # Change here to enable SEPA wire transfers.
ENABLE = NO ENABLE = NO
[exchange-wire-incoming-sepa] # Response for /wire
SEPA_RESPONSE_FILE = "sepa.json" SEPA_RESPONSE_FILE = "sepa.json"
[exchange-wire-outgoing-sepa] [exchange-wire-outgoing-sepa]
SEPA_RESPONSE_FILE = "sepa.json" # TBD
[exchange-wire-sepa]
[exchange-wire-test]
# Change here to disable TEST wire transfers.
ENABLE = YES ENABLE = YES
[exchange-wire-incoming-test] # Response for /wire
TEST_RESPONSE_FILE = "test.json"
# What is the main website of the bank? # What is the main website of the bank?
BANK_URI = "https://bank/" BANK_URI = "https://bank/"
# Into which account at the 'bank' should incoming # Into which account at the 'bank' should incoming
# wire transfers be made? # wire transfers be made?
BANK_ACCOUNT_NUMBER = 2 BANK_ACCOUNT_NUMBER = 2
[exchange-wire-outgoing-test]
# What is the main website of the bank?
BANK_URI = "https://bank/"
# From which account at the 'bank' should outgoing
# wire transfers be made?
BANK_ACCOUNT_NUMBER = 2

View File

@ -441,36 +441,24 @@ option @cite{currency} in section @cite{[taler]}.
@section Bank account @section Bank account
@menu @menu
* Wireformat:: * Wiremethod-test::
* Incoming:: * Wiremethod-sepa::
* Outgoing::
@end menu @end menu
@node Wireformat @node Wiremethod-test
@subsection Wireformat @subsection Wiremethod-test
The wireformat is the protocol to be used between the exchange and the
banks. The option is @cite{wireformat}, under section
@cite{[exchange]}. The exchange currently supports the @cite{test}
wireformat. This wireformat is used for testing the system against a
fictional bank.
@cartouche
@quotation Note
The SEPA wireformat is work in progress.
@end quotation
@end cartouche
@node Incoming
@subsection Incoming
To enable the wire transfer method ``test'', you need to set
``ENABLE=YES'' in section @cite{[exchange-wire-test]}.
The bank account where the exchange gets money from customers is The bank account where the exchange gets money from customers is
configured under the section @cite{[exchange-wire-incoming-X]}, where configured under the section @cite{[exchange-wire-test]}. It must
@cite{X} matches the value given to the option @cite{wireformat}. This contain the options ``EXCHANGE_ACCOUNT_NO'' and ``BANK_URI''.
section contains only one option: @cite{X_response_file}, which takes For basic authentication, it must additionally contain the
``USERNAME'' and ``PASSWORD'' of the exchange's account at the bank.
For incoming transfers, the section must additional contain the
option: @cite{test_response_file}, which takes
the path to a text file containing the exchange's bank account details the path to a text file containing the exchange's bank account details
in JSON format. in JSON format.
@ -483,21 +471,47 @@ $ taler-exchange-wire -j '@{"name": "The Exchange", "account_number":
test -o exchange.json test -o exchange.json
@end example @end example
Note that the value given to option @cite{-t} must match the value in
the JSON's field @code{"type"}.
The generated file will be echoed by the exchange when serving
/wire@footnote{https://api.taler.net/api-exchange.html#wire-req}
requests.
@node Wiremethod-sepa
@subsection Wiremethod-sepa
To enable the wire transfer method ``sepa'', you need to set
``ENABLE=YES'' in section @cite{[exchange-wire-sepa]}.
The bank account where the exchange gets money from customers is
configured under the section @cite{[exchange-wire-incoming-sepa]}. This
section contains only one option: @cite{sepa_response_file}, which takes
the path to a text file containing the exchange's bank account details
in JSON format.
The command line tool @cite{taler-exchange-wire} is used to create such a file.
For example, the utility may be invoked as follows:
@example
$ taler-exchange-wire -j '@{"name": "The Exchange", "account_number":
10, "bank_uri": "https://bank.demo.taler.net", "type": "sepa"@}' -t
sepa -o exchange.json
@end example
Note that the value given to option @cite{-t} must match the value in the JSON's field @code{"type"}. Note that the value given to option @cite{-t} must match the value in the JSON's field @code{"type"}.
The generated file will be echoed by the exchange when serving The generated file will be echoed by the exchange when serving
/wire@footnote{https://api.taler.net/api-exchange.html#wire-req} /wire@footnote{https://api.taler.net/api-exchange.html#wire-req}
requests. requests.
@node Outgoing This exchange's outgoing bank account is used to give money to merchants, after
@subsection Outgoing
This exchange's bank account is used to give money to merchants, after
successful successful
deposits@footnote{https://api.taler.net/api-exchange.html#deposit-par} deposits@footnote{https://api.taler.net/api-exchange.html#deposit-par}
operations. If @cite{test} is the chosen wireformat, the outcoming operations. The outgoing bank account is configured by the following
bank account is configured by the following options under options under @cite{[exchange-wire-outgoing-sepa]}: (NOT YET SUPPORTED).
@cite{[exchange-wire-outcoming-test]}:
@quotation @quotation

View File

@ -16,7 +16,8 @@ libtalerbank_la_LDFLAGS = \
libtalerbank_la_SOURCES = \ libtalerbank_la_SOURCES = \
bank_api_admin.c \ bank_api_admin.c \
bank_api_common.c bank_api_common.h bank_api_common.c bank_api_common.h \
bank_api_history.c
libtalerbank_la_LIBADD = \ libtalerbank_la_LIBADD = \
$(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/json/libtalerjson.la \

View File

@ -64,34 +64,6 @@ struct TALER_BANK_AdminAddIncomingHandle
}; };
/**
* Obtain the URL to use for an API request.
*
* @param u base URL of the bank
* @param path Taler API path (i.e. "/reserve/withdraw")
* @return the full URI to use with cURL
*/
static char *
path_to_url (const char *u,
const char *path)
{
char *url;
if ( ('/' == path[0]) &&
(0 < strlen (u)) &&
('/' == u[strlen (u) - 1]) )
path++; /* avoid generating URL with "//" from concat */
GNUNET_asprintf (&url,
"%s%s",
u,
path);
return url;
}
/** /**
* Function called when we're done processing the * Function called when we're done processing the
* HTTP /admin/add/incoming request. * HTTP /admin/add/incoming request.
@ -195,7 +167,7 @@ TALER_BANK_admin_add_incoming (struct GNUNET_CURL_Context *ctx,
aai = GNUNET_new (struct TALER_BANK_AdminAddIncomingHandle); aai = GNUNET_new (struct TALER_BANK_AdminAddIncomingHandle);
aai->cb = res_cb; aai->cb = res_cb;
aai->cb_cls = res_cb_cls; aai->cb_cls = res_cb_cls;
aai->request_url = path_to_url (bank_base_url, aai->request_url = TALER_BANK_path_to_url_ (bank_base_url,
"/admin/add/incoming"); "/admin/add/incoming");
aai->authh = TALER_BANK_make_auth_header_ (auth); aai->authh = TALER_BANK_make_auth_header_ (auth);
eh = curl_easy_init (); eh = curl_easy_init ();

View File

@ -85,4 +85,32 @@ TALER_BANK_make_auth_header_ (const struct TALER_BANK_AuthenticationData *auth)
return authh; return authh;
} }
/**
* Obtain the URL to use for an API request.
*
* @param u base URL of the bank
* @param path Taler API path (i.e. "/history")
* @return the full URI to use with cURL
*/
char *
TALER_BANK_path_to_url_ (const char *u,
const char *path)
{
char *url;
if ( ('/' == path[0]) &&
(0 < strlen (u)) &&
('/' == u[strlen (u) - 1]) )
path++; /* avoid generating URL with "//" from concat */
GNUNET_asprintf (&url,
"%s%s",
u,
path);
return url;
}
/* end of bank_api_common.c */ /* end of bank_api_common.c */

View File

@ -39,4 +39,16 @@ struct curl_slist *
TALER_BANK_make_auth_header_ (const struct TALER_BANK_AuthenticationData *auth); TALER_BANK_make_auth_header_ (const struct TALER_BANK_AuthenticationData *auth);
/**
* Obtain the URL to use for an API request.
*
* @param u base URL of the bank
* @param path Taler API path (i.e. "/history")
* @return the full URI to use with cURL
*/
char *
TALER_BANK_path_to_url_ (const char *u,
const char *path);
#endif #endif

View File

@ -0,0 +1,328 @@
/*
This file is part of TALER
Copyright (C) 2017 GNUnet e.V. & Inria
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see
<http://www.gnu.org/licenses/>
*/
/**
* @file bank-lib/bank_api_history.c
* @brief Implementation of the /history requests of the bank's HTTP API
* @author Christian Grothoff
*/
#include "platform.h"
#include "bank_api_common.h"
#include <microhttpd.h> /* just for HTTP status codes */
#include "taler_signatures.h"
/**
* @brief A /history Handle
*/
struct TALER_BANK_HistoryHandle
{
/**
* The url for this request.
*/
char *request_url;
/**
* The base URL of the bank.
*/
char *bank_base_url;
/**
* Handle for the request.
*/
struct GNUNET_CURL_Job *job;
/**
* HTTP authentication-related headers for the request.
*/
struct curl_slist *authh;
/**
* Function to call with the result.
*/
TALER_BANK_HistoryResultCallback hcb;
/**
* Closure for @a cb.
*/
void *hcb_cls;
};
/**
* Parse history given in JSON format and invoke the callback on each item.
*
* @param hh handle to the account history request
* @param history JSON array with the history
* @return #GNUNET_OK if history was valid and @a rhistory and @a balance
* were set,
* #GNUNET_SYSERR if there was a protocol violation in @a history
*/
static int
parse_account_history (struct TALER_BANK_HistoryHandle *hh,
const json_t *history)
{
for (unsigned int i=0;i<json_array_size (history);i++)
{
struct TALER_BANK_TransferDetails td;
const char *sign;
uint64_t other_account;
uint64_t serial_id;
enum TALER_BANK_Direction direction;
struct GNUNET_JSON_Specification hist_spec[] = {
GNUNET_JSON_spec_string ("sign",
&sign),
TALER_JSON_spec_amount ("amount",
&td.amount),
GNUNET_JSON_spec_absolute_time ("date",
&td.execution_date),
GNUNET_JSON_spec_uint64 ("row_id",
&serial_id),
GNUNET_JSON_spec_string ("wire_subject",
&td.wire_transfer_subject),
GNUNET_JSON_spec_uint64 ("other_account",
&other_account),
GNUNET_JSON_spec_end()
};
json_t *transaction = json_array_get (history,
i);
if (GNUNET_OK !=
GNUNET_JSON_parse (transaction,
hist_spec,
NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
td.account_details = json_pack ("{s:s, s:s, s:I}",
"type", "test",
"bank_uri", hh->bank_base_url,
"account_number", (json_int_t) other_account);
direction = (0 == strcasecmp (sign,
"+"))
? TALER_BANK_DIRECTION_CREDIT
: TALER_BANK_DIRECTION_DEBIT;
hh->hcb (hh->hcb_cls,
MHD_HTTP_OK,
direction,
serial_id,
&td,
transaction);
GNUNET_JSON_parse_free (hist_spec);
json_decref (td.account_details);
}
return GNUNET_OK;
}
/**
* Function called when we're done processing the
* HTTP /admin/add/incoming request.
*
* @param cls the `struct TALER_BANK_HistoryHandle`
* @param response_code HTTP response code, 0 on error
* @param json parsed JSON result, NULL on error
*/
static void
handle_history_finished (void *cls,
long response_code,
const json_t *json)
{
struct TALER_BANK_HistoryHandle *hh = cls;
hh->job = NULL;
switch (response_code)
{
case 0:
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
parse_account_history (hh,
json))
{
GNUNET_break_op (0);
response_code = 0;
break;
}
response_code = MHD_HTTP_NO_CONTENT; /* signal end of list */
break;
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the bank is buggy
(or API version conflict); just pass JSON reply to the application */
break;
case MHD_HTTP_FORBIDDEN:
/* Access denied */
break;
case MHD_HTTP_UNAUTHORIZED:
/* Nothing really to verify, bank says one of the signatures is
invalid; as we checked them, this should never happen, we
should pass the JSON reply to the application */
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
break;
default:
/* unexpected response code */
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u\n",
(unsigned int) response_code);
GNUNET_break (0);
response_code = 0;
break;
}
hh->hcb (hh->hcb_cls,
response_code,
TALER_BANK_DIRECTION_NONE,
0LLU,
NULL,
json);
TALER_BANK_history_cancel (hh);
}
/**
* Request the wire transfer history of a bank account.
*
* @param ctx curl context for the event loop
* @param bank_base_url URL of the bank (used to execute this request)
* @param auth authentication data to use
* @param account_number which account number should we query
* @param direction what kinds of wire transfers should be returned
* @param start_row from which row on do we want to get results, use UINT64_MAX for the latest; exclusive
* @param num_results how many results do we want; negative numbers to go into the past,
* positive numbers to go into the future starting at @a start_row;
* must not be zero.
* @param hres_cb the callback to call with the transaction history
* @param hres_cb_cls closure for the above callback
* @return NULL
* if the inputs are invalid (i.e. zero value for @e num_results).
* In this case, the callback is not called.
*/
struct TALER_BANK_HistoryHandle *
TALER_BANK_history (struct GNUNET_CURL_Context *ctx,
const char *bank_base_url,
const struct TALER_BANK_AuthenticationData *auth,
uint64_t account_number,
enum TALER_BANK_Direction direction,
uint64_t start_row,
int64_t num_results,
TALER_BANK_HistoryResultCallback hres_cb,
void *hres_cb_cls)
{
struct TALER_BANK_HistoryHandle *hh;
CURL *eh;
char *url;
if (0 == num_results)
{
GNUNET_break (0);
return NULL;
}
if (TALER_BANK_DIRECTION_NONE == direction)
{
GNUNET_break (0);
return NULL;
}
if (UINT64_MAX == start_row)
{
if (TALER_BANK_DIRECTION_BOTH == direction)
GNUNET_asprintf (&url,
"/history?account_number=%llu&num_results=%lld",
(unsigned long long) account_number,
(long long) num_results);
else
GNUNET_asprintf (&url,
"/history?account_number=%llu&num_results=%lld&direction=%s",
(unsigned long long) account_number,
(long long) num_results,
(TALER_BANK_DIRECTION_CREDIT == direction) ? "credit" : "debit");
}
else
{
if (TALER_BANK_DIRECTION_BOTH == direction)
GNUNET_asprintf (&url,
"/history?account_number=%llu&num_results=%lld&start_row=%llu",
(unsigned long long) account_number,
(long long) num_results,
(unsigned long long) start_row);
else
GNUNET_asprintf (&url,
"/history?account_number=%llu&num_results=%lld&start_row=%llu&direction=%s",
(unsigned long long) account_number,
(long long) num_results,
(unsigned long long) start_row,
(TALER_BANK_DIRECTION_CREDIT == direction) ? "credit" : "debit");
}
hh = GNUNET_new (struct TALER_BANK_HistoryHandle);
hh->hcb = hres_cb;
hh->hcb_cls = hres_cb_cls;
hh->bank_base_url = GNUNET_strdup (bank_base_url);
hh->request_url = TALER_BANK_path_to_url_ (bank_base_url,
url);
GNUNET_free (url);
hh->authh = TALER_BANK_make_auth_header_ (auth);
eh = curl_easy_init ();
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_HTTPHEADER,
hh->authh));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_URL,
hh->request_url));
hh->job = GNUNET_CURL_job_add (ctx,
eh,
GNUNET_NO,
&handle_history_finished,
hh);
return hh;
}
/**
* Cancel a history request. This function cannot be used on a request
* handle if a response is already served for it.
*
* @param hh the history request handle
*/
void
TALER_BANK_history_cancel (struct TALER_BANK_HistoryHandle *hh)
{
if (NULL != hh->job)
{
GNUNET_CURL_job_cancel (hh->job);
hh->job = NULL;
}
curl_slist_free_all (hh->authh);
GNUNET_free (hh->request_url);
GNUNET_free (hh->bank_base_url);
GNUNET_free (hh);
}
/* end of bank_api_history.c */

View File

@ -26,14 +26,12 @@ DB_CONN_STR = "postgres:///talercheck"
# Enable 'test' for testing of the actual coin operations. # Enable 'test' for testing of the actual coin operations.
ENABLE = YES ENABLE = YES
[exchange-wire-outgoing-test]
# What is the main website of the bank? # What is the main website of the bank?
# (Not used unless the aggregator is run.) # (Not used unless the aggregator is run.)
BANK_URI = "http://localhost:8082/" BANK_URI = "http://localhost:8082/"
# From which account at the 'bank' should outgoing wire transfers be made? # From which account at the 'bank' should outgoing wire transfers be made?
BANK_ACCOUNT_NUMBER = 2 BANK_ACCOUNT_NUMBER = 2
[exchange-wire-incoming-test]
# This is the response we give out for the /wire request. It provides # This is the response we give out for the /wire request. It provides
# wallets with the bank information for transfers to the exchange. # wallets with the bank information for transfers to the exchange.
TEST_RESPONSE_FILE = ${TALER_CONFIG_HOME}/test.json TEST_RESPONSE_FILE = ${TALER_CONFIG_HOME}/test.json

View File

@ -30,6 +30,10 @@ DB_CONN_STR = "postgres:///talercheck"
# Enable 'sepa' to test SEPA-specific routines. # Enable 'sepa' to test SEPA-specific routines.
ENABLE = YES ENABLE = YES
# This is the response we give out for the /wire request. It provides
# wallets with the bank information for transfers to the exchange.
SEPA_RESPONSE_FILE = ${TALER_CONFIG_HOME}/sepa.json
# Fees for the forseeable future... # Fees for the forseeable future...
# If you see this after 2017, update to match the next 10 years... # If you see this after 2017, update to match the next 10 years...
WIRE-FEE-2017 = EUR:0.01 WIRE-FEE-2017 = EUR:0.01
@ -54,11 +58,6 @@ CLOSING-FEE-2024 = EUR:0.01
CLOSING-FEE-2025 = EUR:0.01 CLOSING-FEE-2025 = EUR:0.01
CLOSING-FEE-2026 = EUR:0.01 CLOSING-FEE-2026 = EUR:0.01
[exchange-wire-incoming-sepa]
# This is the response we give out for the /wire request. It provides
# wallets with the bank information for transfers to the exchange.
SEPA_RESPONSE_FILE = ${TALER_CONFIG_HOME}/sepa.json
[exchange-wire-test] [exchange-wire-test]
# Enable 'test' for testing of the actual coin operations. # Enable 'test' for testing of the actual coin operations.
ENABLE = YES ENABLE = YES
@ -87,17 +86,16 @@ CLOSING-FEE-2024 = EUR:0.01
CLOSING-FEE-2025 = EUR:0.01 CLOSING-FEE-2025 = EUR:0.01
CLOSING-FEE-2026 = EUR:0.01 CLOSING-FEE-2026 = EUR:0.01
[exchange-wire-incoming-test]
# This is the response we give out for the /wire request. It provides # This is the response we give out for the /wire request. It provides
# wallets with the bank information for transfers to the exchange. # wallets with the bank information for transfers to the exchange.
TEST_RESPONSE_FILE = ${TALER_CONFIG_HOME}/test.json TEST_RESPONSE_FILE = ${TALER_CONFIG_HOME}/test.json
[exchange-wire-outgoing-test]
# What is the main website of the bank? # What is the main website of the bank?
BANK_URI = "http://localhost:8082/" BANK_URI = "http://localhost:8082/"
# From which account at the 'bank' should outgoing wire transfers be made? # From which account at the 'bank' should outgoing wire transfers be made?
BANK_ACCOUNT_NUMBER = 2 BANK_ACCOUNT_NUMBER = 2
[coin_eur_ct_1] [coin_eur_ct_1]
value = EUR:0.01 value = EUR:0.01
duration_overlap = 5 minutes duration_overlap = 5 minutes

View File

@ -187,21 +187,21 @@ then
# If possible, initialize outgoing wire account details ('test' method only) # If possible, initialize outgoing wire account details ('test' method only)
if (test "test" = "$WMETHOD" -a ! -z "$ARG_BANK_URI") if (test "test" = "$WMETHOD" -a ! -z "$ARG_BANK_URI")
then then
$CS -s exchange-wire-outgoing-test -o BANK_URI -V "$ARG_BANK_URI" || exit 1 $CS -s exchange-wire-test -o BANK_URI -V "$ARG_BANK_URI" || exit 1
else else
echo "Skipped generating outgoing wire account details for exchange" echo "Skipped generating wire account details for exchange"
fi fi
if (test "test" = "$ARG_W" -a ! -z "$ARG_EXCHANGE_BANK_ACCOUNT") if (test "test" = "$ARG_W" -a ! -z "$ARG_EXCHANGE_BANK_ACCOUNT")
then then
$CS -s exchange-wire-outgoing-test -o BANK_ACCOUNT_NUMBER -V "$ARG_EXCHANGE_BANK_ACCOUNT" || exit 1 $CS -s exchange-wire-test -o BANK_ACCOUNT_NUMBER -V "$ARG_EXCHANGE_BANK_ACCOUNT" || exit 1
else else
echo "Skipped generating outgoing wire account details for exchange" echo "Skipped generating wire account details for exchange"
fi fi
# If possible, initialize /wire response from JSON (with signature) # If possible, initialize /wire response from JSON (with signature)
if (test ! -z $ARG_JE) if (test ! -z $ARG_JE)
then then
JSONF=`$CS -s exchange-wire-incoming-${ARG_W} -o ${ARG_W}_RESPONSE_FILE -f` JSONF=`$CS -s exchange-wire-${ARG_W} -o ${ARG_W}_RESPONSE_FILE -f`
# echo "Generating /wire response at $JSONF" # echo "Generating /wire response at $JSONF"
mkdir -p `dirname $JSONF` mkdir -p `dirname $JSONF`
taler-exchange-wire -c "$ARG_CONFIG" -t "$ARG_W" -j "$ARG_JE" -m "$MASTER_KEY" -o "$JSONF" || exit 1 taler-exchange-wire -c "$ARG_CONFIG" -t "$ARG_W" -j "$ARG_JE" -m "$MASTER_KEY" -o "$JSONF" || exit 1

View File

@ -207,12 +207,10 @@ TEH_json_validate_wireformat (const json_t *wire,
* Obtain JSON of the supported wire methods for a given * Obtain JSON of the supported wire methods for a given
* account name prefix. * account name prefix.
* *
* @param prefix prefix for the account, the suffix will
* be determined by the name of the plugin
* @return JSON array with the supported validation methods * @return JSON array with the supported validation methods
*/ */
json_t * json_t *
TEH_VALIDATION_get_wire_methods (const char *prefix) TEH_VALIDATION_get_wire_methods ()
{ {
json_t *methods; json_t *methods;
char *account_name; char *account_name;
@ -227,8 +225,7 @@ TEH_VALIDATION_get_wire_methods (const char *prefix)
json_t *fees; json_t *fees;
GNUNET_asprintf (&account_name, GNUNET_asprintf (&account_name,
"%s-%s", "exchange-wire-%s",
prefix,
p->type); p->type);
method = plugin->get_wire_details (plugin->cls, method = plugin->get_wire_details (plugin->cls,
cfg, cfg,

View File

@ -61,12 +61,10 @@ TEH_json_validate_wireformat (const json_t *wire,
* Obtain JSON of the supported wire methods for a given * Obtain JSON of the supported wire methods for a given
* account name prefix. * account name prefix.
* *
* @param prefix prefix for the account, the suffix will
* be determined by the name of the plugin
* @return JSON array with the supported validation methods * @return JSON array with the supported validation methods
*/ */
json_t * json_t *
TEH_VALIDATION_get_wire_methods (const char *prefix); TEH_VALIDATION_get_wire_methods (void);
#endif #endif

View File

@ -143,7 +143,7 @@ TEH_WIRE_handler_wire (struct TEH_RequestHandler *rh,
int int
TEH_WIRE_init () TEH_WIRE_init ()
{ {
wire_methods = TEH_VALIDATION_get_wire_methods ("exchange-wire-incoming"); wire_methods = TEH_VALIDATION_get_wire_methods ();
if ( (NULL == wire_methods) || if ( (NULL == wire_methods) ||
(0 == json_object_size (wire_methods)) ) (0 == json_object_size (wire_methods)) )
{ {

View File

@ -63,7 +63,6 @@ CLOSING-FEE-2025 = EUR:0.01
CLOSING-FEE-2026 = EUR:0.01 CLOSING-FEE-2026 = EUR:0.01
[exchange-wire-outgoing-test]
# What is the main website of the bank? # What is the main website of the bank?
BANK_URI = "http://localhost:8082/" BANK_URI = "http://localhost:8082/"

View File

@ -36,7 +36,6 @@ DB_CONN_STR = "postgres:///talercheck"
# Enable 'test' for testing of the actual coin operations. # Enable 'test' for testing of the actual coin operations.
ENABLE = YES ENABLE = YES
[exchange-wire-outgoing-test]
# What is the main website of the bank? # What is the main website of the bank?
BANK_URI = "http://localhost:8082/" BANK_URI = "http://localhost:8082/"

View File

@ -150,10 +150,15 @@ TALER_BANK_admin_add_incoming_cancel (struct TALER_BANK_AdminAddIncomingHandle *
/** /**
* Which types of transactions should be returned? * Which types of transactions should be (or is being) returned?
*/ */
enum TALER_BANK_Direction { enum TALER_BANK_Direction {
/**
* Base case, used to indicate errors or end of list.
*/
TALER_BANK_DIRECTION_NONE = 0,
/** /**
* Transactions where the bank account receives money. * Transactions where the bank account receives money.
*/ */
@ -192,15 +197,10 @@ struct TALER_BANK_TransferDetails
*/ */
struct GNUNET_TIME_Absolute execution_date; struct GNUNET_TIME_Absolute execution_date;
/**
* monotonically increasing counter corresponding to the transaction
*/
uint64_t serial_id;
/** /**
* wire transfer subject * wire transfer subject
*/ */
char *wire_transfer_subject; const char *wire_transfer_subject;
/** /**
* what was the other account that was involved * what was the other account that was involved
@ -220,6 +220,7 @@ struct TALER_BANK_TransferDetails
* last callback is always of this status (even if `abs(num_results)` were * last callback is always of this status (even if `abs(num_results)` were
* already returned). * already returned).
* @param dir direction of the transfer * @param dir direction of the transfer
* @param serial_id monotonically increasing counter corresponding to the transaction
* @param details details about the wire transfer * @param details details about the wire transfer
* @param json detailed response from the HTTPD, or NULL if reply was not in JSON * @param json detailed response from the HTTPD, or NULL if reply was not in JSON
*/ */
@ -227,22 +228,20 @@ typedef void
(*TALER_BANK_HistoryResultCallback) (void *cls, (*TALER_BANK_HistoryResultCallback) (void *cls,
unsigned int http_status, unsigned int http_status,
enum TALER_BANK_Direction dir, enum TALER_BANK_Direction dir,
uint64_t serial_id,
const struct TALER_BANK_TransferDetails *details, const struct TALER_BANK_TransferDetails *details,
const json_t *json); const json_t *json);
/** /**
* Notify the bank that we have received an incoming transaction * Request the wire transfer history of a bank account.
* which fills a reserve. Note that this API is an administrative
* API and thus not accessible to typical bank clients, but only
* to the operators of the bank.
* *
* @param ctx curl context for the event loop * @param ctx curl context for the event loop
* @param bank_base_url URL of the bank (used to execute this request) * @param bank_base_url URL of the bank (used to execute this request)
* @param auth authentication data to use * @param auth authentication data to use
* @param account_number which account number should we query * @param account_number which account number should we query
* @param direction what kinds of wire transfers should be returned * @param direction what kinds of wire transfers should be returned
* @param start_row from which row on do we want to get results, use UINT64_MAX for the latest * @param start_row from which row on do we want to get results, use UINT64_MAX for the latest; exclusive
* @param num_results how many results do we want; negative numbers to go into the past, * @param num_results how many results do we want; negative numbers to go into the past,
* positive numbers to go into the future starting at @a start_row; * positive numbers to go into the future starting at @a start_row;
* must not be zero. * must not be zero.

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2016 GNUnet e.V. Copyright (C) 2016, 2017 GNUnet e.V. & Inria
TALER is free software; you can redistribute it and/or modify it under the TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software terms of the GNU General Public License as published by the Free Software
@ -25,6 +25,7 @@
#include <jansson.h> #include <jansson.h>
#include "taler_util.h" #include "taler_util.h"
#include "taler_error_codes.h" #include "taler_error_codes.h"
#include "taler_bank_service.h" /* for `enum TALER_BANK_Direction` and `struct TALER_BANK_TransferDetails` */
/** /**
@ -40,17 +41,47 @@ typedef void
size_t buf_size); size_t buf_size);
/**
* Callbacks of this type are used to serve the result of asking
* the bank for the transaction history.
*
* @param cls closure
* @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
* 0 if the bank's reply is bogus (fails to follow the protocol),
* #MHD_HTTP_NO_CONTENT if there are no more results; on success the
* last callback is always of this status (even if `abs(num_results)` were
* already returned).
* @param dir direction of the transfer
* @param row_off identification of the position at which we are querying
* @param row_off_size number of bytes in @a row_off
* @param details details about the wire transfer
* @param json detailed response from the HTTPD, or NULL if reply was not in JSON
*/
typedef void
(*TALER_WIRE_HistoryResultCallback) (void *cls,
unsigned int http_status,
enum TALER_BANK_Direction dir,
const void *row_off,
size_t row_off_size,
const struct TALER_BANK_TransferDetails *details,
const json_t *json);
/** /**
* Handle returned for cancelling a preparation step. * Handle returned for cancelling a preparation step.
*/ */
struct TALER_WIRE_PrepareHandle; struct TALER_WIRE_PrepareHandle;
/** /**
* Handle returned for cancelling an execution step. * Handle returned for cancelling an execution step.
*/ */
struct TALER_WIRE_ExecuteHandle; struct TALER_WIRE_ExecuteHandle;
/**
* Handle returned for querying the transaction history.
*/
struct TALER_WIRE_HistoryHandle;
/** /**
* Function called with the result from the execute step. * Function called with the result from the execute step.
@ -217,6 +248,43 @@ struct TALER_WIRE_Plugin
struct TALER_WIRE_ExecuteHandle *eh); struct TALER_WIRE_ExecuteHandle *eh);
/**
* Query transfer history of an account. We use the variable-size
* @a start_off to indicate which transfers we are interested in as
* different banking systems may have different ways to identify
* transfers. The @a start_off value must thus match the value of
* a `row_off` argument previously given to the @a hres_cb. Use
* NULL to query transfers from the beginning of time (with
* positive @a num_results) or from the latest committed transfers
* (with negative @a num_results).
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param direction what kinds of wire transfers should be returned
* @param start_off from which row on do we want to get results, use NULL for the latest; exclusive
* @param start_off_len number of bytes in @a start_off
* @param num_results how many results do we want; negative numbers to go into the past,
* positive numbers to go into the future starting at @a start_row;
* must not be zero.
* @param hres_cb the callback to call with the transaction history
* @param hres_cb_cls closure for the above callback
*/
struct TALER_WIRE_HistoryHandle *
(*get_history) (void *cls,
enum TALER_BANK_Direction direction,
const void *start_off,
size_t start_off_len,
int64_t num_results,
TALER_WIRE_HistoryResultCallback hres_cb,
void *hres_cb_cls);
/**
* Cancel going over the account's history.
*
* @param whh operation to cancel
*/
void
(*get_history_cancel) (struct TALER_WIRE_HistoryHandle *whh);
}; };

View File

@ -735,6 +735,52 @@ sepa_execute_wire_transfer_cancel (void *cls,
} }
/**
* Query transfer history of an account. We use the variable-size
* @a start_off to indicate which transfers we are interested in as
* different banking systems may have different ways to identify
* transfers. The @a start_off value must thus match the value of
* a `row_off` argument previously given to the @a hres_cb. Use
* NULL to query transfers from the beginning of time (with
* positive @a num_results) or from the latest committed transfers
* (with negative @a num_results).
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param direction what kinds of wire transfers should be returned
* @param start_off from which row on do we want to get results, use NULL for the latest; exclusive
* @param start_off_len number of bytes in @a start_off; must be `sizeof(uint64_t)`.
* @param num_results how many results do we want; negative numbers to go into the past,
* positive numbers to go into the future starting at @a start_row;
* must not be zero.
* @param hres_cb the callback to call with the transaction history
* @param hres_cb_cls closure for the above callback
*/
static struct TALER_WIRE_HistoryHandle *
sepa_get_history (void *cls,
enum TALER_BANK_Direction direction,
const void *start_off,
size_t start_off_len,
int64_t num_results,
TALER_WIRE_HistoryResultCallback hres_cb,
void *hres_cb_cls)
{
GNUNET_break (0);
return NULL;
}
/**
* Cancel going over the account's history.
*
* @param whh operation to cancel
*/
static void
sepa_get_history_cancel (struct TALER_WIRE_HistoryHandle *whh)
{
GNUNET_break (0);
}
/** /**
* Initialize sepa-wire subsystem. * Initialize sepa-wire subsystem.
* *
@ -774,6 +820,8 @@ libtaler_plugin_wire_sepa_init (void *cls)
plugin->prepare_wire_transfer_cancel = &sepa_prepare_wire_transfer_cancel; plugin->prepare_wire_transfer_cancel = &sepa_prepare_wire_transfer_cancel;
plugin->execute_wire_transfer = &sepa_execute_wire_transfer; plugin->execute_wire_transfer = &sepa_execute_wire_transfer;
plugin->execute_wire_transfer_cancel = &sepa_execute_wire_transfer_cancel; plugin->execute_wire_transfer_cancel = &sepa_execute_wire_transfer_cancel;
plugin->get_history = &sepa_get_history;
plugin->get_history_cancel = &sepa_get_history_cancel;
return plugin; return plugin;
} }

View File

@ -219,6 +219,52 @@ template_execute_wire_transfer_cancel (void *cls,
} }
/**
* Query transfer history of an account. We use the variable-size
* @a start_off to indicate which transfers we are interested in as
* different banking systems may have different ways to identify
* transfers. The @a start_off value must thus match the value of
* a `row_off` argument previously given to the @a hres_cb. Use
* NULL to query transfers from the beginning of time (with
* positive @a num_results) or from the latest committed transfers
* (with negative @a num_results).
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param direction what kinds of wire transfers should be returned
* @param start_off from which row on do we want to get results, use NULL for the latest; exclusive
* @param start_off_len number of bytes in @a start_off; must be `sizeof(uint64_t)`.
* @param num_results how many results do we want; negative numbers to go into the past,
* positive numbers to go into the future starting at @a start_row;
* must not be zero.
* @param hres_cb the callback to call with the transaction history
* @param hres_cb_cls closure for the above callback
*/
static struct TALER_WIRE_HistoryHandle *
template_get_history (void *cls,
enum TALER_BANK_Direction direction,
const void *start_off,
size_t start_off_len,
int64_t num_results,
TALER_WIRE_HistoryResultCallback hres_cb,
void *hres_cb_cls)
{
GNUNET_break (0);
return NULL;
}
/**
* Cancel going over the account's history.
*
* @param whh operation to cancel
*/
static void
template_get_history_cancel (struct TALER_WIRE_HistoryHandle *whh)
{
GNUNET_break (0);
}
/** /**
* Initialize template-wire subsystem. * Initialize template-wire subsystem.
* *
@ -270,6 +316,8 @@ libtaler_plugin_wire_template_init (void *cls)
plugin->prepare_wire_transfer_cancel = &template_prepare_wire_transfer_cancel; plugin->prepare_wire_transfer_cancel = &template_prepare_wire_transfer_cancel;
plugin->execute_wire_transfer = &template_execute_wire_transfer; plugin->execute_wire_transfer = &template_execute_wire_transfer;
plugin->execute_wire_transfer_cancel = &template_execute_wire_transfer_cancel; plugin->execute_wire_transfer_cancel = &template_execute_wire_transfer_cancel;
plugin->get_history = &template_get_history;
plugin->get_history_cancel = &template_get_history_cancel;
return plugin; return plugin;
} }

View File

@ -62,9 +62,9 @@ struct TestClosure
/** /**
* Number of the account that the exchange has at the bank for * Number of the account that the exchange has at the bank for
* outgoing transfers. * transfers.
*/ */
unsigned long long exchange_account_outgoing_no; unsigned long long exchange_account_no;
}; };
@ -731,7 +731,7 @@ test_execute_wire_transfer (void *cls,
exchange_base_url, exchange_base_url,
&bf.wtid, &bf.wtid,
&amount, &amount,
(uint64_t) tc->exchange_account_outgoing_no, (uint64_t) tc->exchange_account_no,
(uint64_t) account_no, (uint64_t) account_no,
&execute_cb, &execute_cb,
eh); eh);
@ -767,6 +767,166 @@ test_execute_wire_transfer_cancel (void *cls,
} }
/**
* Handle for a #test_get_history() request.
*/
struct TALER_WIRE_HistoryHandle
{
/**
* Function to call with results.
*/
TALER_WIRE_HistoryResultCallback hres_cb;
/**
* Closure for @e hres_cb.
*/
void *hres_cb_cls;
/**
* Request to the bank.
*/
struct TALER_BANK_HistoryHandle *hh;
};
/**
* Function called with results from the bank about the transaction history.
*
* @param cls the `struct TALER_WIRE_HistoryHandle`
* @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
* 0 if the bank's reply is bogus (fails to follow the protocol),
* #MHD_HTTP_NO_CONTENT if there are no more results; on success the
* last callback is always of this status (even if `abs(num_results)` were
* already returned).
* @param dir direction of the transfer
* @param serial_id monotonically increasing counter corresponding to the transaction
* @param details details about the wire transfer
* @param json detailed response from the HTTPD, or NULL if reply was not in JSON
*/
static void
bhist_cb (void *cls,
unsigned int http_status,
enum TALER_BANK_Direction dir,
uint64_t serial_id,
const struct TALER_BANK_TransferDetails *details,
const json_t *json)
{
struct TALER_WIRE_HistoryHandle *whh = cls;
uint64_t bserial_id = GNUNET_htonll (serial_id);
whh->hres_cb (whh->hres_cb_cls,
http_status,
dir,
&bserial_id,
sizeof (bserial_id),
details,
json);
if (MHD_HTTP_OK != http_status)
{
whh->hh = NULL;
GNUNET_free (whh);
return;
}
}
/**
* Query transfer history of an account. We use the variable-size
* @a start_off to indicate which transfers we are interested in as
* different banking systems may have different ways to identify
* transfers. The @a start_off value must thus match the value of
* a `row_off` argument previously given to the @a hres_cb. Use
* NULL to query transfers from the beginning of time (with
* positive @a num_results) or from the latest committed transfers
* (with negative @a num_results).
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param direction what kinds of wire transfers should be returned
* @param start_off from which row on do we want to get results, use NULL for the latest; exclusive
* @param start_off_len number of bytes in @a start_off; must be `sizeof(uint64_t)`.
* @param num_results how many results do we want; negative numbers to go into the past,
* positive numbers to go into the future starting at @a start_row;
* must not be zero.
* @param hres_cb the callback to call with the transaction history
* @param hres_cb_cls closure for the above callback
*/
static struct TALER_WIRE_HistoryHandle *
test_get_history (void *cls,
enum TALER_BANK_Direction direction,
const void *start_off,
size_t start_off_len,
int64_t num_results,
TALER_WIRE_HistoryResultCallback hres_cb,
void *hres_cb_cls)
{
struct TestClosure *tc = cls;
struct TALER_WIRE_HistoryHandle *whh;
const uint64_t *start_off_b64;
uint64_t start_row;
if (0 == num_results)
{
GNUNET_break (0);
return NULL;
}
if (TALER_BANK_DIRECTION_NONE == direction)
{
GNUNET_break (0);
return NULL;
}
if ( (NULL != start_off) &&
(sizeof (uint64_t) != start_off_len) )
{
GNUNET_break (0);
return NULL;
}
if (NULL == start_off)
{
start_row = (num_results > 0) ? 0 : UINT64_MAX;
}
else
{
start_off_b64 = start_off;
start_row = GNUNET_ntohll (*start_off_b64);
}
whh = GNUNET_new (struct TALER_WIRE_HistoryHandle);
whh->hres_cb = hres_cb;
whh->hres_cb_cls = hres_cb_cls;
whh->hh = TALER_BANK_history (tc->ctx,
tc->bank_uri,
&tc->auth,
(uint64_t) tc->exchange_account_no,
direction,
start_row,
num_results,
&bhist_cb,
whh);
if (NULL == whh->hh)
{
GNUNET_break (0);
GNUNET_free (whh);
return NULL;
}
return whh;
}
/**
* Cancel going over the account's history.
*
* @param whh operation to cancel
*/
static void
test_get_history_cancel (struct TALER_WIRE_HistoryHandle *whh)
{
TALER_BANK_history_cancel (whh->hh);
GNUNET_free (whh);
}
/** /**
* Initialize test-wire subsystem. * Initialize test-wire subsystem.
* *
@ -787,24 +947,24 @@ libtaler_plugin_wire_test_init (void *cls)
{ {
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg, GNUNET_CONFIGURATION_get_value_string (cfg,
"exchange-wire-outgoing-test", "exchange-wire-test",
"BANK_URI", "BANK_URI",
&tc->bank_uri)) &tc->bank_uri))
{ {
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchange-wire-outgoing-test", "exchange-wire-test",
"BANK_URI"); "BANK_URI");
GNUNET_free (tc); GNUNET_free (tc);
return NULL; return NULL;
} }
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_number (cfg, GNUNET_CONFIGURATION_get_value_number (cfg,
"exchange-wire-outgoing-test", "exchange-wire-test",
"EXCHANGE_ACCOUNT_NUMBER", "EXCHANGE_ACCOUNT_NUMBER",
&tc->exchange_account_outgoing_no)) &tc->exchange_account_no))
{ {
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchange-wire-outgoing-test", "exchange-wire-test",
"EXCHANGE_ACCOUNT_NUMBER"); "EXCHANGE_ACCOUNT_NUMBER");
GNUNET_free (tc->bank_uri); GNUNET_free (tc->bank_uri);
GNUNET_free (tc); GNUNET_free (tc);
@ -825,12 +985,12 @@ libtaler_plugin_wire_test_init (void *cls)
} }
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg, GNUNET_CONFIGURATION_get_value_string (cfg,
"exchange-wire-outgoing-test", "exchange-wire-test",
"USERNAME", "USERNAME",
&user)) &user))
{ {
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchange-wire-outgoing-test", "exchange-wire-test",
"USERNAME"); "USERNAME");
GNUNET_free (tc->bank_uri); GNUNET_free (tc->bank_uri);
GNUNET_free (tc); GNUNET_free (tc);
@ -838,12 +998,12 @@ libtaler_plugin_wire_test_init (void *cls)
} }
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg, GNUNET_CONFIGURATION_get_value_string (cfg,
"exchange-wire-outgoing-test", "exchange-wire-test",
"PASSWORD", "PASSWORD",
&pass)) &pass))
{ {
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchange-wire-outgoing-test", "exchange-wire-test",
"PASSWORD"); "PASSWORD");
GNUNET_free (tc->bank_uri); GNUNET_free (tc->bank_uri);
GNUNET_free (tc); GNUNET_free (tc);
@ -877,6 +1037,8 @@ libtaler_plugin_wire_test_init (void *cls)
plugin->prepare_wire_transfer_cancel = &test_prepare_wire_transfer_cancel; plugin->prepare_wire_transfer_cancel = &test_prepare_wire_transfer_cancel;
plugin->execute_wire_transfer = &test_execute_wire_transfer; plugin->execute_wire_transfer = &test_execute_wire_transfer;
plugin->execute_wire_transfer_cancel = &test_execute_wire_transfer_cancel; plugin->execute_wire_transfer_cancel = &test_execute_wire_transfer_cancel;
plugin->get_history = &test_get_history;
plugin->get_history_cancel = &test_get_history_cancel;
return plugin; return plugin;
} }

View File

@ -11,7 +11,7 @@ TEST_RESPONSE_FILE = test_wire_plugin_test.json
SEPA_RESPONSE_FILE = test_wire_plugin_sepa.json SEPA_RESPONSE_FILE = test_wire_plugin_sepa.json
[exchange-wire-outgoing-test] [exchange-wire-test]
# For transfers made by the exchange, we need to know # For transfers made by the exchange, we need to know
# the URI of the bank (where the /admin/add/incoming API # the URI of the bank (where the /admin/add/incoming API
# is avaialble). # is avaialble).

View File

@ -6,7 +6,6 @@
# Set to "YES" to activate the 'sepa' plugin. # Set to "YES" to activate the 'sepa' plugin.
ENABLE = NO ENABLE = NO
[exchange-wire-incoming-sepa]
# This is the response we give out for the /wire request. It provides # This is the response we give out for the /wire request. It provides
# wallets with the bank information for transfers to the exchange. # wallets with the bank information for transfers to the exchange.
SEPA_RESPONSE_FILE = ${TALER_CONFIG_HOME}/exchange/wire/sepa.json SEPA_RESPONSE_FILE = ${TALER_CONFIG_HOME}/exchange/wire/sepa.json

View File

@ -6,19 +6,15 @@
# Set to "YES" to activate the 'test' plugin. # Set to "YES" to activate the 'test' plugin.
ENABLE = NO ENABLE = NO
[exchange-wire-incoming-test]
# This is the response we give out for the /wire request. It provides # This is the response we give out for the /wire request. It provides
# wallets with the bank information for transfers to the exchange. # wallets with the bank information for transfers to the exchange.
TEST_RESPONSE_FILE = ${TALER_CONFIG_HOME}/exchange/wire/test.json TEST_RESPONSE_FILE = ${TALER_CONFIG_HOME}/exchange/wire/test.json
[exchange-wire-outgoing-test] # We need to know the exchange's account number at the bank.
# For outgoing transfers, we need to know the exchange's
# account number at the bank.
EXCHANGE_ACCOUNT_NUMBER = 2 EXCHANGE_ACCOUNT_NUMBER = 2
# For transfers made by the exchange, we need to know # For accessing transfers, we need to know
# the URI of the bank (where the /admin/add/incoming API # the URI of the bank (where the /history API is available).
# is avaialble).
# BANK_URI = https://bank.demo.taler.net/ # BANK_URI = https://bank.demo.taler.net/
# Authentication information for basic authentication # Authentication information for basic authentication