commit a5f4a65c223f396a8b18caa492105793e46df909
parent e94d256e09a9ba6c0283843ffb02ddb2c02e104d
Author: Marcello Stanisci <marcello.stanisci@inria.fr>
Date: Tue, 7 Mar 2017 15:57:34 +0100
Merge branch 'master' of taler.net:merchant
Diffstat:
16 files changed, 1103 insertions(+), 310 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -26,8 +26,10 @@ GRTAGS
GTAGS
*.swp
src/backend/taler-merchant-httpd
+src/merchant-tools/taler-merchant-dbinit
src/lib/test_merchant_api
src/lib/test_merchant_api_home/.local/share/taler/exchange/live-keys/
+src/lib/test_merchant_api_home/.local/share/taler/wirefees/
taler_merchant_config.h
taler_merchant_config.h.in
doc/*
@@ -35,3 +37,4 @@ doc/*
!doc/*.am
!doc/*.sh
!doc/examples/
+src/lib/test_merchant_api_home/.local/share/taler/exchange/wirefees/
diff --git a/ChangeLog b/ChangeLog
@@ -1,3 +1,12 @@
+Mon Mar 6 17:57:51 CET 2017
+ Add support for wire fee calculations to /pay handling (#4935),
+ and adding setting "max_wire_fee" and "wire_fee_amortization"
+ in contract from defaults. -CG
+
+Mon Mar 6 00:59:25 CET 2017
+ Implement BIND_TO option to allow binding backend to a particular
+ IP address (#4752). Enabling use of dual-stack by default. -CG
+
Thu Dec 15 10:37:08 CET 2016
Implementing:
- /map/in, /map/out API, to allow frontends to store
diff --git a/doc/manual.texi b/doc/manual.texi
@@ -507,14 +507,14 @@ Which currency the Web shop deals in, i.e. ``EUR'' or ``USD'', is specified usin
@cindex currency
@cindex KUDOS
@example
-[merchant]/currency
+[taler]/currency
@end example
For testing purposes, the currency MUST match ``KUDOS'' so that tests will work
with the Taler demonstration exchange at @url{https://exchange.demo.taler.net/}:
@example
-$ taler-config -s merchant -o currency -V KUDOS
+$ taler-config -s taler -o currency -V KUDOS
@end example
@item Database
diff --git a/src/backend/merchant.conf b/src/backend/merchant.conf
@@ -5,14 +5,35 @@
# General settings for the backend.
[merchant]
+
+# Use TCP or UNIX domain sockets?
SERVE = tcp
-# Which HTTP port does the backend listen on?
+# Which HTTP port does the backend listen on? Only used if "SERVE" is 'tcp'.
PORT = 9966
+# Which IP address should we bind to? i.e. 127.0.0.1 or ::1 for loopback.
+# Can also be given as a hostname. We will bind to the wildcard (dual-stack)
+# if left empty. Only used if "SERVE" is 'tcp'.
+# BIND_TO =
+
+
+# Which unix domain path should we bind to? Only used if "SERVE" is 'unix'.
UNIXPATH = ${TALER_RUNTIME_DIR}/merchant.http
+# What should be the file access permissions (see chmod) for "UNIXPATH"?
UNIXPATH_MODE = 660
+
+# Maximum wire fee to permit by default. You most certainly want to
+# adjust at least the currency.
+# DEFAULT_MAX_WIRE_FEE = "KUDOS:0.10"
+
+# Which fraction of an exessivly high wire fee is the customer expected
+# to cover? Must be a positive integer representing the expected
+# average number of transactions aggregated by exchanges. 1 is
+# always safe (financially speaking).
+DEFAULT_WIRE_FEE_AMORTIZATION = 1
+
# Where does the backend store the merchant's private key?
KEYFILE = ${TALER_DATA_HOME}/merchant/merchant.priv
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014, 2015, 2016 INRIA
+ (C) 2014-2017 INRIA
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
@@ -73,6 +73,17 @@ static long long unsigned port;
struct GNUNET_TIME_Relative wire_transfer_delay;
/**
+ * Default maximum wire fee to assume, unless stated differently in the proposal
+ * already.
+ */
+struct TALER_Amount default_max_wire_fee;
+
+/**
+ * Default factor for wire fee amortization.
+ */
+unsigned long long default_wire_fee_amortization;
+
+/**
* Should a "Connection: close" header be added to each HTTP response?
*/
int TMH_merchant_connection_close;
@@ -242,19 +253,25 @@ url_handler (void *cls,
/**
* Callback that frees all the elements in the hashmap
*
- * @param cls closure
+ * @param cls closure, NULL
* @param key current key
- * @param value current value
+ * @param value a `struct MerchantInstance`
*/
-int
+static int
hashmap_free (void *cls,
const struct GNUNET_HashCode *key,
void *value)
{
- GNUNET_free (value);
+ struct MerchantInstance *mi = value;
+
+ json_decref (mi->j_wire);
+ GNUNET_free (mi->id);
+ GNUNET_free (mi->keyfile);
+ GNUNET_free (mi);
return GNUNET_YES;
}
+
/**
* Shutdown task (magically invoked when the application is being
* quit)
@@ -269,6 +286,10 @@ do_shutdown (void *cls)
GNUNET_SCHEDULER_cancel (mhd_task);
mhd_task = NULL;
}
+ /* FIXME: MHD API requires us to resume all suspended
+ connections before we do this, but /pay currently
+ suspends connections without giving us a way to
+ enumerate / resume them... */
if (NULL != mhd)
{
MHD_stop_daemon (mhd);
@@ -286,9 +307,15 @@ do_shutdown (void *cls)
&hashmap_free,
NULL);
if (NULL != by_id_map)
+ {
GNUNET_CONTAINER_multihashmap_destroy (by_id_map);
+ by_id_map = NULL;
+ }
if (NULL != by_kpub_map)
+ {
GNUNET_CONTAINER_multihashmap_destroy (by_kpub_map);
+ by_kpub_map = NULL;
+ }
}
@@ -434,13 +461,15 @@ instances_iterator_cb (void *cls,
/* used as hashmap keys */
struct GNUNET_HashCode h_pk;
struct GNUNET_HashCode h_id;
+ json_t *type;
char *emsg;
iic = cls;
substr = strstr (section, "merchant-instance-");
- if ((NULL == substr)
- || (NULL != strstr (section, "merchant-instance-wireformat-")))
+ if ( (NULL == substr) ||
+ (NULL != strstr (section,
+ "merchant-instance-wireformat-")) )
return;
if (substr != section)
@@ -467,6 +496,7 @@ instances_iterator_cb (void *cls,
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
"KEYFILE");
+ GNUNET_free (mi);
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -479,6 +509,8 @@ instances_iterator_cb (void *cls,
(pk = GNUNET_CRYPTO_eddsa_key_create_from_file (mi->keyfile)))
{
GNUNET_break (0);
+ GNUNET_free (mi->keyfile);
+ GNUNET_free (mi);
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -487,11 +519,6 @@ instances_iterator_cb (void *cls,
&mi->pubkey.eddsa_pub);
GNUNET_free (pk);
- /**
- * FIXME: 'token' must NOT be freed, as it is handled by the
- * gnunet_configuration facility. OTOH mi->id does need to be freed,
- * because it is a duplicate.
- */
mi->id = GNUNET_strdup (token + 1);
if (0 == strcmp ("default", mi->id))
iic->default_instance = GNUNET_YES;
@@ -499,11 +526,19 @@ instances_iterator_cb (void *cls,
GNUNET_asprintf (&instance_wiresection,
"merchant-instance-wireformat-%s",
mi->id);
-
mi->j_wire = iic->plugin->get_wire_details (iic->plugin->cls,
iic->config,
instance_wiresection);
GNUNET_free (instance_wiresection);
+ if ( (NULL == (type = json_object_get (mi->j_wire,
+ "type"))) ||
+ (! json_is_string (type)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Malformed wireformat: lacks type\n");
+ iic->ret |= GNUNET_SYSERR;
+ }
+ mi->wire_method = json_string_value (type);
if (TALER_EC_NONE !=
iic->plugin->wire_validate (iic->plugin->cls,
@@ -511,7 +546,6 @@ instances_iterator_cb (void *cls,
NULL,
&emsg))
{
-
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Malformed wireformat: %s\n",
emsg);
@@ -535,7 +569,7 @@ instances_iterator_cb (void *cls,
#endif
GNUNET_CRYPTO_hash (mi->id,
- strlen(mi->id),
+ strlen (mi->id),
&h_id);
GNUNET_CRYPTO_hash (&mi->pubkey.eddsa_pub,
sizeof (struct GNUNET_CRYPTO_EddsaPublicKey),
@@ -550,18 +584,6 @@ instances_iterator_cb (void *cls,
"Failed to put an entry into the 'by_id' hashmap\n");
iic->ret |= GNUNET_SYSERR;
}
- #ifdef EXTRADEBUG
- else {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Added element at %p, by by-id key %s of '%s' in hashmap\n",
- mi,
- GNUNET_h2s (&h_id),
- mi->id);
- GNUNET_assert (NULL != GNUNET_CONTAINER_multihashmap_get (by_id_map,
- &h_id));
- }
- #endif
-
if (GNUNET_OK !=
GNUNET_CONTAINER_multihashmap_put (by_kpub_map,
&h_pk,
@@ -633,8 +655,8 @@ get_instance (struct json_t *json)
*
* @param config configuration handle
* @param allowed which wire format is allowed/expected?
- * @return GNUNET_OK if successful, GNUNET_SYSERR upon errors
- * (for example, if no "defaul" instance is defined)
+ * @return #GNUNET_OK if successful, #GNUNET_SYSERR upon errors
+ * (for example, if no "default" instance is defined)
*/
static unsigned int
iterate_instances (const struct GNUNET_CONFIGURATION_Handle *config,
@@ -688,7 +710,8 @@ iterate_instances (const struct GNUNET_CONFIGURATION_Handle *config,
GNUNET_free (iic);
return GNUNET_OK;
- fail: do {
+ fail:
+ do {
GNUNET_PLUGIN_unload (lib_name,
iic->plugin);
GNUNET_free (lib_name);
@@ -717,7 +740,6 @@ run (void *cls,
char *wireformat;
int fh;
- wireformat = NULL;
result = GNUNET_SYSERR;
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
NULL);
@@ -764,6 +786,56 @@ run (void *cls,
GNUNET_SCHEDULER_shutdown ();
return;
}
+
+ if (GNUNET_OK !=
+ TALER_config_get_denom (config,
+ "merchant",
+ "DEFAULT_MAX_WIRE_FEE",
+ &default_max_wire_fee))
+ {
+ char *currency;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (config,
+ "taler",
+ "CURRENCY",
+ ¤cy))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "taler",
+ "CURRENCY");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_get_zero (currency,
+ &default_max_wire_fee))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "taler",
+ "CURRENCY",
+ "Specified value not legal for a Taler currency");
+ GNUNET_SCHEDULER_shutdown ();
+ GNUNET_free (currency);
+ return;
+ }
+ GNUNET_free (currency);
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (config,
+ "merchant",
+ "DEFAULT_WIRE_FEE_AMORTIZATION",
+ &default_wire_fee_amortization))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "merchant",
+ "DEFAULT_WIRE_FEE_AMORTIZATION");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ wireformat = NULL;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (config,
"merchant",
@@ -776,10 +848,10 @@ run (void *cls,
GNUNET_SCHEDULER_shutdown ();
return;
}
-
- iterate_instances (config, wireformat);
-
+ iterate_instances (config,
+ wireformat);
GNUNET_free (wireformat);
+
if (NULL ==
(db = TALER_MERCHANTDB_plugin_load (config)))
{
@@ -882,41 +954,57 @@ run (void *cls,
un = GNUNET_new (struct sockaddr_un);
un->sun_family = AF_UNIX;
- strncpy (un->sun_path, serve_unixpath, sizeof (un->sun_path) - 1);
+ strncpy (un->sun_path,
+ serve_unixpath,
+ sizeof (un->sun_path) - 1);
GNUNET_NETWORK_unix_precheck (un);
- if (NULL == (nh = GNUNET_NETWORK_socket_create (AF_UNIX, SOCK_STREAM, 0)))
+ if (NULL == (nh = GNUNET_NETWORK_socket_create (AF_UNIX,
+ SOCK_STREAM,
+ 0)))
{
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "create(for AF_UNIX)");
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "socket(AF_UNIX)");
GNUNET_SCHEDULER_shutdown ();
return;
}
- if (GNUNET_OK != GNUNET_NETWORK_socket_bind (nh, (void *) un, sizeof (struct sockaddr_un)))
+ if (GNUNET_OK !=
+ GNUNET_NETWORK_socket_bind (nh,
+ (void *) un,
+ sizeof (struct sockaddr_un)))
{
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "bind(for AF_UNIX)");
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "bind(AF_UNIX)");
GNUNET_SCHEDULER_shutdown ();
return;
}
- if (GNUNET_OK != GNUNET_NETWORK_socket_listen (nh, UNIX_BACKLOG))
+ if (GNUNET_OK !=
+ GNUNET_NETWORK_socket_listen (nh,
+ UNIX_BACKLOG))
{
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "listen(for AF_UNIX)");
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "listen(AF_UNIX)");
GNUNET_SCHEDULER_shutdown ();
return;
}
fh = GNUNET_NETWORK_get_fd (nh);
- if (0 != chmod (serve_unixpath, unixpath_mode))
+ GNUNET_NETWORK_socket_free_memory_only_ (nh);
+ if (0 != chmod (serve_unixpath,
+ unixpath_mode))
{
- fprintf (stderr, "chmod failed: %s\n", strerror (errno));
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "chmod");
GNUNET_SCHEDULER_shutdown ();
return;
}
- GNUNET_NETWORK_socket_free_memory_only_ (nh);
port = 0;
}
else if (0 == strcmp (serve_type, "tcp"))
{
+ char *bind_to;
+
if (GNUNET_SYSERR ==
GNUNET_CONFIGURATION_get_value_number (config,
"merchant",
@@ -929,7 +1017,81 @@ run (void *cls,
GNUNET_SCHEDULER_shutdown ();
return;
}
- fh = -1;
+ if (GNUNET_OK ==
+ GNUNET_CONFIGURATION_get_value_string (config,
+ "merchant",
+ "BIND_TO",
+ &bind_to))
+ {
+ char port_str[6];
+ struct addrinfo hints;
+ struct addrinfo *res;
+ int ec;
+ struct GNUNET_NETWORK_Handle *nh;
+
+ GNUNET_snprintf (port_str,
+ sizeof (port_str),
+ "%u",
+ (uint16_t) port);
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ hints.ai_flags = AI_PASSIVE | AI_IDN;
+ if (0 !=
+ (ec = getaddrinfo (bind_to,
+ port_str,
+ &hints,
+ &res)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to resolve BIND_TO address `%s': %s\n",
+ bind_to,
+ gai_strerror (ec));
+ GNUNET_free (bind_to);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_free (bind_to);
+
+ if (NULL == (nh = GNUNET_NETWORK_socket_create (res->ai_family,
+ res->ai_socktype,
+ res->ai_protocol)))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "socket");
+ freeaddrinfo (res);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_NETWORK_socket_bind (nh,
+ res->ai_addr,
+ res->ai_addrlen))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "bind");
+ freeaddrinfo (res);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ freeaddrinfo (res);
+ if (GNUNET_OK !=
+ GNUNET_NETWORK_socket_listen (nh,
+ UNIX_BACKLOG))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "listen");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ fh = GNUNET_NETWORK_get_fd (nh);
+ GNUNET_NETWORK_socket_free_memory_only_ (nh);
+ }
+ else
+ {
+ fh = -1;
+ }
}
else
{
@@ -937,15 +1099,13 @@ run (void *cls,
GNUNET_assert (0);
}
}
- mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME,
+ mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_DUAL_STACK,
port,
NULL, NULL,
&url_handler, NULL,
MHD_OPTION_LISTEN_SOCKET, fh,
- MHD_OPTION_NOTIFY_COMPLETED,
- &handle_mhd_completion_callback, NULL,
- MHD_OPTION_CONNECTION_TIMEOUT,
- (unsigned int) 10 /* 10s */,
+ MHD_OPTION_NOTIFY_COMPLETED, &handle_mhd_completion_callback, NULL,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 10 /* 10s */,
MHD_OPTION_END);
if (NULL == mhd)
{
diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h
@@ -38,7 +38,8 @@
* Used by the iterator of the various merchant's instances given
* in configuration
*/
-struct IterateInstancesCls {
+struct IterateInstancesCls
+{
/**
* Handle for the configuration beig parsed
@@ -75,7 +76,8 @@ struct IterateInstancesCls {
* backend can account for several merchants, as used to do in donation
* shops
*/
-struct MerchantInstance {
+struct MerchantInstance
+{
/**
* Instance's mnemonic identifier. This value lives as long as
@@ -89,6 +91,14 @@ struct MerchantInstance {
*/
char *keyfile;
+ /* NOTE: the *_wire-fields should eventually be moved into a DLL
+ once we implement #4939 */
+
+ /**
+ * Which wire method is @e j_wire using?
+ */
+ const char *wire_method;
+
/**
* Wire details for this instance
*/
@@ -215,6 +225,17 @@ struct TM_HandlerContext
extern json_t *j_wire;
/**
+ * Default maximum wire fee to assume, unless stated differently in the proposal
+ * already.
+ */
+extern struct TALER_Amount default_max_wire_fee;
+
+/**
+ * Default factor for wire fee amortization.
+ */
+extern unsigned long long default_wire_fee_amortization;
+
+/**
* Hash of our wire format details as given in #j_wire.
*/
extern struct GNUNET_HashCode h_wire;
diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014, 2015, 2016 INRIA
+ (C) 2014-2017 INRIA
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
@@ -84,6 +84,11 @@ struct TMH_EXCHANGES_FindOperation
struct Exchange *my_exchange;
/**
+ * Wire method we care about for fees.
+ */
+ char *wire_method;
+
+ /**
* Task scheduled to asynchronously return the result to
* the find continuation.
*/
@@ -93,6 +98,35 @@ struct TMH_EXCHANGES_FindOperation
/**
+ * Information about wire transfer fees of an exchange, by wire method.
+ */
+struct FeesByWireMethod
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct FeesByWireMethod *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct FeesByWireMethod *prev;
+
+ /**
+ * Wire method these fees are for.
+ */
+ char *wire_method;
+
+ /**
+ * Applicable fees, NULL if unknown/error.
+ */
+ struct TALER_EXCHANGE_WireAggregateFees *af;
+
+};
+
+
+/**
* Exchange
*/
struct Exchange
@@ -129,6 +163,26 @@ struct Exchange
struct TALER_EXCHANGE_Handle *conn;
/**
+ * Active /wire request to the exchange, or NULL.
+ */
+ struct TALER_EXCHANGE_WireHandle *wire_request;
+
+ /**
+ * Task to re-run /wire after some delay.
+ */
+ struct GNUNET_SCHEDULER_Task *wire_task;
+
+ /**
+ * Head of wire fees from /wire request.
+ */
+ struct FeesByWireMethod *wire_fees_head;
+
+ /**
+ * Tail of wire fees from /wire request.
+ */
+ struct FeesByWireMethod *wire_fees_tail;
+
+ /**
* Master public key, guaranteed to be set ONLY for
* trusted exchanges.
*/
@@ -239,6 +293,279 @@ retry_exchange (void *cls)
/**
+ * Function called with information about the wire fees
+ * for each wire method. Stores the wire fees with the
+ * exchange for laster use.
+ *
+ * @param cls closure
+ * @param wire_method name of the wire method (i.e. "sepa")
+ * @param fees fee structure for this method
+ */
+static void
+process_wire_fees (void *cls,
+ const char *wire_method,
+ const struct TALER_EXCHANGE_WireAggregateFees *fees)
+{
+ struct Exchange *exchange = cls;
+ struct FeesByWireMethod *f;
+ struct TALER_EXCHANGE_WireAggregateFees *endp;
+ struct TALER_EXCHANGE_WireAggregateFees *af;
+
+ for (f = exchange->wire_fees_head; NULL != f; f = f->next)
+ if (0 == strcasecmp (wire_method,
+ f->wire_method))
+ break;
+ if (NULL == f)
+ {
+ f = GNUNET_new (struct FeesByWireMethod);
+ f->wire_method = GNUNET_strdup (wire_method);
+ GNUNET_CONTAINER_DLL_insert (exchange->wire_fees_head,
+ exchange->wire_fees_tail,
+ f);
+ }
+ endp = f->af;
+ while ( (NULL != endp) &&
+ (NULL != endp->next) )
+ endp = endp->next;
+ while ( (NULL != endp) &&
+ (fees->start_date.abs_value_us < endp->end_date.abs_value_us) )
+ fees = fees->next;
+ if ( (NULL != endp) &&
+ (fees->start_date.abs_value_us != endp->end_date.abs_value_us) )
+ {
+ /* Hole in the fee structure, not allowed! */
+ GNUNET_break_op (0);
+ return;
+ }
+ while (NULL != fees)
+ {
+ af = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees);
+ *af = *fees;
+ af->next = NULL;
+ if (NULL == endp)
+ f->af = af;
+ else
+ endp->next = af;
+ endp = af;
+ // FIXME: also preserve `fees` in backend DB (under wire method + exchange master pub!)
+ fees = fees->next;
+ }
+}
+
+
+/**
+ * Obtain applicable fees for @a exchange and @a wire_method.
+ *
+ * @param exchange the exchange to query
+ * @param now current time
+ * @param wire_method the wire method we want the fees for
+ * @return NULL if we do not have fees for this method yet
+ */
+static struct TALER_EXCHANGE_WireAggregateFees *
+get_wire_fees (struct Exchange *exchange,
+ struct GNUNET_TIME_Absolute now,
+ const char *wire_method)
+{
+ for (struct FeesByWireMethod *fbw = exchange->wire_fees_head;
+ NULL != fbw;
+ fbw = fbw->next)
+ if (0 == strcasecmp (fbw->wire_method,
+ wire_method) )
+ {
+ struct TALER_EXCHANGE_WireAggregateFees *af;
+
+ /* Advance through list up to current time */
+ while ( (NULL != (af = fbw->af)) &&
+ (now.abs_value_us >= af->end_date.abs_value_us) )
+ {
+ fbw->af = af->next;
+ GNUNET_free (af);
+ }
+ return af;
+ }
+ return NULL;
+}
+
+
+/**
+ * Check if we have any remaining pending requests for the
+ * given @a exchange, and if we have the required data, call
+ * the callback.
+ *
+ * @param exchange the exchange to check for pending find operations
+ * @return #GNUNET_YES if we need /wire data from @a exchange
+ */
+static int
+process_find_operations (struct Exchange *exchange)
+{
+ struct TMH_EXCHANGES_FindOperation *fo;
+ struct TMH_EXCHANGES_FindOperation *fn;
+ struct GNUNET_TIME_Absolute now;
+ int need_wire;
+
+ now = GNUNET_TIME_absolute_get ();
+ need_wire = GNUNET_NO;
+ for (fo = exchange->fo_head; NULL != fo; fo = fn)
+ {
+ const struct TALER_Amount *wire_fee;
+
+ fn = fo->next;
+ if (NULL != fo->wire_method)
+ {
+ struct TALER_EXCHANGE_WireAggregateFees *af;
+
+ /* Find fee structure for our wire method */
+ af = get_wire_fees (exchange,
+ now,
+ fo->wire_method);
+ if (NULL == af)
+ {
+ need_wire = GNUNET_YES;
+ continue;
+ }
+ if (af->start_date.abs_value_us > now.abs_value_us)
+ {
+ /* Disagreement on the current time */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Exchange's earliest fee is %s adhead of our time. Clock skew issue?\n",
+ GNUNET_STRINGS_relative_time_to_string (GNUNET_TIME_absolute_get_remaining (af->start_date),
+ GNUNET_YES));
+ continue;
+ }
+ /* found fee, great! */
+ wire_fee = &af->wire_fee;
+ }
+ else
+ {
+ /* no wire transfer method given, so we yield no fee */
+ wire_fee = NULL;
+ }
+ GNUNET_CONTAINER_DLL_remove (exchange->fo_head,
+ exchange->fo_tail,
+ fo);
+ fo->fc (fo->fc_cls,
+ exchange->conn,
+ wire_fee,
+ exchange->trusted);
+ GNUNET_free_non_null (fo->wire_method);
+ GNUNET_free (fo);
+ }
+ return need_wire;
+}
+
+
+/**
+ * Check if we have any remaining pending requests for the
+ * given @a exchange, and if we have the required data, call
+ * the callback. If requests without /wire data remain,
+ * retry the /wire request after some delay.
+ *
+ * @param cls a `struct Exchange` to check
+ */
+static void
+wire_task_cb (void *cls);
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * wire format inquiry request to a exchange.
+ *
+ * If the request fails to generate a valid response from the
+ * exchange, @a http_status will also be zero.
+ *
+ * @param cls closure, a `struct Exchange`
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful request;
+ * 0 if the exchange's reply is bogus (fails to follow the protocol)
+ * @param ec taler-specific error code, #TALER_EC_NONE on success
+ * @param obj the received JSON reply, if successful this should be the wire
+ * format details as provided by /wire, or NULL if the
+ * reply was not in JSON format.
+ */
+static void
+handle_wire_data (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const json_t *obj)
+{
+ struct Exchange *exchange = cls;
+
+ exchange->wire_request = NULL;
+ if (MHD_HTTP_OK != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to obtain /wire details from `%s': %d\n",
+ exchange->uri,
+ ec);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_wire_get_fees (&TALER_EXCHANGE_get_keys (exchange->conn)->master_pub,
+ obj,
+ &process_wire_fees,
+ exchange))
+ {
+ /* Report hard failure to all callbacks! */
+ struct TMH_EXCHANGES_FindOperation *fo;
+
+ GNUNET_break_op (0);
+ while (NULL != (fo = exchange->fo_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (exchange->fo_head,
+ exchange->fo_tail,
+ fo);
+ /* TODO: report more precise error, this ultimately generates
+ "exchange not supported" instead of "exchange violated
+ protocol"; we should ideally generate a reply with
+ a specific TALER_EC-code, boxing 'obj' within it. */
+ fo->fc (fo->fc_cls,
+ NULL,
+ NULL,
+ GNUNET_NO);
+ GNUNET_free_non_null (fo->wire_method);
+ GNUNET_free (fo);
+ }
+ return;
+ }
+ if (GNUNET_YES ==
+ process_find_operations (exchange))
+ {
+ /* need to run /wire again, with some delay */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Do not have sufficient wire data. Will re-request /wire in 1 minute\n");
+
+ GNUNET_assert (NULL == exchange->wire_task);
+ exchange->wire_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES,
+ &wire_task_cb,
+ exchange);
+ }
+}
+
+
+/**
+ * Check if we have any remaining pending requests for the
+ * given @a exchange, and if we have the required data, call
+ * the callback. If requests without /wire data remain,
+ * retry the /wire request after some delay.
+ *
+ * @param cls a `struct Exchange` to check
+ */
+static void
+wire_task_cb (void *cls)
+{
+ struct Exchange *exchange = cls;
+
+ exchange->wire_task = NULL;
+ if (GNUNET_YES !=
+ process_find_operations (exchange))
+ return; /* no more need */
+ GNUNET_assert (NULL == exchange->wire_request);
+ exchange->wire_request = TALER_EXCHANGE_wire (exchange->conn,
+ &handle_wire_data,
+ exchange);
+}
+
+
+/**
* Function called with information about who is auditing
* a particular exchange and what key the exchange is using.
*
@@ -257,13 +584,22 @@ keys_mgmt_cb (void *cls,
const struct TALER_EXCHANGE_Keys *keys)
{
struct Exchange *exchange = cls;
- struct TMH_EXCHANGES_FindOperation *fo;
struct GNUNET_TIME_Absolute expire;
struct GNUNET_TIME_Relative delay;
if (NULL == keys)
{
exchange->pending = GNUNET_YES;
+ if (NULL != exchange->wire_request)
+ {
+ TALER_EXCHANGE_wire_cancel (exchange->wire_request);
+ exchange->wire_request = NULL;
+ }
+ if (NULL != exchange->wire_task)
+ {
+ GNUNET_SCHEDULER_cancel (exchange->wire_task);
+ exchange->wire_task = NULL;
+ }
exchange->retry_delay = RETRY_BACKOFF (exchange->retry_delay);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to fetch /keys from `%s', retrying in %s\n",
@@ -280,21 +616,23 @@ keys_mgmt_cb (void *cls,
delay = RELOAD_DELAY;
else
delay = GNUNET_TIME_absolute_get_remaining (expire);
- exchange->retry_delay = GNUNET_TIME_UNIT_ZERO;
+ exchange->retry_delay
+ = GNUNET_TIME_UNIT_ZERO;
exchange->retry_task
= GNUNET_SCHEDULER_add_delayed (delay,
&retry_exchange,
exchange);
exchange->pending = GNUNET_NO;
- while (NULL != (fo = exchange->fo_head))
+ if (GNUNET_YES ==
+ process_find_operations (exchange))
{
- GNUNET_CONTAINER_DLL_remove (exchange->fo_head,
- exchange->fo_tail,
- fo);
- fo->fc (fo->fc_cls,
- exchange->conn,
- exchange->trusted);
- GNUNET_free (fo);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got key data, but do not have current wire data. Will request /wire now\n");
+ GNUNET_assert (NULL == exchange->wire_request);
+ GNUNET_assert (NULL == exchange->wire_task);
+ exchange->wire_request = TALER_EXCHANGE_wire (exchange->conn,
+ &handle_wire_data,
+ exchange);
}
}
@@ -310,17 +648,19 @@ return_result (void *cls)
struct TMH_EXCHANGES_FindOperation *fo = cls;
struct Exchange *exchange = fo->my_exchange;
- fo->at = NULL;
- GNUNET_CONTAINER_DLL_remove (exchange->fo_head,
- exchange->fo_tail,
- fo);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Returning result for exchange %s, trusted=%d\n",
- exchange->uri, exchange->trusted);
- fo->fc (fo->fc_cls,
- (GNUNET_SYSERR == exchange->pending) ? NULL : exchange->conn,
- exchange->trusted);
- GNUNET_free (fo);
+ if ( (GNUNET_YES ==
+ process_find_operations (exchange)) &&
+ (NULL == exchange->wire_request) &&
+ (GNUNET_NO == exchange->pending) &&
+ (NULL != exchange->wire_task) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Do not have current wire data. Will re-request /wire in 1 minute\n");
+ exchange->wire_task
+ = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES,
+ &wire_task_cb,
+ exchange);
+ }
}
@@ -330,12 +670,14 @@ return_result (void *cls)
* NULL for the exchange.
*
* @param chosen_exchange URI of the exchange we would like to talk to
+ * @param wire_method the wire method we will use with @a chosen_exchange, NULL for none
* @param fc function to call with the handles for the exchange
* @param fc_cls closure for @a fc
* @return NULL on error
*/
struct TMH_EXCHANGES_FindOperation *
TMH_EXCHANGES_find_exchange (const char *chosen_exchange,
+ const char *wire_method,
TMH_EXCHANGES_FindContinuation fc,
void *fc_cls)
{
@@ -387,11 +729,17 @@ TMH_EXCHANGES_find_exchange (const char *chosen_exchange,
fo->fc = fc;
fo->fc_cls = fc_cls;
fo->my_exchange = exchange;
+ if (NULL != wire_method)
+ fo->wire_method = GNUNET_strdup (wire_method);
GNUNET_CONTAINER_DLL_insert (exchange->fo_head,
exchange->fo_tail,
fo);
- if (GNUNET_YES != exchange->pending)
+ if ( (GNUNET_YES != exchange->pending) &&
+ ( (NULL == fo->wire_method) ||
+ (NULL != get_wire_fees (exchange,
+ GNUNET_TIME_absolute_get (),
+ fo->wire_method)) ) )
{
/* We are not currently waiting for a reply, immediately
return result */
@@ -404,9 +752,22 @@ TMH_EXCHANGES_find_exchange (const char *chosen_exchange,
if ( (NULL == exchange->conn) &&
(GNUNET_YES == exchange->pending) )
{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Do not have current key data. Will request /keys now\n");
exchange->retry_task = GNUNET_SCHEDULER_add_now (&retry_exchange,
exchange);
}
+ else if ( (GNUNET_NO == exchange->pending) &&
+ (NULL == exchange->wire_task) &&
+ (NULL == exchange->wire_request) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Do not have current wire data. Will re-request /wire now\n");
+ exchange->wire_task = GNUNET_SCHEDULER_add_now (&wire_task_cb,
+ exchange);
+ }
+
+
return fo;
}
@@ -425,10 +786,11 @@ TMH_EXCHANGES_find_exchange_cancel (struct TMH_EXCHANGES_FindOperation *fo)
{
GNUNET_SCHEDULER_cancel (fo->at);
fo->at = NULL;
- }
+ }
GNUNET_CONTAINER_DLL_remove (exchange->fo_head,
exchange->fo_tail,
fo);
+ GNUNET_free_non_null (fo->wire_method);
GNUNET_free (fo);
}
@@ -443,7 +805,7 @@ TMH_EXCHANGES_find_exchange_cancel (struct TMH_EXCHANGES_FindOperation *fo)
*/
static void
accept_exchanges (void *cls,
- const char *section)
+ const char *section)
{
const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
char *uri;
@@ -561,10 +923,26 @@ TMH_EXCHANGES_done ()
GNUNET_CONTAINER_DLL_remove (exchange_head,
exchange_tail,
exchange);
+ if (NULL != exchange->wire_request)
+ {
+ TALER_EXCHANGE_wire_cancel (exchange->wire_request);
+ exchange->wire_request = NULL;
+ }
+ if (NULL != exchange->wire_task)
+ {
+ GNUNET_SCHEDULER_cancel (exchange->wire_task);
+ exchange->wire_task = NULL;
+ }
if (NULL != exchange->conn)
+ {
TALER_EXCHANGE_disconnect (exchange->conn);
+ exchange->conn = NULL;
+ }
if (NULL != exchange->retry_task)
+ {
GNUNET_SCHEDULER_cancel (exchange->retry_task);
+ exchange->retry_task = NULL;
+ }
GNUNET_free (exchange->uri);
GNUNET_free (exchange);
}
diff --git a/src/backend/taler-merchant-httpd_exchanges.h b/src/backend/taler-merchant-httpd_exchanges.h
@@ -60,11 +60,13 @@ TMH_EXCHANGES_done (void);
*
* @param cls closure
* @param eh handle to the exchange context
+ * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
* @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
*/
typedef void
(*TMH_EXCHANGES_FindContinuation)(void *cls,
struct TALER_EXCHANGE_Handle *eh,
+ const struct TALER_Amount *wire_fee,
int exchange_trusted);
@@ -80,11 +82,13 @@ struct TMH_EXCHANGES_FindOperation;
* NULL for the exchange.
*
* @param chosen_exchange URI of the exchange we would like to talk to
+ * @param wire_method the wire method we will use with @a chosen_exchange, NULL for none
* @param fc function to call with the handles for the exchange
* @param fc_cls closure for @a fc
*/
struct TMH_EXCHANGES_FindOperation *
TMH_EXCHANGES_find_exchange (const char *chosen_exchange,
+ const char *wire_method,
TMH_EXCHANGES_FindContinuation fc,
void *fc_cls);
diff --git a/src/backend/taler-merchant-httpd_history.c b/src/backend/taler-merchant-httpd_history.c
@@ -53,7 +53,6 @@ unsigned int current = 0;
* @param refund refund deadline
* @param total_amount total amount we receive for the contract after fees
*/
-
static void
pd_cb (void *cls,
const char *order_id,
@@ -63,26 +62,37 @@ pd_cb (void *cls,
json_t *entry;
json_t *amount;
json_t *timestamp;
+ json_t *instance;
+ GNUNET_assert (-1 != json_unpack ((json_t *) proposal_data,
+ "{s:o, s:o, s:{s:o}}",
+ "amount", &amount,
+ "timestamp", ×tamp,
+ "merchant", "instance", &instance));
- GNUNET_assert (NULL != (amount = json_copy (json_object_get (proposal_data, "amount"))));
- GNUNET_assert (NULL != (timestamp = json_object_get (proposal_data, "timestamp")));
-
- if (current >= start && current < start + delta)
+ if ( (current >= start) &&
+ (current < start + delta) )
{
- GNUNET_break (NULL != (entry = json_pack ("{s:s, s:o, s:s}",
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Adding history element. Current: %u, start: %u, delta: %u\n",
+ current,
+ start,
+ delta);
+ GNUNET_break (NULL != (entry = json_pack ("{s:s, s:O, s:O, s:O}",
"order_id", order_id,
"amount", amount,
- "timestamp", json_string_value (timestamp))));
+ "timestamp", timestamp,
+ "instance", instance)));
- GNUNET_break (0 == json_array_append_new (response, entry));
-
+ GNUNET_break (0 == json_array_append_new (response,
+ entry));
}
// FIXME to zero after returned.
current++;
}
+
/**
* Manage a /history request. Query the db and returns transactions
* younger than the date given as parameter
@@ -108,7 +118,7 @@ MH_handler_history (struct TMH_RequestHandler *rh,
unsigned int ret;
unsigned long long seconds;
struct MerchantInstance *mi;
-
+
response = json_array (); /*FIXME who decrefs this?*/
str = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
@@ -118,17 +128,22 @@ MH_handler_history (struct TMH_RequestHandler *rh,
if (NULL != str)
{
if (1 != sscanf (str, "%llu", &seconds))
- return TMH_RESPONSE_reply_arg_invalid (connection,
- TALER_EC_PARAMETER_MALFORMED,
- "date");
+ {
+ json_decref (response);
+ return TMH_RESPONSE_reply_arg_invalid (connection,
+ TALER_EC_PARAMETER_MALFORMED,
+ "date");
+ }
}
date.abs_value_us = seconds * 1000LL * 1000LL;
if (date.abs_value_us / 1000LL / 1000LL != seconds)
+ {
+ json_decref (response);
return TMH_RESPONSE_reply_bad_request (connection,
TALER_EC_HISTORY_TIMESTAMP_OVERFLOW,
"Timestamp overflowed");
-
+ }
mi = TMH_lookup_instance ("default");
@@ -139,13 +154,15 @@ MH_handler_history (struct TMH_RequestHandler *rh,
mi = TMH_lookup_instance (str);
if (NULL == mi)
+ {
+ json_decref (response);
return TMH_RESPONSE_reply_not_found (connection,
TALER_EC_HISTORY_INSTANCE_UNKNOWN,
"instance");
-
+ }
start = 0;
delta = 20;
-
+
str = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"start");
@@ -153,9 +170,12 @@ MH_handler_history (struct TMH_RequestHandler *rh,
{
if ((1 != sscanf (str, "%d", &start)) ||
start < 0)
+ {
+ json_decref (response);
return TMH_RESPONSE_reply_arg_invalid (connection,
TALER_EC_PARAMETER_MALFORMED,
- "start");
+ "start");
+ }
}
str = MHD_lookup_connection_value (connection,
@@ -168,11 +188,11 @@ MH_handler_history (struct TMH_RequestHandler *rh,
delta < 0)
return TMH_RESPONSE_reply_arg_invalid (connection,
TALER_EC_PARAMETER_MALFORMED,
- "delta");
+ "delta");
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Querying history back to %llu\n",
- date.abs_value_us);
+ "Querying history back to %s\n",
+ GNUNET_STRINGS_absolute_time_to_string (date));
ret = db->find_proposal_data_by_date (db->cls,
date,
@@ -181,17 +201,21 @@ MH_handler_history (struct TMH_RequestHandler *rh,
response);
current = 0;
if (GNUNET_SYSERR == ret)
+ {
+ json_decref (response);
return TMH_RESPONSE_reply_internal_error (connection,
TALER_EC_HISTORY_DB_FETCH_ERROR,
"db error to get history");
-
+ }
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"history data: %s\n",
json_dumps (response, JSON_INDENT (1)));
- return TMH_RESPONSE_reply_json (connection,
- response,
- MHD_HTTP_OK);
+ ret = TMH_RESPONSE_reply_json (connection,
+ response,
+ MHD_HTTP_OK);
+ json_decref (response);
+ return ret;
}
/* end of taler-merchant-httpd_history.c */
diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014, 2015, 2016 GNUnet e.V. and INRIA
+ (C) 2014-2017 GNUnet e.V. and INRIA
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
@@ -165,18 +165,46 @@ struct PayContext
struct TALER_Amount max_fee;
/**
+ * Maximum wire fee the merchant is willing to pay, from @e root.
+ * Note that IF the total fee of the exchange is higher, that is
+ * acceptable to the merchant if the customer is willing to
+ * pay the amorized difference. Wire fees are charged over an
+ * aggregate of several translations, hence unlike the deposit
+ * fees, they are amortized over several customer's transactions.
+ * The contract specifies under @e wire_fee_amortization how many
+ * customer's transactions he expects the wire fees to be amortized
+ * over on average. Thus, if the wire fees are larger than
+ * @e max_wire_fee, each customer is expected to contribute
+ * $\frac{actual-wire-fee - max_wire_fee}{wire_fee_amortization}$.
+ * The customer's contribution may be further reduced by the
+ * difference between @e max_fee and the sum of the deposit fees.
+ *
+ * Default is that the merchant is unwilling to pay any wire fees.
+ */
+ struct TALER_Amount max_wire_fee;
+
+ /**
+ * Number of transactions that the wire fees are expected to be
+ * amortized over. Never zero, defaults (conservateively) to 1.
+ * May be higher if merchants expect many small transactions to
+ * be aggregated and thus wire fees to be reasonably amortized
+ * due to aggregation.
+ */
+ uint32_t wire_fee_amortization;
+
+ /**
* Amount from @e root. This is the amount the merchant expects
* to make, minus @e max_fee.
*/
struct TALER_Amount amount;
/**
- * Timestamp from @e root.
+ * Timestamp from @e proposal_data.
*/
struct GNUNET_TIME_Absolute timestamp;
/**
- * Refund deadline from @e root.
+ * Refund deadline from @e proposal_data.
*/
struct GNUNET_TIME_Absolute refund_deadline;
@@ -186,11 +214,17 @@ struct PayContext
struct GNUNET_TIME_Absolute pay_deadline;
/**
- * "H_contract" from @e root.
+ * "H_contract" from @e proposal_data.
*/
struct GNUNET_HashCode h_proposal_data;
/**
+ * "H_wire" from @e proposal_data. Used to identify the instance's
+ * wire transfer method.
+ */
+ struct GNUNET_HashCode h_wire;
+
+ /**
* Wire transfer deadline. How soon would the merchant like the
* wire transfer to be executed? (Can be given by the frontend
* or be determined by our configuration via #wire_transfer_delay.)
@@ -281,28 +315,6 @@ resume_pay_with_response (struct PayContext *pc,
/**
- * Convert denomination key to its base32 representation
- *
- * @param dk denomination key to convert
- * @return 0-terminated base32 encoding of @a dk, to be deallocated
- */
-static char *
-denomination_to_string_alloc (struct TALER_DenominationPublicKey *dk)
-{
- char *buf;
- char *buf2;
- size_t buf_size;
-
- buf_size = GNUNET_CRYPTO_rsa_public_key_encode (dk->rsa_public_key,
- &buf);
- buf2 = GNUNET_STRINGS_data_to_string_alloc (buf,
- buf_size);
- GNUNET_free (buf);
- return buf2;
-}
-
-
-/**
* Abort all pending /deposit operations.
*
* @param pc pay context to abort
@@ -347,10 +359,11 @@ sign_success_response (struct PayContext *pc)
&mr.purpose,
&sig);
- return TMH_RESPONSE_make_json_pack ("{s:O, s:s, s:o}",
- "proposal_data", pc->proposal_data,
+ return TMH_RESPONSE_make_json_pack ("{s:O, s:o, s:o}",
+ "proposal_data",
+ pc->proposal_data,
"sig",
- json_string_value (GNUNET_JSON_from_data_auto (&sig)),
+ GNUNET_JSON_from_data_auto (&sig),
"h_proposal_data",
GNUNET_JSON_from_data (&pc->h_proposal_data,
sizeof (struct GNUNET_HashCode)));
@@ -451,9 +464,9 @@ deposit_cb (void *cls,
if (0 != pc->pending)
return; /* still more to do */
-
-
- resume_pay_with_response (pc, MHD_HTTP_OK, sign_success_response (pc));
+ resume_pay_with_response (pc,
+ MHD_HTTP_OK,
+ sign_success_response (pc));
}
@@ -525,16 +538,20 @@ pay_context_cleanup (struct TM_HandlerContext *hc)
*
* @param cls the `struct PayContext`
* @param mh NULL if exchange was not found to be acceptable
+ * @param wire_fee current applicable fee for dealing with @a mh, NULL if not available
* @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
*/
static void
process_pay_with_exchange (void *cls,
struct TALER_EXCHANGE_Handle *mh,
+ const struct TALER_Amount *wire_fee,
int exchange_trusted)
{
struct PayContext *pc = cls;
struct TALER_Amount acc_fee;
struct TALER_Amount acc_amount;
+ struct TALER_Amount wire_fee_delta;
+ struct TALER_Amount wire_fee_customer_contribution;
const struct TALER_EXCHANGE_Keys *keys;
unsigned int i;
@@ -551,7 +568,6 @@ process_pay_with_exchange (void *cls,
return;
}
pc->mh = mh;
-
keys = TALER_EXCHANGE_get_keys (mh);
if (NULL == keys)
{
@@ -573,8 +589,6 @@ process_pay_with_exchange (void *cls,
&dc->denom);
if (NULL == denom_details)
{
- char *denom_enc;
-
GNUNET_break_op (0);
resume_pay_with_response (pc,
MHD_HTTP_BAD_REQUEST,
@@ -583,11 +597,6 @@ process_pay_with_exchange (void *cls,
"code", TALER_EC_PAY_DENOMINATION_KEY_NOT_FOUND,
"denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key),
"exchange_keys", TALER_EXCHANGE_get_keys_raw (mh)));
- denom_enc = denomination_to_string_alloc (&dc->denom);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "unknown denom to exchange: %s\n",
- denom_enc);
- GNUNET_free (denom_enc);
return;
}
if (GNUNET_OK !=
@@ -595,8 +604,6 @@ process_pay_with_exchange (void *cls,
denom_details,
exchange_trusted))
{
- char *denom_enc;
-
GNUNET_break_op (0);
resume_pay_with_response (pc,
MHD_HTTP_BAD_REQUEST,
@@ -604,11 +611,6 @@ process_pay_with_exchange (void *cls,
"error", "invalid denomination",
"code", (json_int_t) TALER_EC_PAY_DENOMINATION_KEY_AUDITOR_FAILURE,
"denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key)));
- denom_enc = denomination_to_string_alloc (&dc->denom);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Client offered invalid denomination: %s\n",
- denom_enc);
- GNUNET_free (denom_enc);
return;
}
dc->deposit_fee = denom_details->fee_deposit;
@@ -654,6 +656,38 @@ process_pay_with_exchange (void *cls,
}
}
+ /* Now compare exchange wire fee compared to what we are willing to pay */
+ if (GNUNET_YES !=
+ TALER_amount_cmp_currency (wire_fee,
+ &pc->max_wire_fee))
+ {
+ GNUNET_break (0);
+ resume_pay_with_response (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TMH_RESPONSE_make_internal_error (TALER_EC_PAY_WIRE_FEE_CURRENCY_MISSMATCH,
+ "wire_fee"));
+ return;
+ }
+
+ if (GNUNET_OK ==
+ TALER_amount_subtract (&wire_fee_delta,
+ wire_fee,
+ &pc->max_wire_fee))
+ {
+ /* Actual wire fee is indeed higher than our maximum, compute
+ how much the customer is expected to cover! */
+ TALER_amount_divide (&wire_fee_customer_contribution,
+ &wire_fee_delta,
+ pc->wire_fee_amortization);
+ }
+ else
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (wire_fee->currency,
+ &wire_fee_customer_contribution));
+
+ }
+
/* Now check that the customer paid enough for the full contract */
if (-1 == TALER_amount_cmp (&pc->max_fee,
&acc_fee))
@@ -680,6 +714,12 @@ process_pay_with_exchange (void *cls,
"overflow"));
return;
}
+ /* add wire fee contribution to the total */
+ if (GNUNET_OK ==
+ TALER_amount_add (&total_needed,
+ &total_needed,
+ &wire_fee_customer_contribution))
+
/* check if total payment sufficies */
if (-1 == TALER_amount_cmp (&acc_amount,
&total_needed))
@@ -694,7 +734,40 @@ process_pay_with_exchange (void *cls,
}
else
{
- /* fees are acceptable, we cover them all; let's check the amount */
+ struct TALER_Amount deposit_fee_savings;
+
+ /* Compute how much the customer saved by not going to the
+ limit on the deposit fees, as this amount is counted against
+ what we expect him to cover for the wire fees */
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_amount_subtract (&deposit_fee_savings,
+ &pc->max_fee,
+ &acc_fee));
+ /* See how much of wire fee contribution is covered by fee_savings */
+ if (-1 == TALER_amount_cmp (&deposit_fee_savings,
+ &wire_fee_customer_contribution))
+ {
+ /* wire_fee_customer_contribution > deposit_fee_savings */
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_amount_subtract (&wire_fee_customer_contribution,
+ &wire_fee_customer_contribution,
+ &deposit_fee_savings));
+ /* subtract remaining wire fees from total contribution */
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&acc_amount,
+ &acc_amount,
+ &wire_fee_customer_contribution))
+ {
+ GNUNET_break_op (0);
+ resume_pay_with_response (pc,
+ MHD_HTTP_METHOD_NOT_ACCEPTABLE,
+ TMH_RESPONSE_make_external_error (TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES,
+ "insufficient funds (including excessive exchange fees to be covered by customer)"));
+ return;
+ }
+ }
+
+ /* fees are acceptable, merchant covers them all; let's check the amount */
if (-1 == TALER_amount_cmp (&acc_amount,
&pc->amount))
{
@@ -710,8 +783,6 @@ process_pay_with_exchange (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Exchange and fee structure OK. Initiating deposit operation for coins\n");
-
-
/* Initiate /deposit operation for all coins */
for (i=0;i<pc->coins_cnt;i++)
{
@@ -759,24 +830,21 @@ process_pay_with_exchange (void *cls,
/**
* Handle a timeout for the processing of the pay request.
*
- * @param cls closure
+ * @param cls our `struct PayContext`
*/
static void
handle_pay_timeout (void *cls)
{
struct PayContext *pc = cls;
+ pc->timeout_task = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Resuming /pay with error after timeout\n");
-
- pc->timeout_task = NULL;
-
if (NULL != pc->fo)
{
TMH_EXCHANGES_find_exchange_cancel (pc->fo);
pc->fo = NULL;
}
-
resume_pay_with_response (pc,
MHD_HTTP_SERVICE_UNAVAILABLE,
TMH_RESPONSE_make_internal_error (TALER_EC_PAY_EXCHANGE_TIMEOUT,
@@ -873,48 +941,29 @@ check_transaction_exists (void *cls,
}
}
+
+// FIXME: declare in proper header!
extern struct MerchantInstance *
get_instance (struct json_t *json);
/**
- * Just a stub used to double-check if a transaction
- * has been correctly inserted into db.
- *
- * @param cls closure
- * @param transaction_id of the contract
- * @param merchant's public key
- * @param exchange_uri URI of the exchange
- * @param h_contract hash of the contract
- * @param h_wire hash of our wire details
- * @param timestamp time of the confirmation
- * @param refund refund deadline
- * @param total_amount total amount we receive for the contract after fees
- */
-static void
-transaction_double_check (void *cls,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const char *exchange_uri,
- const struct GNUNET_HashCode *h_proposal_data,
- const struct GNUNET_HashCode *h_wire,
- struct GNUNET_TIME_Absolute timestamp,
- struct GNUNET_TIME_Absolute refund,
- const struct TALER_Amount *total_amount)
-{
- return;
-}
-
-
-
-/**
* Try to parse the pay request into the given pay context.
*
* Schedules an error response in the connection on failure.
*
- * @return #GNUNET_YES on success
+ *
+ * @param connection HTTP connection we are receiving payment on
+ * @param root JSON upload with payment data
+ * @param pc context we use to handle the payment
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO on failure (response was queued with MHD)
+ * #GNUNET_SYSERR on hard error (MHD connection must be dropped)
*/
static int
-parse_pay (struct MHD_Connection *connection, json_t *root, struct PayContext *pc)
+parse_pay (struct MHD_Connection *connection,
+ const json_t *root,
+ struct PayContext *pc)
{
json_t *coins;
json_t *coin;
@@ -940,19 +989,17 @@ parse_pay (struct MHD_Connection *connection, json_t *root, struct PayContext *p
GNUNET_break (0);
return res;
}
-
res = db->find_proposal_data (db->cls,
&pc->proposal_data,
order_id,
&merchant_pub);
-
-
if (GNUNET_OK != res)
{
-
- if (MHD_YES != TMH_RESPONSE_reply_not_found (connection,
- TALER_EC_PAY_DB_STORE_PAY_ERROR,
- "Proposal not found"))
+ GNUNET_JSON_parse_free (spec);
+ if (MHD_YES !=
+ TMH_RESPONSE_reply_not_found (connection,
+ TALER_EC_PAY_DB_STORE_PAY_ERROR,
+ "Proposal not found"))
{
GNUNET_break (0);
return GNUNET_SYSERR;
@@ -960,12 +1007,15 @@ parse_pay (struct MHD_Connection *connection, json_t *root, struct PayContext *p
return GNUNET_NO;
}
-
- if (GNUNET_OK != TALER_JSON_hash (pc->proposal_data, &pc->h_proposal_data))
+ if (GNUNET_OK !=
+ TALER_JSON_hash (pc->proposal_data,
+ &pc->h_proposal_data))
{
- if (MHD_YES != TMH_RESPONSE_reply_internal_error (connection,
- TALER_EC_NONE,
- "Can not hash proposal"))
+ GNUNET_JSON_parse_free (spec);
+ if (MHD_YES !=
+ TMH_RESPONSE_reply_internal_error (connection,
+ TALER_EC_PAY_FAILED_COMPUTE_PROPOSAL_HASH,
+ "Failed to hash proposal"))
{
GNUNET_break (0);
return GNUNET_SYSERR;
@@ -973,15 +1023,17 @@ parse_pay (struct MHD_Connection *connection, json_t *root, struct PayContext *p
return GNUNET_NO;
}
-
- merchant = json_object_get (pc->proposal_data, "merchant");
+ merchant = json_object_get (pc->proposal_data,
+ "merchant");
if (NULL == merchant)
{
- // invalid contract:
+ /* invalid contract */
GNUNET_break (0);
- if (MHD_YES != TMH_RESPONSE_reply_internal_error (connection,
- TALER_EC_NONE,
- "No merchant field in contract"))
+ GNUNET_JSON_parse_free (spec);
+ if (MHD_YES !=
+ TMH_RESPONSE_reply_internal_error (connection,
+ TALER_EC_PAY_MERCHANT_FIELD_MISSING,
+ "No merchant field in contract"))
{
GNUNET_break (0);
return GNUNET_SYSERR;
@@ -989,11 +1041,11 @@ parse_pay (struct MHD_Connection *connection, json_t *root, struct PayContext *p
return GNUNET_NO;
}
pc->mi = get_instance (merchant);
-
if (NULL == pc->mi)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Not able to find the specified instance\n");
+ "Unable to find the specified instance\n");
+ GNUNET_JSON_parse_free (spec);
if (MHD_NO == TMH_RESPONSE_reply_not_found (connection,
TALER_EC_PAY_INSTANCE_UNKNOWN,
"Unknown instance given"))
@@ -1007,14 +1059,10 @@ parse_pay (struct MHD_Connection *connection, json_t *root, struct PayContext *p
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"/pay: picked instance %s with key %s\n",
pc->mi->id,
- GNUNET_STRINGS_data_to_string_alloc (&pc->mi->pubkey, sizeof (pc->mi->pubkey)));
+ GNUNET_STRINGS_data_to_string_alloc (&pc->mi->pubkey,
+ sizeof (pc->mi->pubkey)));
pc->chosen_exchange = GNUNET_strdup (chosen_exchange);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Parsed JSON for /pay.\n");
-
-
-
{
struct GNUNET_JSON_Specification espec[] = {
GNUNET_JSON_spec_absolute_time ("refund_deadline",
@@ -1027,6 +1075,8 @@ parse_pay (struct MHD_Connection *connection, json_t *root, struct PayContext *p
&pc->max_fee),
TALER_JSON_spec_amount ("amount",
&pc->amount),
+ GNUNET_JSON_spec_fixed_auto ("H_wire",
+ &pc->h_wire),
GNUNET_JSON_spec_end()
};
@@ -1040,7 +1090,9 @@ parse_pay (struct MHD_Connection *connection, json_t *root, struct PayContext *p
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
- pc->wire_transfer_deadline = GNUNET_TIME_absolute_add (pc->timestamp, wire_transfer_delay);
+ pc->wire_transfer_deadline
+ = GNUNET_TIME_absolute_add (pc->timestamp,
+ wire_transfer_delay);
if (pc->wire_transfer_deadline.abs_value_us < pc->refund_deadline.abs_value_us)
{
@@ -1052,8 +1104,72 @@ parse_pay (struct MHD_Connection *connection, json_t *root, struct PayContext *p
}
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO, "parsed timestamps\n");
+ /* NOTE: In the future, iterate over all wire hashes
+ available to a given instance here! (#4939) */
+ if (0 != memcmp (&pc->h_wire,
+ &pc->mi->h_wire,
+ sizeof (struct GNUNET_HashCode)))
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TMH_RESPONSE_reply_internal_error (connection,
+ TALER_EC_PAY_WIRE_HASH_UNKNOWN,
+ "Did not find matching wire details");
+ }
+
+ /* parse optional details */
+ if (NULL != json_object_get (pc->proposal_data,
+ "max_wire_fee"))
+ {
+ struct GNUNET_JSON_Specification espec[] = {
+ TALER_JSON_spec_amount ("max_wire_fee",
+ &pc->max_wire_fee),
+ GNUNET_JSON_spec_end()
+ };
+
+ res = TMH_PARSE_json_data (connection,
+ pc->proposal_data,
+ espec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0); /* invalid input, use default */
+ /* default is we cover no fee */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (pc->max_fee.currency,
+ &pc->max_wire_fee));
+ }
+ }
+ else
+ {
+ /* default is we cover no fee */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (pc->max_fee.currency,
+ &pc->max_wire_fee));
+ }
+ if (NULL != json_object_get (pc->proposal_data,
+ "wire_fee_amortization"))
+ {
+ struct GNUNET_JSON_Specification espec[] = {
+ GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
+ &pc->wire_fee_amortization),
+ GNUNET_JSON_spec_end()
+ };
+ res = TMH_PARSE_json_data (connection,
+ pc->proposal_data,
+ espec);
+ if ( (GNUNET_YES != res) ||
+ (0 == pc->wire_fee_amortization) )
+ {
+ GNUNET_break_op (0); /* invalid input, use default */
+ /* default is no amortization */
+ pc->wire_fee_amortization = 1;
+ }
+ }
+ else
+ {
+ pc->wire_fee_amortization = 1;
+ }
pc->coins_cnt = json_array_size (coins);
if (0 == pc->coins_cnt)
@@ -1086,53 +1202,40 @@ parse_pay (struct MHD_Connection *connection, json_t *root, struct PayContext *p
if (GNUNET_YES != res)
{
GNUNET_JSON_parse_free (spec);
- json_decref (root);
GNUNET_break (0);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
- {
- char *s;
-
- s = TALER_amount_to_string (&dc->amount_with_fee);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Coin #%i has f %s\n",
- coins_index,
- s);
- GNUNET_free (s);
- }
-
dc->index = coins_index;
dc->pc = pc;
}
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO, "parsed coins\n");
-
pc->pending = pc->coins_cnt;
-
GNUNET_JSON_parse_free (spec);
-
return GNUNET_OK;
}
/**
* Process a payment for a proposal.
+ *
+ * @param connection HTTP connection we are receiving payment on
+ * @param root JSON upload with payment data
+ * @param pc context we use to handle the payment
+ * @return value to return to MHD (#MHD_NO to drop connection,
+ * #MHD_YES to keep handling it)
*/
static int
handler_pay_json (struct MHD_Connection *connection,
- json_t *root,
+ const json_t *root,
struct PayContext *pc)
{
int ret;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO, "about to parse '/pay' body\n");
-
- ret = parse_pay (connection, root, pc);
+ ret = parse_pay (connection,
+ root,
+ pc);
if (GNUNET_OK != ret)
- return ret;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO, "parsed '/pay' body\n");
+ return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
/* Check if this payment attempt has already succeeded */
if (GNUNET_SYSERR ==
@@ -1224,18 +1327,13 @@ handler_pay_json (struct MHD_Connection *connection,
TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
"Merchant database error");
}
- if (GNUNET_OK != db->find_transaction (db->cls,
- &pc->h_proposal_data,
- &pc->mi->pubkey,
- &transaction_double_check,
- NULL))
- GNUNET_break (0);
}
MHD_suspend_connection (connection);
/* Find the responsible exchange, this may take a while... */
pc->fo = TMH_EXCHANGES_find_exchange (pc->chosen_exchange,
+ pc->mi->wire_method,
&process_pay_with_exchange,
pc);
@@ -1248,7 +1346,7 @@ handler_pay_json (struct MHD_Connection *connection,
pc->timeout_task = GNUNET_SCHEDULER_add_delayed (PAY_TIMEOUT,
&handle_pay_timeout,
pc);
- return GNUNET_OK;
+ return MHD_YES;
}
@@ -1301,11 +1399,8 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
res = MHD_queue_response (connection,
pc->response_code,
pc->response);
- if (NULL != pc->response)
- {
- MHD_destroy_response (pc->response);
- pc->response = NULL;
- }
+ MHD_destroy_response (pc->response);
+ pc->response = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Queueing response (%u) for /pay (%s).\n",
(unsigned int) pc->response_code,
@@ -1329,10 +1424,13 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
GNUNET_break (0);
return TMH_RESPONSE_reply_invalid_json (connection);
}
- if ((GNUNET_NO == res) || (NULL == root))
+ if ( (GNUNET_NO == res) ||
+ (NULL == root) )
return MHD_YES; /* the POST's body has to be further fetched */
- res = handler_pay_json (connection, root, pc);
+ res = handler_pay_json (connection,
+ root,
+ pc);
json_decref (root);
if (GNUNET_SYSERR == res)
return MHD_NO;
diff --git a/src/backend/taler-merchant-httpd_proposal.c b/src/backend/taler-merchant-httpd_proposal.c
@@ -127,8 +127,9 @@ get_instance (struct json_t *json);
* @param order to process
* @return MHD result code
*/
-int
-proposal_put (struct MHD_Connection *connection, json_t *order)
+static int
+proposal_put (struct MHD_Connection *connection,
+ json_t *order)
{
int res;
struct MerchantInstance *mi;
@@ -169,35 +170,67 @@ proposal_put (struct MHD_Connection *connection, json_t *order)
time (&timer);
tm_info = localtime (&timer);
- off = strftime (buf, sizeof (buf), "%H:%M:%S", tm_info);
+ off = strftime (buf,
+ sizeof (buf),
+ "%H:%M:%S",
+ tm_info);
snprintf (buf + off, sizeof (buf) - off,
- "-%llX",
- (long long unsigned) GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX));
- json_object_set (order, "order_id", json_string (buf));
+ "-%llX",
+ (long long unsigned) GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
+ UINT64_MAX));
+ json_object_set (order,
+ "order_id",
+ json_string (buf));
}
- if (NULL == json_string_value (json_object_get (order, "timestamp")))
+ if (NULL == json_object_get (order,
+ "timestamp"))
{
struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+
(void) GNUNET_TIME_round_abs (&now);
- json_object_set (order, "timestamp", GNUNET_JSON_from_time_abs (now));
+ json_object_set (order,
+ "timestamp",
+ GNUNET_JSON_from_time_abs (now));
}
- if (NULL == json_string_value (json_object_get (order, "refund_deadline")))
+ if (NULL == json_object_get (order,
+ "refund_deadline"))
{
struct GNUNET_TIME_Absolute zero = { 0 };
- json_object_set (order, "refund_deadline", GNUNET_JSON_from_time_abs (zero));
+
+ json_object_set (order,
+ "refund_deadline",
+ GNUNET_JSON_from_time_abs (zero));
}
- if (NULL == json_string_value (json_object_get (order, "pay_deadline")))
+ if (NULL == json_object_get (order,
+ "pay_deadline"))
{
struct GNUNET_TIME_Absolute t;
+
/* FIXME: read the delay for pay_deadline from config */
- t = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), GNUNET_TIME_UNIT_HOURS);
+ t = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
(void) GNUNET_TIME_round_abs (&t);
json_object_set (order, "pay_deadline", GNUNET_JSON_from_time_abs (t));
}
+ if (NULL == json_object_get (order,
+ "max_wire_fee"))
+ {
+ json_object_set (order,
+ "max_wire_fee",
+ TALER_JSON_from_amount (&default_max_wire_fee));
+ }
+
+ if (NULL == json_object_get (order,
+ "wire_fee_amortization"))
+ {
+ json_object_set (order,
+ "wire_fee_amortization",
+ json_integer ((json_int_t) default_wire_fee_amortization));
+ }
+
/* extract fields we need to sign separately */
res = TMH_PARSE_json_data (connection, order, spec);
if (GNUNET_NO == res)
@@ -210,7 +243,7 @@ proposal_put (struct MHD_Connection *connection, json_t *order)
TALER_EC_NONE,
"Impossible to parse the order");
}
-
+
/* check contract is well-formed */
if (GNUNET_OK != check_products (products))
@@ -225,7 +258,7 @@ proposal_put (struct MHD_Connection *connection, json_t *order)
if (NULL == mi)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Not able to find the specified instance\n");
+ "Not able to find the specified instance\n");
return TMH_RESPONSE_reply_not_found (connection,
TALER_EC_CONTRACT_INSTANCE_UNKNOWN,
"Unknown instance given");
@@ -272,7 +305,7 @@ proposal_put (struct MHD_Connection *connection, json_t *order)
TALER_EC_PROPOSAL_STORE_DB_ERROR,
"db error: could not store this proposal's data into db");
}
-
+
res = TMH_RESPONSE_reply_json_pack (connection,
MHD_HTTP_OK,
@@ -347,6 +380,7 @@ MH_handler_proposal_put (struct TMH_RequestHandler *rh,
return res;
}
+
/**
* Manage a GET /proposal request. Query the db and returns the
* proposal's data related to the transaction id given as the URL's
@@ -396,7 +430,7 @@ MH_handler_proposal_lookup (struct TMH_RequestHandler *rh,
order_id,
&mi->pubkey);
if (GNUNET_NO == res)
- return TMH_RESPONSE_reply_not_found (connection,
+ return TMH_RESPONSE_reply_not_found (connection,
TALER_EC_PROPOSAL_LOOKUP_NOT_FOUND,
"unknown transaction id");
@@ -405,10 +439,10 @@ MH_handler_proposal_lookup (struct TMH_RequestHandler *rh,
TALER_EC_PROPOSAL_LOOKUP_DB_ERROR,
"An error occurred while retrieving proposal data from db");
-
+
return TMH_RESPONSE_reply_json (connection,
proposal_data,
- MHD_HTTP_OK);
+ MHD_HTTP_OK);
}
diff --git a/src/backend/taler-merchant-httpd_track-transaction.c b/src/backend/taler-merchant-httpd_track-transaction.c
@@ -340,6 +340,7 @@ trace_coins (struct TrackTransactionContext *tctx);
* not provide any (set only if @a http_status is #MHD_HTTP_OK)
* @param total_amount total amount of the wire transfer, or NULL if the exchange could
* not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK)
+ * @param wire_fee wire fee that was charged by the exchange
* @param details_length length of the @a details array
* @param details array with details about the combined transactions
*/
@@ -352,16 +353,17 @@ wire_deposits_cb (void *cls,
const struct GNUNET_HashCode *h_wire,
struct GNUNET_TIME_Absolute execution_time,
const struct TALER_Amount *total_amount,
+ const struct TALER_Amount *wire_fee,
unsigned int details_length,
const struct TALER_TrackTransferDetails *details)
- {
- struct TrackTransactionContext *tctx = cls;
- struct TrackCoinContext *tcc;
- unsigned int i;
+{
+ struct TrackTransactionContext *tctx = cls;
+ struct TrackCoinContext *tcc;
+ unsigned int i;
- tctx->wdh = NULL;
- if (MHD_HTTP_OK != http_status)
- {
+ tctx->wdh = NULL;
+ if (MHD_HTTP_OK != http_status)
+ {
resume_track_transaction_with_response
(tctx,
MHD_HTTP_FAILED_DEPENDENCY,
@@ -509,7 +511,7 @@ wtid_cb (void *cls,
/* WARNING: if two transactions got aggregated under the same
WTID, then this branch is always taken (when attempting to
track the second transaction). */
- if (GNUNET_OK ==
+ if (GNUNET_OK ==
db->find_proof_by_wtid (db->cls,
tctx->exchange_uri,
wtid,
@@ -677,11 +679,13 @@ trace_coins (struct TrackTransactionContext *tctx)
*
* @param cls the `struct TrackTransactionContext`
* @param eh NULL if exchange was not found to be acceptable
+ * @param wire_fee NULL (we did not specify a wire method)
* @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
*/
static void
process_track_transaction_with_exchange (void *cls,
struct TALER_EXCHANGE_Handle *eh,
+ const struct TALER_Amount *wire_fee,
int exchange_trusted)
{
struct TrackTransactionContext *tctx = cls;
@@ -985,6 +989,7 @@ MH_handler_track_transaction (struct TMH_RequestHandler *rh,
"Suspending /track/transaction handling while working with the exchange\n");
MHD_suspend_connection (connection);
tctx->fo = TMH_EXCHANGES_find_exchange (tctx->exchange_uri,
+ NULL,
&process_track_transaction_with_exchange,
tctx);
diff --git a/src/backend/taler-merchant-httpd_track-transfer.c b/src/backend/taler-merchant-httpd_track-transfer.c
@@ -267,6 +267,7 @@ check_transfer (void *cls,
* @param execution_time time when the exchange claims to have performed the wire transfer
* @param total_amount total amount of the wire transfer, or NULL if the exchange could
* not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK)
+ * @param wire_fee wire fee that was charged by the exchange
* @param details_length length of the @a details array
* @param details array with details about the combined transactions
*/
@@ -279,6 +280,7 @@ wire_transfer_cb (void *cls,
const struct GNUNET_HashCode *h_wire,
struct GNUNET_TIME_Absolute execution_time,
const struct TALER_Amount *total_amount,
+ const struct TALER_Amount *wire_fee,
unsigned int details_length,
const struct TALER_TrackTransferDetails *details)
{
@@ -413,11 +415,13 @@ wire_transfer_cb (void *cls,
*
* @param cls the `struct TrackTransferContext`
* @param eh NULL if exchange was not found to be acceptable
+ * @param wire_fee NULL (we did not specify a wire method)
* @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
*/
static void
process_track_transfer_with_exchange (void *cls,
struct TALER_EXCHANGE_Handle *eh,
+ const struct TALER_Amount *wire_fee,
int exchange_trusted)
{
struct TrackTransferContext *rctx = cls;
@@ -621,6 +625,7 @@ MH_handler_track_transfer (struct TMH_RequestHandler *rh,
"Suspending /track/transfer handling while working with the exchange\n");
MHD_suspend_connection (connection);
rctx->fo = TMH_EXCHANGES_find_exchange (uri,
+ NULL,
&process_track_transfer_with_exchange,
rctx);
rctx->timeout_task
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
@@ -1428,7 +1428,7 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
pg->conn = GNUNET_POSTGRES_connect (cfg, "merchantdb-postgres");
if (NULL == pg->conn)
{
- GNUNET_break (0);
+ GNUNET_free (pg);
return NULL;
}
plugin = GNUNET_new (struct TALER_MERCHANTDB_Plugin);
diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c
@@ -565,6 +565,11 @@ struct Command
char *expected_transfer_ref;
/**
+ * Wire fee we expect to pay for this transaction.
+ */
+ const char *wire_fee;
+
+ /**
* Handle to a /track/transaction operation
*/
struct TALER_MERCHANT_TrackTransactionHandle *tth;
@@ -1315,7 +1320,7 @@ proposal_lookup_cb (void *cls,
{
struct InterpreterState *is = cls;
struct Command *cmd = &is->commands[is->ip];
-
+
cmd->details.proposal_lookup.plo = NULL;
if (cmd->expected_response_code != http_status)
@@ -1363,6 +1368,7 @@ track_transaction_cb (void *cls,
{
const struct Command *ref;
struct TALER_Amount ea;
+ struct TALER_Amount wire_fee;
struct TALER_Amount coin_contribution;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -1401,9 +1407,16 @@ track_transaction_cb (void *cls,
TALER_string_to_amount (ref->details.check_bank_transfer.amount,
&ea));
GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (cmd->details.track_transaction.wire_fee,
+ &wire_fee));
+ GNUNET_assert (GNUNET_OK ==
TALER_amount_subtract (&coin_contribution,
&transfers[0].coins[0].amount_with_fee,
&transfers[0].coins[0].deposit_fee));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_subtract (&coin_contribution,
+ &coin_contribution,
+ &wire_fee));
if (0 !=
TALER_amount_cmp (&ea,
&coin_contribution))
@@ -1524,7 +1537,7 @@ interpreter_run (void *cls)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Switching instance: '%s'\n",
instance);
-
+
is->task = GNUNET_SCHEDULER_add_now (interpreter_run,
is);
return;
@@ -1532,11 +1545,11 @@ interpreter_run (void *cls)
case OC_PROPOSAL_LOOKUP:
{
const char *order_id;
-
+
GNUNET_assert (NULL != cmd->details.proposal_lookup.proposal_reference);
ref = find_command (is, cmd->details.proposal_lookup.proposal_reference);
GNUNET_assert (NULL != ref);
-
+
order_id =
json_string_value (json_object_get (ref->details.proposal.proposal_data,
"order_id"));
@@ -1549,7 +1562,7 @@ interpreter_run (void *cls)
proposal_lookup_cb,
is)));
}
-
+
return;
case OC_ADMIN_ADD_INCOMING:
@@ -1919,6 +1932,7 @@ interpreter_run (void *cls)
&amount,
cmd->details.check_bank_transfer.account_debit,
cmd->details.check_bank_transfer.account_credit,
+ EXCHANGE_URI,
&cmd->details.check_bank_transfer.wtid))
{
GNUNET_break (0);
@@ -2372,7 +2386,7 @@ run (void *cls)
/* Proposal lookup */
{
- .oc = OC_PROPOSAL_LOOKUP,
+ .oc = OC_PROPOSAL_LOOKUP,
.label = "fetch-proposal-2",
.expected_response_code = MHD_HTTP_OK,
.details.proposal_lookup.proposal_reference = "create-proposal-2" },
@@ -2388,8 +2402,8 @@ run (void *cls)
/* Obtain WTID of the transfer */
{ .oc = OC_CHECK_BANK_TRANSFER,
- .label = "check_bank_transfer-499c",
- .details.check_bank_transfer.amount = "EUR:4.99",
+ .label = "check_bank_transfer-498c",
+ .details.check_bank_transfer.amount = "EUR:4.98",
.details.check_bank_transfer.account_debit = 2, /* exchange-outgoing */
.details.check_bank_transfer.account_credit = 62 /* merchant */
},
@@ -2401,21 +2415,22 @@ run (void *cls)
{ .oc = OC_TRACK_TRANSACTION,
.label = "track-transaction-1",
.expected_response_code = MHD_HTTP_OK,
- .details.track_transaction.expected_transfer_ref = "check_bank_transfer-499c",
- .details.track_transaction.pay_ref = "deposit-simple"
+ .details.track_transaction.expected_transfer_ref = "check_bank_transfer-498c",
+ .details.track_transaction.pay_ref = "deposit-simple",
+ .details.track_transaction.wire_fee = "EUR:0.01",
},
/* Trace the WTID back to the original transaction */
{ .oc = OC_TRACK_TRANSFER,
.label = "track-transfer-1",
.expected_response_code = MHD_HTTP_OK,
- .details.track_transfer.check_bank_ref = "check_bank_transfer-499c",
+ .details.track_transfer.check_bank_ref = "check_bank_transfer-498c",
.details.track_transfer.expected_pay_ref = "deposit-simple"
},
{ .oc = OC_TRACK_TRANSFER,
.label = "track-transfer-1-again",
.expected_response_code = MHD_HTTP_OK,
- .details.track_transfer.check_bank_ref = "check_bank_transfer-499c",
+ .details.track_transfer.check_bank_ref = "check_bank_transfer-498c",
.details.track_transfer.expected_pay_ref = "deposit-simple"
},
@@ -2434,8 +2449,8 @@ run (void *cls)
/* Obtain WTID of the transfer */
{ .oc = OC_CHECK_BANK_TRANSFER,
- .label = "check_bank_transfer-499c-2",
- .details.check_bank_transfer.amount = "EUR:4.99",
+ .label = "check_bank_transfer-498c-2",
+ .details.check_bank_transfer.amount = "EUR:4.98",
.details.check_bank_transfer.account_debit = 2, /* exchange-outgoing */
.details.check_bank_transfer.account_credit = 62 /* merchant */
},
@@ -2448,21 +2463,22 @@ run (void *cls)
{ .oc = OC_TRACK_TRANSFER,
.label = "track-transfer-2",
.expected_response_code = MHD_HTTP_OK,
- .details.track_transfer.check_bank_ref = "check_bank_transfer-499c-2",
+ .details.track_transfer.check_bank_ref = "check_bank_transfer-498c-2",
.details.track_transfer.expected_pay_ref = "deposit-simple-2"
},
{ .oc = OC_TRACK_TRANSFER,
.label = "track-transfer-2-again",
.expected_response_code = MHD_HTTP_OK,
- .details.track_transfer.check_bank_ref = "check_bank_transfer-499c-2",
+ .details.track_transfer.check_bank_ref = "check_bank_transfer-498c-2",
.details.track_transfer.expected_pay_ref = "deposit-simple-2"
},
{ .oc = OC_TRACK_TRANSACTION,
.label = "track-transaction-2",
.expected_response_code = MHD_HTTP_OK,
- .details.track_transaction.expected_transfer_ref = "check_bank_transfer-499c-2",
- .details.track_transaction.pay_ref = "deposit-simple-2"
+ .details.track_transaction.expected_transfer_ref = "check_bank_transfer-498c-2",
+ .details.track_transaction.wire_fee = "EUR:0.01",
+ .details.track_transaction.pay_ref = "deposit-simple-2"
},
{ .oc = OC_HISTORY,
@@ -2476,7 +2492,7 @@ run (void *cls)
.label = "history-2",
.expected_response_code = MHD_HTTP_OK,
/*no records to be returned, as limit is in the future*/
- .details.history.date.abs_value_us = 43 * 1000LL * 1000LL,
+ .details.history.date.abs_value_us = 43 * 1000LL * 1000LL,
.details.history.nresult = 0
},
@@ -2635,8 +2651,7 @@ main (int argc,
merchantd = GNUNET_OS_start_process (GNUNET_NO,
GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
- "valgrind",
- "valgrind",
+ "taler-merchant-httpd",
"taler-merchant-httpd",
"-c", "test_merchant_api.conf",
"-L", "DEBUG",
diff --git a/src/lib/test_merchant_api.conf b/src/lib/test_merchant_api.conf
@@ -19,9 +19,6 @@ CURRENCY = EUR
# Which port do we run the backend on? (HTTP server)
PORT = 8082
-# FIXME: is this one used?
-HOSTNAME = localhost
-
# How quickly do we want the exchange to send us our money?
# Used only if the frontend does not specify a value.
# FIXME: EDATE is a bit short, 'execution_delay'?
@@ -30,7 +27,7 @@ EDATE = 3 week
# Which plugin (backend) do we use for the DB.
DB = postgres
-# Which wireformat do we use?
+# Wire format supported by the merchant.
WIREFORMAT = test
# This option is a list of instances which are to be used
@@ -39,6 +36,25 @@ WIREFORMAT = test
# section like X-wireformat and merchant-instance-X
INSTANCES = tor default
+
+[exchange-wire-test]
+# Enable 'test' for testing of the actual coin operations.
+ENABLE = YES
+
+# Fees for the forseeable future...
+# If you see this after 2017, update to match the next 10 years...
+WIRE-FEE-2017 = EUR:0.01
+WIRE-FEE-2018 = EUR:0.01
+WIRE-FEE-2019 = EUR:0.01
+WIRE-FEE-2020 = EUR:0.01
+WIRE-FEE-2021 = EUR:0.01
+WIRE-FEE-2022 = EUR:0.01
+WIRE-FEE-2023 = EUR:0.01
+WIRE-FEE-2024 = EUR:0.01
+WIRE-FEE-2025 = EUR:0.01
+WIRE-FEE-2026 = EUR:0.01
+
+
[merchant-exchange-test]
URI = http://localhost:8081/
MASTER_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
@@ -89,10 +105,10 @@ ADDRESS = "Garching"
[exchange]
# How to access our database
DB = postgres
+
# HTTP port the exchange listens to
PORT = 8081
-# Wire format supported by the exchange
-WIREFORMAT = test
+
# Our public key
MASTER_PUBLIC_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG