commit 893e793496541fc4d28d0d5b77739952de0e1e26
parent 93b7f0022fdb0f8fc62f2ef977e01a87973d5376
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date: Wed, 23 Jul 2025 08:25:29 +0200
Merge branch 'master' into dev/bohdan-potuzhnyi/donau-integration
Diffstat:
11 files changed, 1122 insertions(+), 380 deletions(-)
diff --git a/configure.ac b/configure.ac
@@ -18,7 +18,7 @@
# This configure file is in the public domain
AC_PREREQ([2.69])
-AC_INIT([taler-merchant],[1.0.4],[taler-bug@gnunet.org])
+AC_INIT([taler-merchant],[1.0.5],[taler-bug@gnunet.org])
AC_CONFIG_SRCDIR([src/backend/taler-merchant-httpd.c])
AC_CONFIG_HEADERS([taler_merchant_config.h])
# support for non-recursive builds
diff --git a/debian/changelog b/debian/changelog
@@ -1,3 +1,9 @@
+taler-merchant (1.0.5) unstable; urgency=low
+
+ * Release 1.0.5.
+
+ -- Florian Dold <florian@dold.me> Wed, 16 Jul 2025 21:46:01 +0200
+
taler-merchant (1.0.4) unstable; urgency=low
* Release 1.0.4.
diff --git a/doc/doxygen/taler.doxy b/doc/doxygen/taler.doxy
@@ -5,7 +5,7 @@
#---------------------------------------------------------------------------
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "GNU Taler: Merchant"
-PROJECT_NUMBER = 1.0.4
+PROJECT_NUMBER = 1.0.5
PROJECT_LOGO = logo.svg
OUTPUT_DIRECTORY = .
CREATE_SUBDIRS = YES
diff --git a/src/backend/taler-merchant-httpd_config.c b/src/backend/taler-merchant-httpd_config.c
@@ -43,7 +43,7 @@
* #MERCHANT_PROTOCOL_CURRENT and #MERCHANT_PROTOCOL_AGE in
* merchant_api_config.c!
*/
-#define MERCHANT_PROTOCOL_VERSION "20:0:17"
+#define MERCHANT_PROTOCOL_VERSION "20:0:8"
/**
diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c
@@ -35,7 +35,7 @@
* Threshold after which exponential backoff should not increase.
*/
#define RETRY_BACKOFF_THRESHOLD GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_SECONDS, 60)
+ GNUNET_TIME_UNIT_SECONDS, 60)
/**
* This is how long /keys long-polls for, so we should
@@ -43,7 +43,7 @@
* answer. See exchange_api_handle.c.
*/
#define LONG_POLL_THRESHOLD GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_SECONDS, 120)
+ GNUNET_TIME_UNIT_SECONDS, 120)
/**
@@ -258,6 +258,18 @@ lookup_exchange (const char *exchange_url)
}
+bool
+TMH_EXCHANGES_check_trusted (
+ const char *exchange_url)
+{
+ struct TMH_Exchange *exchange = lookup_exchange (exchange_url);
+
+ if (NULL == exchange)
+ return false;
+ return exchange->trusted;
+}
+
+
/**
* Check if we have any remaining pending requests for the
* given @a exchange, and if we have the required data, call
diff --git a/src/backend/taler-merchant-httpd_exchanges.h b/src/backend/taler-merchant-httpd_exchanges.h
@@ -76,6 +76,18 @@ struct TMH_EXCHANGES_KeysOperation;
/**
+ * Check if we trust the exchange at @a exchange_url.
+ *
+ * @param exchange_url exchange base url to check
+ * @return true if we trust that exchange (assuming the master
+ * public key matches)
+ */
+bool
+TMH_EXCHANGES_check_trusted (
+ const char *exchange_url);
+
+
+/**
* Get /keys of the given @a exchange.
*
* @param exchange URL of the exchange we would like to talk to
diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c
@@ -911,6 +911,13 @@ kyc_status_cb (
struct KycContext *kc = cls;
struct ExchangeKycRequest *ekr;
+ if (! TMH_EXCHANGES_check_trusted (exchange_url))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Skipping exchange `%s': not trusted\n",
+ exchange_url);
+ return;
+ }
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC status for `%s' at `%s' is %u/%s/%s/%s\n",
payto_uri.full_payto,
diff --git a/src/backend/taler-merchant-httpd_private-get-orders.c b/src/backend/taler-merchant-httpd_private-get-orders.c
@@ -20,6 +20,7 @@
*/
#include "platform.h"
#include "taler-merchant-httpd_private-get-orders.h"
+#include <taler/taler_merchant_util.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_dbevents.h>
@@ -302,11 +303,12 @@ add_order (void *cls,
json_t *contract_terms = NULL;
struct TALER_PrivateContractHashP h_contract_terms;
enum GNUNET_DB_QueryStatus qs;
- const char *summary;
char *order_id = NULL;
bool refundable = false;
bool paid;
- struct TALER_Amount order_amount;
+ bool wired;
+ struct TALER_MERCHANT_Contract *contract = NULL;
+ int16_t choice_index = -1;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Adding order `%s' (%llu) to result set at instance `%s'\n",
@@ -341,13 +343,19 @@ add_order (void *cls,
{
/* First try to find the order in the contracts */
uint64_t os;
-
- qs = TMH_db->lookup_contract_terms (TMH_db->cls,
- po->instance_id,
- order_id,
- &contract_terms,
- &os,
- NULL);
+ bool session_matches;
+
+ qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
+ po->instance_id,
+ order_id,
+ NULL,
+ &contract_terms,
+ &os,
+ &paid,
+ &wired,
+ &session_matches,
+ NULL,
+ &choice_index);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
GNUNET_break (os == order_serial);
}
@@ -356,6 +364,8 @@ add_order (void *cls,
/* Might still be unclaimed, so try order table */
struct TALER_MerchantPostDataHashP unused;
+ paid = false;
+ wired = false;
qs = TMH_db->lookup_order (TMH_db->cls,
po->instance_id,
order_id,
@@ -368,112 +378,152 @@ add_order (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Order %llu disappeared during iteration. Skipping.\n",
(unsigned long long) order_serial);
- json_decref (contract_terms); /* should still be NULL */
- GNUNET_free (order_id);
- return;
+ goto cleanup;
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
GNUNET_break (0);
po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
- json_decref (contract_terms);
- GNUNET_free (order_id);
- return;
+ goto cleanup;
}
+ contract = TALER_MERCHANT_contract_parse (contract_terms,
+ true);
+ if (NULL == contract)
{
- struct GNUNET_TIME_Timestamp rd;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("amount",
- &order_amount),
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &rd),
- GNUNET_JSON_spec_string ("summary",
- &summary),
- GNUNET_JSON_spec_end ()
+ GNUNET_break (0);
+ po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID;
+ goto cleanup;
+ }
+
+ if (GNUNET_TIME_absolute_is_future (
+ contract->refund_deadline.abs_time) &&
+ paid)
+ {
+ struct ProcessRefundsClosure prc = {
+ .ec = TALER_EC_NONE
};
+ const struct TALER_Amount *brutto;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (contract_terms,
- spec,
- NULL, NULL))
+ switch (contract->version)
{
+ case TALER_MERCHANT_CONTRACT_VERSION_0:
+ brutto = &contract->details.v0.brutto;
+ break;
+ case TALER_MERCHANT_CONTRACT_VERSION_1:
+ {
+ struct TALER_MERCHANT_ContractChoice *choice
+ = &contract->details.v1.choices[choice_index];
+
+ GNUNET_assert (choice_index < contract->details.v1.choices_len);
+ brutto = &choice->amount;
+ }
+ break;
+ default:
GNUNET_break (0);
- po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID;
- json_decref (contract_terms);
- GNUNET_free (order_id);
- return;
+ goto cleanup;
+ }
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (brutto->currency,
+ &prc.total_refund_amount));
+ qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
+ po->instance_id,
+ &h_contract_terms,
+ &process_refunds_cb,
+ &prc);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
+ goto cleanup;
}
+ if (TALER_EC_NONE != prc.ec)
+ {
+ GNUNET_break (0);
+ po->result = prc.ec;
+ goto cleanup;
+ }
+ if (0 > TALER_amount_cmp (&prc.total_refund_amount,
+ brutto))
+ refundable = true;
+ }
- if (TALER_amount_is_zero (&order_amount) &&
+ switch (contract->version)
+ {
+ case TALER_MERCHANT_CONTRACT_VERSION_0:
+ if (TALER_amount_is_zero (&contract->details.v0.brutto) &&
(po->of.wired != TALER_EXCHANGE_YNA_ALL) )
{
/* If we are actually filtering by wire status,
and the order was over an amount of zero,
do not return it as wire status is not
exactly meaningful for orders over zero. */
- json_decref (contract_terms);
- GNUNET_free (order_id);
- return;
+ goto cleanup;
}
-
- if (GNUNET_TIME_absolute_is_future (rd.abs_time) &&
- paid)
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ po->pa,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("order_id",
+ contract->order_id),
+ GNUNET_JSON_pack_uint64 ("row_id",
+ order_serial),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ creation_time),
+ TALER_JSON_pack_amount (
+ "amount",
+ &contract->details.v0.brutto),
+ GNUNET_JSON_pack_string ("summary",
+ contract->summary),
+ GNUNET_JSON_pack_bool ("refundable",
+ refundable),
+ GNUNET_JSON_pack_bool ("paid",
+ paid))));
+ break;
+ case TALER_MERCHANT_CONTRACT_VERSION_1:
+ if (-1 == choice_index)
+ choice_index = 0; /* default choice */
+ GNUNET_assert (choice_index < contract->details.v1.choices_len);
{
- struct ProcessRefundsClosure prc = {
- .ec = TALER_EC_NONE
- };
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (order_amount.currency,
- &prc.total_refund_amount));
- qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
- po->instance_id,
- &h_contract_terms,
- &process_refunds_cb,
- &prc);
- if (0 > qs)
- {
- GNUNET_break (0);
- po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
- json_decref (contract_terms);
- GNUNET_free (order_id);
- return;
- }
- if (TALER_EC_NONE != prc.ec)
- {
- GNUNET_break (0);
- po->result = prc.ec;
- json_decref (contract_terms);
- GNUNET_free (order_id);
- return;
- }
- if (0 > TALER_amount_cmp (&prc.total_refund_amount,
- &order_amount))
- refundable = true;
+ struct TALER_MERCHANT_ContractChoice *choice
+ = &contract->details.v1.choices[choice_index];
+
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ po->pa,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("order_id",
+ contract->order_id),
+ GNUNET_JSON_pack_uint64 ("row_id",
+ order_serial),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ creation_time),
+ TALER_JSON_pack_amount ("amount",
+ &choice->amount),
+ GNUNET_JSON_pack_string ("summary",
+ contract->summary),
+ GNUNET_JSON_pack_bool ("refundable",
+ refundable),
+ GNUNET_JSON_pack_bool ("paid",
+ paid))));
}
+ break;
+ default:
+ GNUNET_break (0);
+ goto cleanup;
}
- GNUNET_assert (0 ==
- json_array_append_new (
- po->pa,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("order_id",
- order_id),
- GNUNET_JSON_pack_uint64 ("row_id",
- order_serial),
- GNUNET_JSON_pack_timestamp ("timestamp",
- creation_time),
- TALER_JSON_pack_amount ("amount",
- &order_amount),
- GNUNET_JSON_pack_string ("summary",
- summary),
- GNUNET_JSON_pack_bool ("refundable",
- refundable),
- GNUNET_JSON_pack_bool ("paid",
- paid))));
+cleanup:
json_decref (contract_terms);
GNUNET_free (order_id);
+ if (NULL != contract)
+ {
+ TALER_MERCHANT_contract_free (contract);
+ contract = NULL;
+ }
}
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2024 Taler Systems SA
+ (C) 2014-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -157,6 +157,47 @@ struct RekeyExchange
/**
+ * Data structure where we evaluate the viability of a given
+ * wire method for this order.
+ */
+struct WireMethodCandidate
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct WireMethodCandidate *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WireMethodCandidate *prev;
+
+ /**
+ * The wire method we are evaluating.
+ */
+ const struct TMH_WireMethod *wm;
+
+ /**
+ * List of exchanges to use when we use this wire method.
+ */
+ json_t *exchanges;
+
+ /**
+ * Array of maximum amounts that could be paid over all available exchanges
+ * for this @a wm. Used to determine if this order creation requests exceeds
+ * legal limits.
+ */
+ struct TALER_Amount *total_exchange_limits;
+
+ /**
+ * Length of the @e total_exchange_limits array.
+ */
+ unsigned int num_total_exchange_limits;
+
+};
+
+
+/**
* Information we keep per order we are processing.
*/
struct OrderContext
@@ -232,19 +273,6 @@ struct OrderContext
} parse_request;
-
- /**
- * Information set in the ORDER_PHASE_ADD_PAYMENT_DETAILS phase.
- */
- struct
- {
- /**
- * Wire method (and our bank account) we have selected
- * to be included for this order.
- */
- const struct TMH_WireMethod *wm;
- } add_payment_details;
-
/**
* Information set in the ORDER_PHASE_PARSE_ORDER phase.
*/
@@ -431,52 +459,78 @@ struct OrderContext
} merge_inventory;
/**
- * Information set in the ORDER_PHASE_SET_EXCHANGES phase.
+ * Information set in the ORDER_PHASE_ADD_PAYMENT_DETAILS phase.
*/
struct
{
+
/**
- * Array of exchanges we find acceptable for this
- * order.
+ * DLL of wire methods under evaluation.
*/
- json_t *exchanges;
+ struct WireMethodCandidate *wmc_head;
/**
- * Forced requests to /keys to update our exchange
- * information.
+ * DLL of wire methods under evaluation.
*/
- struct RekeyExchange *pending_reload_head;
+ struct WireMethodCandidate *wmc_tail;
/**
- * Forced requests to /keys to update our exchange
- * information.
+ * Array of maximum amounts that appear in the contract choices
+ * per currency.
+ * Determines the maximum amounts that a client could pay for this
+ * order and which we must thus make sure is acceptable for the
+ * selected wire method/account if possible.
*/
- struct RekeyExchange *pending_reload_tail;
+ struct TALER_Amount *max_choice_limits;
/**
- * Did we previously force reloading of /keys from
- * all exchanges? Set to 'true' to prevent us from
- * doing it again (and again...).
+ * Length of the @e max_choice_limits array.
*/
- bool forced_reload;
+ unsigned int num_max_choice_limits;
+
+ /**
+ * Set to true if we may need an exchange. True if any amount is non-zero.
+ */
+ bool need_exchange;
+
+ } add_payment_details;
+
+ /**
+ * Information set in the ORDER_PHASE_SELECT_WIRE_METHOD phase.
+ */
+ struct
+ {
+
+ /**
+ * Array of exchanges we find acceptable for this order and wire method.
+ */
+ json_t *exchanges;
/**
- * Set to true once we are sure that we have at
- * least one good exchange.
+ * Wire method (and our bank account) we have selected
+ * to be included for this order.
*/
- bool exchange_good;
+ const struct TMH_WireMethod *wm;
+
+ } select_wire_method;
+
+ /**
+ * Information set in the ORDER_PHASE_SET_EXCHANGES phase.
+ */
+ struct
+ {
/**
- * Array of maximum amounts that could be paid over all
- * available exchanges. Used to determine if this
- * order creation requests exceeds legal limits.
+ * Forced requests to /keys to update our exchange
+ * information.
*/
- struct TALER_Amount *total_exchange_limits;
+ struct RekeyExchange *pending_reload_head;
/**
- * Length of the @e total_exchange_limits array.
+ * Forced requests to /keys to update our exchange
+ * information.
*/
- unsigned int num_total_exchange_limits;
+ struct RekeyExchange *pending_reload_tail;
/**
* How long do we wait at most until giving up on getting keys?
@@ -489,6 +543,19 @@ struct OrderContext
struct GNUNET_SCHEDULER_Task *wakeup_task;
/**
+ * Did we previously force reloading of /keys from
+ * all exchanges? Set to 'true' to prevent us from
+ * doing it again (and again...).
+ */
+ bool forced_reload;
+
+ /**
+ * Set to true once we have attempted to load exchanges
+ * for the first time.
+ */
+ bool exchanges_tried;
+
+ /**
* Details depending on the contract version.
*/
union
@@ -638,6 +705,7 @@ struct OrderContext
ORDER_PHASE_MERGE_INVENTORY,
ORDER_PHASE_ADD_PAYMENT_DETAILS,
ORDER_PHASE_SET_EXCHANGES,
+ ORDER_PHASE_SELECT_WIRE_METHOD,
ORDER_PHASE_SET_MAX_FEE,
ORDER_PHASE_SERIALIZE_ORDER,
ORDER_PHASE_SALT_FORGETTABLE,
@@ -687,6 +755,66 @@ TMH_force_orders_resume ()
/**
+ * Add the given @a val to the @a array. Adds the
+ * amount to a given entry in @a array if one with the same
+ * currency exists, otherwise extends the @a array.
+ *
+ * @param[in,out] array pointer to array of amounts
+ * @param[in,out] array_len length of @a array
+ * @param val amount to add
+ * @param cap cap for the sums to enforce, can be NULL
+ */
+static void
+add_to_currency_vector (struct TALER_Amount **array,
+ unsigned int *array_len,
+ const struct TALER_Amount *val,
+ const struct TALER_Amount *cap)
+{
+ for (unsigned int i = 0; i<*array_len; i++)
+ {
+ struct TALER_Amount *ai = &(*array)[i];
+
+ if (GNUNET_OK ==
+ TALER_amount_cmp_currency (ai,
+ val))
+ {
+ enum TALER_AmountArithmeticResult aar;
+
+ aar = TALER_amount_add (ai,
+ ai,
+ val);
+ /* If we have a cap, we tolerate the overflow */
+ GNUNET_assert ( (aar >= 0) ||
+ ( (TALER_AAR_INVALID_RESULT_OVERFLOW == aar) &&
+ (NULL != cap) ) );
+ if (TALER_AAR_INVALID_RESULT_OVERFLOW == aar)
+ {
+ *ai = *cap;
+ }
+ else if (NULL != cap)
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_min (ai,
+ ai,
+ cap));
+ return;
+ }
+ }
+ GNUNET_array_append (*array,
+ *array_len,
+ *val);
+ if (NULL != cap)
+ {
+ struct TALER_Amount *ai = &(*array)[(*array_len) - 1];
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_min (ai,
+ ai,
+ cap));
+ }
+}
+
+
+/**
* Update the phase of @a oc based on @a mret.
*
* @param[in,out] oc order to update phase for
@@ -748,6 +876,26 @@ reply_with_error (struct OrderContext *oc,
/**
+ * Clean up memory used by @a wmc.
+ *
+ * @param[in,out] oc order context the WMC is part of
+ * @param[in] wmc wire method candidate to free
+ */
+static void
+free_wmc (struct OrderContext *oc,
+ struct WireMethodCandidate *wmc)
+{
+ GNUNET_CONTAINER_DLL_remove (oc->add_payment_details.wmc_head,
+ oc->add_payment_details.wmc_tail,
+ wmc);
+ GNUNET_array_grow (wmc->total_exchange_limits,
+ wmc->num_total_exchange_limits,
+ 0);
+ GNUNET_free (wmc);
+}
+
+
+/**
* Clean up memory used by @a cls.
*
* @param[in] cls the `struct OrderContext` to clean up
@@ -758,6 +906,9 @@ clean_order (void *cls)
struct OrderContext *oc = cls;
struct RekeyExchange *rx;
+ while (NULL != oc->add_payment_details.wmc_head)
+ free_wmc (oc,
+ oc->add_payment_details.wmc_head);
while (NULL != (rx = oc->set_exchanges.pending_reload_head))
{
GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
@@ -767,17 +918,19 @@ clean_order (void *cls)
GNUNET_free (rx->url);
GNUNET_free (rx);
}
+ GNUNET_array_grow (oc->add_payment_details.max_choice_limits,
+ oc->add_payment_details.num_max_choice_limits,
+ 0);
if (NULL != oc->set_exchanges.wakeup_task)
{
GNUNET_SCHEDULER_cancel (oc->set_exchanges.wakeup_task);
oc->set_exchanges.wakeup_task = NULL;
}
- if (NULL != oc->set_exchanges.exchanges)
+ if (NULL != oc->select_wire_method.exchanges)
{
- json_decref (oc->set_exchanges.exchanges);
- oc->set_exchanges.exchanges = NULL;
+ json_decref (oc->select_wire_method.exchanges);
+ oc->select_wire_method.exchanges = NULL;
}
- GNUNET_free (oc->set_exchanges.total_exchange_limits);
switch (oc->parse_order.version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
@@ -855,6 +1008,8 @@ clean_order (void *cls)
}
+/* ***************** ORDER_PHASE_EXECUTE_ORDER **************** */
+
/**
* Execute the database transaction to setup the order.
*
@@ -1035,7 +1190,7 @@ execute_transaction (struct OrderContext *oc)
* @param[in,out] oc order context
*/
static void
-execute_order (struct OrderContext *oc)
+phase_execute_order (struct OrderContext *oc)
{
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
@@ -1229,6 +1384,9 @@ execute_order (struct OrderContext *oc)
}
+/* ***************** ORDER_PHASE_CHECK_CONTRACT **************** */
+
+
/**
* Check that the contract is now well-formed. Upon success, continue
* processing with execute_order().
@@ -1236,7 +1394,7 @@ execute_order (struct OrderContext *oc)
* @param[in,out] oc order context
*/
static void
-check_contract (struct OrderContext *oc)
+phase_check_contract (struct OrderContext *oc)
{
struct TALER_PrivateContractHashP h_control;
@@ -1273,6 +1431,9 @@ check_contract (struct OrderContext *oc)
}
+/* ***************** ORDER_PHASE_SALT_FORGETTABLE **************** */
+
+
/**
* Modify the final contract terms adding salts for
* items that are forgettable.
@@ -1280,7 +1441,7 @@ check_contract (struct OrderContext *oc)
* @param[in,out] oc order context
*/
static void
-salt_forgettable (struct OrderContext *oc)
+phase_salt_forgettable (struct OrderContext *oc)
{
if (GNUNET_OK !=
TALER_JSON_contract_seed_forgettable (oc->parse_request.order,
@@ -1298,6 +1459,8 @@ salt_forgettable (struct OrderContext *oc)
}
+/* ***************** ORDER_PHASE_SERIALIZE_ORDER **************** */
+
/**
* Get rounded time interval. @a start is calculated by rounding
* @a ts down to the nearest multiple of @a precision.
@@ -1959,7 +2122,7 @@ output_contract_choices (struct OrderContext *oc)
* @param[in,out] oc order context
*/
static void
-serialize_order (struct OrderContext *oc)
+phase_serialize_order (struct OrderContext *oc)
{
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
@@ -2037,9 +2200,9 @@ serialize_order (struct OrderContext *oc)
GNUNET_JSON_pack_array_incref ("products",
oc->merge_inventory.products),
GNUNET_JSON_pack_data_auto ("h_wire",
- &oc->add_payment_details.wm->h_wire),
+ &oc->select_wire_method.wm->h_wire),
GNUNET_JSON_pack_string ("wire_method",
- oc->add_payment_details.wm->wire_method),
+ oc->select_wire_method.wm->wire_method),
GNUNET_JSON_pack_string ("order_id",
oc->parse_order.order_id),
GNUNET_JSON_pack_timestamp ("timestamp",
@@ -2062,7 +2225,7 @@ serialize_order (struct OrderContext *oc)
GNUNET_JSON_pack_data_auto ("merchant_pub",
&oc->hc->instance->merchant_pub),
GNUNET_JSON_pack_array_incref ("exchanges",
- oc->set_exchanges.exchanges),
+ oc->select_wire_method.exchanges),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("extra",
(json_t *) oc->parse_order.extra))
@@ -2129,6 +2292,9 @@ serialize_order (struct OrderContext *oc)
}
+/* ***************** ORDER_PHASE_SET_MAX_FEE **************** */
+
+
/**
* Set @a max_fee in @a oc based on @a max_stefan_fee value if not overridden
* by @a client_fee. If neither is set, set the fee to zero using currency
@@ -2178,7 +2344,7 @@ compute_fee (struct OrderContext *oc,
* @param[in,out] oc order context
*/
static void
-set_max_fee (struct OrderContext *oc)
+phase_set_max_fee (struct OrderContext *oc)
{
switch (oc->parse_order.version)
{
@@ -2210,6 +2376,159 @@ set_max_fee (struct OrderContext *oc)
}
+/* ***************** ORDER_PHASE_SELECT_WIRE_METHOD **************** */
+
+/**
+ * Check that the @a brutto amount is at or below the
+ * limits we have for the respective wire method candidate.
+ *
+ * @param wmc wire method candidate to check
+ * @param brutto amount to check
+ * @param true if the amount is OK, false if it is too high
+ */
+static bool
+check_limits (struct WireMethodCandidate *wmc,
+ const struct TALER_Amount *brutto)
+{
+ for (unsigned int i = 0; i<wmc->num_total_exchange_limits; i++)
+ {
+ const struct TALER_Amount *total_exchange_limit
+ = &wmc->total_exchange_limits[i];
+
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (brutto,
+ total_exchange_limit))
+ continue;
+ if (1 !=
+ TALER_amount_cmp (brutto,
+ total_exchange_limit))
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Phase to select a wire method that will be acceptable for the order.
+ * If none is "perfect" (allows all choices), might jump back to the
+ * previous phase to force "/keys" downloads to see if that helps.
+ *
+ * @param[in,out] oc order context
+ */
+static void
+phase_select_wire_method (struct OrderContext *oc)
+{
+ const struct TALER_Amount *ea;
+ struct WireMethodCandidate *best = NULL;
+ unsigned int max_choices = 0;
+ unsigned int want_choices;
+
+ for (struct WireMethodCandidate *wmc = oc->add_payment_details.wmc_head;
+ NULL != wmc;
+ wmc = wmc->next)
+ {
+ unsigned int num_choices = 0;
+
+ switch (oc->parse_order.version)
+ {
+ case TALER_MERCHANT_CONTRACT_VERSION_0:
+ want_choices = 1;
+ ea = &oc->parse_order.details.v0.brutto;
+ if (TALER_amount_is_zero (ea) ||
+ check_limits (wmc,
+ ea))
+ num_choices++;
+ break;
+ case TALER_MERCHANT_CONTRACT_VERSION_1:
+ want_choices = oc->parse_choices.choices_len;
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ ea = &oc->parse_choices.choices[i].amount;
+ if (TALER_amount_is_zero (ea) ||
+ check_limits (wmc,
+ ea))
+ num_choices++;
+ }
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+ if (num_choices > max_choices)
+ {
+ best = wmc;
+ max_choices = num_choices;
+ }
+ }
+
+ if ( (want_choices > max_choices) &&
+ (! oc->set_exchanges.forced_reload) )
+ {
+ /* Not all choices in the contract can work with these
+ exchanges, try again with forcing /keys download */
+ for (struct WireMethodCandidate *wmc = oc->add_payment_details.wmc_head;
+ NULL != wmc;
+ wmc = wmc->next)
+ {
+ json_array_clear (wmc->exchanges);
+ GNUNET_array_grow (wmc->total_exchange_limits,
+ wmc->num_total_exchange_limits,
+ 0);
+ }
+ oc->phase = ORDER_PHASE_SET_EXCHANGES;
+ return;
+ }
+
+ if ( (NULL == best) &&
+ (NULL != oc->parse_request.payment_target) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Cannot create order: lacking suitable exchanges for payment target `%s'\n",
+ oc->parse_request.payment_target);
+ reply_with_error (
+ oc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGES_FOR_WIRE_METHOD,
+ oc->parse_request.payment_target);
+ return;
+ }
+
+ if (NULL == best)
+ {
+ /* We actually do not have ANY workable exchange(s) */
+ reply_with_error (
+ oc,
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_AMOUNT_EXCEEDS_LEGAL_LIMITS,
+ NULL);
+ return;
+ }
+
+ if (want_choices > max_choices)
+ {
+ /* Some choices are unpayable */
+ GNUNET_log (
+ GNUNET_ERROR_TYPE_WARNING,
+ "Creating order, but some choices do not work with the selected wire method\n");
+ }
+ if ( (0 == json_array_size (best->exchanges)) &&
+ (oc->add_payment_details.need_exchange) )
+ {
+ /* We did not find any reasonable exchange */
+ GNUNET_log (
+ GNUNET_ERROR_TYPE_WARNING,
+ "Creating order, but only for choices without payment\n");
+ }
+
+ oc->select_wire_method.wm
+ = best->wm;
+ oc->select_wire_method.exchanges
+ = json_incref (best->exchanges);
+ oc->phase++;
+}
+
+
+/* ***************** ORDER_PHASE_SET_EXCHANGES **************** */
+
/**
* Exchange `/keys` processing is done, resume handling
* the order.
@@ -2220,8 +2539,7 @@ static void
resume_with_keys (struct OrderContext *oc)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming order processing after /keys downloads (now have %u accounts)\n",
- (unsigned int) json_array_size (oc->set_exchanges.exchanges));
+ "Resuming order processing after /keys downloads\n");
GNUNET_assert (GNUNET_YES == oc->suspended);
GNUNET_CONTAINER_DLL_remove (oc_head,
oc_tail,
@@ -2321,10 +2639,12 @@ update_stefan (struct OrderContext *oc,
* too low and we failed to create an order.
*
* @param oc order context
+ * @param wmc wire method candidate to notify for
* @param exchange_url exchange to notify about
*/
static void
notify_kyc_required (const struct OrderContext *oc,
+ const struct WireMethodCandidate *wmc,
const char *exchange_url)
{
struct GNUNET_DB_EventHeaderP es = {
@@ -2335,8 +2655,8 @@ notify_kyc_required (const struct OrderContext *oc,
char *extra;
hws = GNUNET_STRINGS_data_to_string_alloc (
- &oc->add_payment_details.wm->h_wire,
- sizeof (oc->add_payment_details.wm->h_wire));
+ &wmc->wm->h_wire,
+ sizeof (wmc->wm->h_wire));
GNUNET_asprintf (&extra,
"%s %s",
@@ -2352,35 +2672,54 @@ notify_kyc_required (const struct OrderContext *oc,
/**
- * Compute the set of exchanges that would be acceptable
- * for this order.
+ * Checks the limits that apply for this @a exchange and
+ * the @a wmc and if the exchange is acceptable at all, adds it
+ * to the list of exchanges for the @a wmc.
*
- * @param cls our `struct OrderContext`
- * @param url base URL of an exchange (not used)
+ * @param oc context of the order
* @param exchange internal handle for the exchange
- * @param max_needed maximum amount needed in this currency
+ * @param exchange_url base URL of this exchange
+ * @param wmc wire method to evaluate this exchange for
+ * @return true if the exchange is acceptable for the contract
*/
-static void
-get_acceptable (void *cls,
- const char *url,
+static bool
+get_acceptable (struct OrderContext *oc,
const struct TMH_Exchange *exchange,
- const struct TALER_Amount *max_needed)
+ const char *exchange_url,
+ struct WireMethodCandidate *wmc)
{
- struct OrderContext *oc = cls;
+ const struct TALER_Amount *max_needed = NULL;
unsigned int priority = 42; /* make compiler happy */
json_t *j_exchange;
enum GNUNET_GenericReturnValue res;
struct TALER_Amount max_amount;
+ for (unsigned int i = 0; i<oc->add_payment_details.num_max_choice_limits; i++)
+ {
+ struct TALER_Amount *val = &oc->add_payment_details.max_choice_limits[i];
+
+ if (0 == strcasecmp (val->currency,
+ TMH_EXCHANGES_get_currency (exchange)))
+ {
+ max_needed = val;
+ break;
+ }
+ }
+ if (NULL == max_needed)
+ {
+ /* exchange currency not relevant for any of our choices, skip it */
+ return false;
+ }
+
max_amount = *max_needed;
res = TMH_exchange_check_debit (
oc->hc->instance->settings.id,
exchange,
- oc->add_payment_details.wm,
+ wmc->wm,
&max_amount);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Exchange %s evaluated at %d with max %s\n",
- url,
+ exchange_url,
res,
TALER_amount2s (&max_amount));
if (TALER_amount_is_zero (&max_amount))
@@ -2390,20 +2729,20 @@ get_acceptable (void *cls,
/* Trigger re-checking the current deposit limit when
* paying non-zero amount with zero deposit limit */
notify_kyc_required (oc,
- url);
+ wmc,
+ exchange_url);
}
/* If deposit is impossible, we don't list the
* exchange in the contract terms. */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Exchange %s deposit limit is zero, skipping it\n",
- url);
- return;
+ exchange_url);
+ return false;
}
switch (res)
{
case GNUNET_OK:
priority = 1024; /* high */
- oc->set_exchanges.exchange_good = true;
break;
case GNUNET_NO:
if (oc->set_exchanges.forced_reload)
@@ -2420,47 +2759,12 @@ get_acceptable (void *cls,
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Exchange %s deposit limit is %s, adding it!\n",
- url,
+ exchange_url,
TALER_amount2s (&max_amount));
- {
- bool found = false;
- for (unsigned int i = 0; i<oc->set_exchanges.num_total_exchange_limits; i++)
- {
- struct TALER_Amount *limit
- = &oc->set_exchanges.total_exchange_limits[i];
-
- if (GNUNET_OK ==
- TALER_amount_cmp_currency (limit,
- &max_amount))
- {
- GNUNET_assert (0 <=
- TALER_amount_add (limit,
- limit,
- &max_amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_min (limit,
- limit,
- max_needed));
- found = true;
- }
- }
- if (! found)
- {
- struct TALER_Amount limit;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_min (&limit,
- &max_amount,
- max_needed));
- GNUNET_array_append (oc->set_exchanges.total_exchange_limits,
- oc->set_exchanges.num_total_exchange_limits,
- limit);
- }
- }
j_exchange = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("url",
- url),
+ exchange_url),
GNUNET_JSON_pack_uint64 ("priority",
priority),
TALER_JSON_pack_amount ("max_contribution",
@@ -2468,9 +2772,16 @@ get_acceptable (void *cls,
GNUNET_JSON_pack_data_auto ("master_pub",
TMH_EXCHANGES_get_master_pub (exchange)));
GNUNET_assert (NULL != j_exchange);
+ /* Add exchange to list of exchanges for this wire method
+ candidate */
GNUNET_assert (0 ==
- json_array_append_new (oc->set_exchanges.exchanges,
+ json_array_append_new (wmc->exchanges,
j_exchange));
+ add_to_currency_vector (&wmc->total_exchange_limits,
+ &wmc->num_total_exchange_limits,
+ &max_amount,
+ max_needed);
+ return true;
}
@@ -2492,6 +2803,7 @@ keys_cb (
struct OrderContext *oc = rx->oc;
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
+ bool applicable = false;
rx->fo = NULL;
GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
@@ -2502,63 +2814,38 @@ keys_cb (
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to download %skeys\n",
rx->url);
+ goto cleanup;
}
- else
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got response for %skeys\n",
+ rx->url);
+
+ /* Evaluate the use of this exchange for each wire method candidate */
+ for (unsigned int j = 0; j<keys->accounts_len; j++)
{
- bool currency_ok = false;
- struct TALER_Amount max_needed;
+ struct TALER_FullPayto full_payto = keys->accounts[j].fpayto_uri;
+ char *wire_method = TALER_payto_get_method (full_payto.full_payto);
- switch (oc->parse_order.version)
+ for (struct WireMethodCandidate *wmc = oc->add_payment_details.wmc_head;
+ NULL != wmc;
+ wmc = wmc->next)
{
- case TALER_MERCHANT_CONTRACT_VERSION_0:
- if (0 == strcasecmp (keys->currency,
- oc->parse_order.details.v0.brutto.currency))
+ if (0 == strcmp (wmc->wm->wire_method,
+ wire_method) )
{
- max_needed = oc->parse_order.details.v0.brutto;
- currency_ok = true;
+ applicable |= get_acceptable (oc,
+ exchange,
+ rx->url,
+ wmc);
}
- break;
- case TALER_MERCHANT_CONTRACT_VERSION_1:
- for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
- {
- const struct TALER_Amount *amount
- = &oc->parse_choices.choices[i].amount;
-
- if (0 == strcasecmp (keys->currency,
- amount->currency))
- {
- if (currency_ok)
- {
- TALER_amount_max (&max_needed,
- &max_needed,
- amount);
- }
- else
- {
- max_needed = *amount;
- currency_ok = true;
- }
- }
- }
- break;
- default:
- GNUNET_assert (0);
- }
- if ( (currency_ok) &&
- (! TALER_amount_is_zero (&max_needed)) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Got response for %skeys\n",
- rx->url);
- if (settings->use_stefan)
- update_stefan (oc,
- keys);
- get_acceptable (oc,
- rx->url,
- exchange,
- &max_needed);
}
+ GNUNET_free (wire_method);
}
+ if (applicable &&
+ settings->use_stefan)
+ update_stefan (oc,
+ keys);
+cleanup:
GNUNET_free (rx->url);
GNUNET_free (rx);
if (NULL != oc->set_exchanges.pending_reload_head)
@@ -2624,102 +2911,49 @@ wakeup_timeout (void *cls)
/**
- * Check that the @a brutto amount is at or below the exchange
- * limits we have for the respective currency.
- *
- * @param oc order context to check
- * @param brutto amount to check
- * @return true if the amount is OK, false if it is too high
- */
-static bool
-check_exchange_limits (const struct OrderContext *oc,
- struct TALER_Amount *brutto)
-{
- for (unsigned int i = 0; i<oc->set_exchanges.num_total_exchange_limits; i++)
- {
- const struct TALER_Amount *total_exchange_limit
- = &oc->set_exchanges.total_exchange_limits[i];
-
- if (GNUNET_OK !=
- TALER_amount_cmp_currency (brutto,
- total_exchange_limit))
- continue;
- if (1 !=
- TALER_amount_cmp (brutto,
- total_exchange_limit))
- return true;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Cannot create order: %s is above the sum of hard limits from supported exchanges\n",
- TALER_amount2s (brutto));
- return false;
-}
-
-
-/**
- * Set list of acceptable exchanges in @a oc. Upon success, continue
- * processing with set_max_fee().
+ * Set list of acceptable exchanges in @a oc. Upon success, continues
+ * processing with add_payment_details().
*
* @param[in,out] oc order context
* @return true to suspend execution
*/
static bool
-set_exchanges (struct OrderContext *oc)
+phase_set_exchanges (struct OrderContext *oc)
{
- bool need_exchange;
-
if (NULL != oc->set_exchanges.wakeup_task)
{
GNUNET_SCHEDULER_cancel (oc->set_exchanges.wakeup_task);
oc->set_exchanges.wakeup_task = NULL;
}
- switch (oc->parse_order.version)
- {
- case TALER_MERCHANT_CONTRACT_VERSION_0:
- need_exchange = ! TALER_amount_is_zero (
- &oc->parse_order.details.v0.brutto);
- break;
- case TALER_MERCHANT_CONTRACT_VERSION_1:
- need_exchange = false;
- for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
- if (! TALER_amount_is_zero (&oc->parse_choices.choices[i].amount))
- {
- need_exchange = true;
- break;
- }
- break;
- default:
- GNUNET_assert (0);
- }
- if (! need_exchange)
+
+ if (! oc->add_payment_details.need_exchange)
{
/* Total amount is zero, so we don't actually need exchanges! */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Order total is zero, no need for exchanges\n");
- oc->set_exchanges.exchanges = json_array ();
- GNUNET_assert (NULL != oc->set_exchanges.exchanges);
- oc->phase++;
+ oc->select_wire_method.exchanges = json_array ();
+ GNUNET_assert (NULL != oc->select_wire_method.exchanges);
+ /* Pick first one, doesn't matter as the amount is zero */
+ oc->select_wire_method.wm = oc->hc->instance->wm_head;
+ oc->phase = ORDER_PHASE_SET_MAX_FEE;
return false;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Trying to find exchanges\n");
- if (NULL == oc->set_exchanges.exchanges)
+ if (NULL == oc->set_exchanges.pending_reload_head)
{
- oc->set_exchanges.keys_timeout
- = GNUNET_TIME_relative_to_absolute (MAX_KEYS_WAIT);
- oc->set_exchanges.exchanges = json_array ();
- GNUNET_assert (NULL != oc->set_exchanges.exchanges);
- TMH_exchange_get_trusted (&get_exchange_keys,
- oc);
- }
- else if (! oc->set_exchanges.exchange_good)
- {
- if (! oc->set_exchanges.forced_reload)
+ if (! oc->set_exchanges.exchanges_tried)
{
+ oc->set_exchanges.exchanges_tried = true;
+ oc->set_exchanges.keys_timeout
+ = GNUNET_TIME_relative_to_absolute (MAX_KEYS_WAIT);
+ TMH_exchange_get_trusted (&get_exchange_keys,
+ oc);
+ }
+ else if (! oc->set_exchanges.forced_reload)
+ {
+ /* Try one more time with forcing /keys download */
oc->set_exchanges.forced_reload = true;
- GNUNET_assert (0 ==
- json_array_clear (oc->set_exchanges.exchanges));
TMH_exchange_get_trusted (&get_exchange_keys,
oc);
}
@@ -2754,67 +2988,476 @@ set_exchanges (struct OrderContext *oc)
oc);
return true; /* reloads pending */
}
- if (0 == json_array_size (oc->set_exchanges.exchanges))
+ oc->phase++;
+ return false;
+}
+
+
+/* ***************** ORDER_PHASE_ADD_PAYMENT_DETAILS **************** */
+
+/**
+ * Process the @a payment_target and add the details of how the
+ * order could be paid to @a order. On success, continue
+ * processing with add_payment_fees().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+phase_add_payment_details (struct OrderContext *oc)
+{
+ /* First, determine the maximum amounts that could be paid per currency */
+ switch (oc->parse_order.version)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Cannot create order: lacking trusted exchanges\n");
- reply_with_error (
- oc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGES_FOR_WIRE_METHOD,
- oc->add_payment_details.wm->wire_method);
- return false;
+ case TALER_MERCHANT_CONTRACT_VERSION_0:
+ GNUNET_array_append (oc->add_payment_details.max_choice_limits,
+ oc->add_payment_details.num_max_choice_limits,
+ oc->parse_order.details.v0.brutto);
+ if (! TALER_amount_is_zero (
+ &oc->parse_order.details.v0.brutto))
+ {
+ oc->add_payment_details.need_exchange = true;
+ }
+ break;
+ case TALER_MERCHANT_CONTRACT_VERSION_1:
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ const struct TALER_Amount *amount
+ = &oc->parse_choices.choices[i].amount;
+ bool found = false;
+
+ if (! TALER_amount_is_zero (amount))
+ {
+ oc->add_payment_details.need_exchange = true;
+ }
+ for (unsigned int j = 0; j<oc->add_payment_details.num_max_choice_limits;
+ j++)
+ {
+ struct TALER_Amount *mx = &oc->add_payment_details.max_choice_limits[j];
+ if (GNUNET_YES ==
+ TALER_amount_cmp_currency (mx,
+ amount))
+ {
+ TALER_amount_max (mx,
+ mx,
+ amount);
+ found = true;
+ break;
+ }
+ }
+ if (! found)
+ {
+ GNUNET_array_append (oc->add_payment_details.max_choice_limits,
+ oc->add_payment_details.num_max_choice_limits,
+ *amount);
+ }
+ }
+ break;
+ default:
+ GNUNET_assert (0);
}
+ /* Then, create a candidate for each available wire method */
+ for (struct TMH_WireMethod *wm = oc->hc->instance->wm_head;
+ NULL != wm;
+ wm = wm->next)
{
- bool ok;
- struct TALER_Amount ea;
+ struct WireMethodCandidate *wmc;
+
+ /* Locate wire method that has a matching payment target */
+ if (! wm->active)
+ continue; /* ignore inactive methods */
+ if ( (NULL != oc->parse_request.payment_target) &&
+ (0 != strcasecmp (oc->parse_request.payment_target,
+ wm->wire_method) ) )
+ continue; /* honor client preference */
+ wmc = GNUNET_new (struct WireMethodCandidate);
+ wmc->wm = wm;
+ wmc->exchanges = json_array ();
+ GNUNET_assert (NULL != wmc->exchanges);
+ GNUNET_CONTAINER_DLL_insert (oc->add_payment_details.wmc_head,
+ oc->add_payment_details.wmc_tail,
+ wmc);
+ }
- switch (oc->parse_order.version)
+ if (NULL == oc->add_payment_details.wmc_head)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No wire method available for instance '%s'\n",
+ oc->hc->instance->settings.id);
+ reply_with_error (oc,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE,
+ oc->parse_request.payment_target);
+ return;
+ }
+
+ /* next, we'll evaluate available exchanges */
+ oc->phase++;
+}
+
+
+/* ***************** ORDER_PHASE_MERGE_INVENTORY **************** */
+
+
+/**
+ * Merge the inventory products into products, querying the
+ * database about the details of those products. Upon success,
+ * continue processing by calling add_payment_details().
+ *
+ * @param[in,out] oc order context to process
+ */
+static void
+phase_merge_inventory (struct OrderContext *oc)
+{
+ /**
+ * parse_request.inventory_products => instructions to add products to contract terms
+ * parse_order.products => contains products that are not from the backend-managed inventory.
+ */
+ if (NULL != oc->parse_order.products)
+ oc->merge_inventory.products
+ = json_deep_copy (oc->parse_order.products);
+ else
+ oc->merge_inventory.products
+ = json_array ();
+ /* Populate products from inventory product array and database */
+ {
+ GNUNET_assert (NULL != oc->merge_inventory.products);
+ for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
{
- case TALER_MERCHANT_CONTRACT_VERSION_0:
- ea = oc->parse_order.details.v0.brutto;
- ok = check_exchange_limits (oc,
- &ea);
- break;
- case TALER_MERCHANT_CONTRACT_VERSION_1:
- ok = true;
- for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ const struct InventoryProduct *ip
+ = &oc->parse_request.inventory_products[i];
+ struct TALER_MERCHANTDB_ProductDetails pd;
+ enum GNUNET_DB_QueryStatus qs;
+ size_t num_categories = 0;
+ uint64_t *categories = NULL;
+
+ qs = TMH_db->lookup_product (TMH_db->cls,
+ oc->hc->instance->settings.id,
+ ip->product_id,
+ &pd,
+ &num_categories,
+ &categories);
+ if (qs <= 0)
{
- ea = oc->parse_choices.choices[i].amount;
- if (! check_exchange_limits (oc,
- &ea))
+ enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ unsigned int http_status = 0;
+
+ switch (qs)
{
- ok = false;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Product %s from order unknown\n",
+ ip->product_id);
+ http_status = MHD_HTTP_NOT_FOUND;
+ ec = TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* case listed to make compilers happy */
+ GNUNET_assert (0);
}
+ reply_with_error (oc,
+ http_status,
+ ec,
+ ip->product_id);
+ return;
}
- break;
- default:
- GNUNET_assert (0);
- }
+ GNUNET_free (categories);
+ oc->parse_order.minimum_age
+ = GNUNET_MAX (oc->parse_order.minimum_age,
+ pd.minimum_age);
+ {
+ json_t *p;
- if (! ok)
- {
- reply_with_error (
- oc,
- MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_AMOUNT_EXCEEDS_LEGAL_LIMITS,
- TALER_amount2s (&ea));
- return false;
+ p = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("product_name",
+ pd.product_name),
+ GNUNET_JSON_pack_string ("description",
+ pd.description),
+ GNUNET_JSON_pack_object_steal ("description_i18n",
+ pd.description_i18n),
+ GNUNET_JSON_pack_string ("unit",
+ pd.unit),
+ TALER_JSON_pack_amount ("price",
+ &pd.price),
+ GNUNET_JSON_pack_array_steal ("taxes",
+ pd.taxes),
+ GNUNET_JSON_pack_string ("image",
+ pd.image),
+ GNUNET_JSON_pack_uint64 (
+ "quantity",
+ ip->quantity));
+ GNUNET_assert (NULL != p);
+ GNUNET_assert (0 ==
+ json_array_append_new (oc->merge_inventory.products,
+ p));
+ }
+ GNUNET_free (pd.description);
+ GNUNET_free (pd.unit);
+ GNUNET_free (pd.image);
+ json_decref (pd.address);
}
}
+ /* check if final product list is well-formed */
+ if (! TMH_products_array_valid (oc->merge_inventory.products))
+ {
+ GNUNET_break_op (0);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order:products");
+ return;
+ }
+ oc->phase++;
+}
+
+
+/* ***************** ORDER_PHASE_PARSE_CHOICES **************** */
+
+/**
+ * Parse contract choices. Upon success, continue
+ * processing with merge_inventory().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+phase_parse_choices (struct OrderContext *oc)
+{
+ const json_t *jchoices;
+
+ switch (oc->parse_order.version)
+ {
+ case TALER_MERCHANT_CONTRACT_VERSION_0:
+ oc->phase++;
+ return;
+ case TALER_MERCHANT_CONTRACT_VERSION_1:
+ /* handle below */
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+
+ jchoices = oc->parse_order.details.v1.choices;
- if (! oc->set_exchanges.exchange_good)
+ if (! json_is_array (jchoices))
+ GNUNET_assert (0);
+ if (0 == json_array_size (jchoices))
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Creating order, but possibly without usable trusted exchanges\n");
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "choices");
+ return;
+ }
+ GNUNET_array_grow (oc->parse_choices.choices,
+ oc->parse_choices.choices_len,
+ json_array_size (jchoices));
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ struct TALER_MERCHANT_ContractChoice *choice
+ = &oc->parse_choices.choices[i];
+ const char *error_name;
+ unsigned int error_line;
+ const json_t *jinputs;
+ const json_t *joutputs;
+ bool no_fee;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &choice->amount),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("max_fee",
+ &choice->max_fee),
+ &no_fee),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("inputs",
+ &jinputs),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("outputs",
+ &joutputs),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = GNUNET_JSON_parse (json_array_get (jchoices,
+ i),
+ spec,
+ &error_name,
+ &error_line);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Choice parsing failed: %s:%u\n",
+ error_name,
+ error_line);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "choice");
+ return;
+ }
+ if ( (! no_fee) &&
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&choice->amount,
+ &choice->max_fee)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ "different currencies used for 'max_fee' and 'amount' currency");
+ return;
+ }
+
+ if (! TMH_test_exchange_configured_for_currency (
+ choice->amount.currency))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGE_FOR_CURRENCY,
+ choice->amount.currency);
+ return;
+ }
+
+ if (NULL != jinputs)
+ {
+ const json_t *jinput;
+ size_t idx;
+ json_array_foreach ((json_t *) jinputs, idx, jinput)
+ {
+ struct TALER_MERCHANT_ContractInput input = {
+ .details.token.count = 1
+ };
+
+ if (GNUNET_OK !=
+ TALER_MERCHANT_parse_choice_input ((json_t *) jinput,
+ &input,
+ idx,
+ true))
+ {
+ GNUNET_break_op (0);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "input");
+ return;
+ }
+
+ switch (input.type)
+ {
+ case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
+ GNUNET_assert (0);
+ break;
+ case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
+ /* Ignore inputs tokens with 'count' field set to 0 */
+ if (0 == input.details.token.count)
+ continue;
+
+ if (GNUNET_OK !=
+ add_input_token_family (oc,
+ input.details.token.token_family_slug))
+
+ {
+ GNUNET_break_op (0);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN,
+ input.details.token.token_family_slug);
+ return;
+ }
+
+ GNUNET_array_append (choice->inputs,
+ choice->inputs_len,
+ input);
+ continue;
+ }
+ GNUNET_assert (0);
+ }
+ }
+
+ if (NULL != joutputs)
+ {
+ const json_t *joutput;
+ size_t idx;
+ json_array_foreach ((json_t *) joutputs, idx, joutput)
+ {
+ struct TALER_MERCHANT_ContractOutput output = {
+ .details.token.count = 1
+ };
+
+ if (GNUNET_OK !=
+ TALER_MERCHANT_parse_choice_output ((json_t *) joutput,
+ &output,
+ idx,
+ true))
+ {
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "output");
+ return;
+ }
+
+ switch (output.type)
+ {
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
+ GNUNET_assert (0);
+ break;
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
+ GNUNET_break (0); /* FIXME-#9059: not yet implemented! */
+ break;
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
+ /* Ignore inputs tokens with 'count' field set to 0 */
+ if (0 == output.details.token.count)
+ continue;
+
+ if (0 == output.details.token.valid_at.abs_time.abs_value_us)
+ output.details.token.valid_at
+ = GNUNET_TIME_timestamp_get ();
+ if (GNUNET_OK !=
+ add_output_token_family (oc,
+ output.details.token.token_family_slug,
+ output.details.token.valid_at,
+ &output.details.token.key_index))
+
+ {
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN,
+ output.details.token.token_family_slug);
+ return;
+ }
+
+ GNUNET_array_append (choice->outputs,
+ choice->outputs_len,
+ output);
+ continue;
+ }
+ GNUNET_assert (0);
+ }
+ }
}
oc->phase++;
- return false;
}
+/* ***************** ORDER_PHASE_PARSE_ORDER **************** */
+
+
/**
* Parse the order field of the request. Upon success, continue
* processing with parse_choices().
@@ -2822,7 +3465,7 @@ set_exchanges (struct OrderContext *oc)
* @param[in,out] oc order context
*/
static void
-parse_order (struct OrderContext *oc)
+phase_parse_order (struct OrderContext *oc)
{
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
@@ -3337,7 +3980,6 @@ parse_order (struct OrderContext *oc)
#ifdef HAVE_DONAU_DONAU_SERVICE_H
-
/**
* Callback function that is called for each donau instance.
* It simply adds the provided donau_url to the json.
@@ -3389,6 +4031,7 @@ parse_donau_instances (struct OrderContext *oc,
#endif
+
/**
* Parse contract choices. Upon success, continue
* processing with merge_inventory().
@@ -3554,8 +4197,9 @@ parse_choices (struct OrderContext *oc)
size_t idx;
json_array_foreach ((json_t *) joutputs, idx, joutput)
{
-
- struct TALER_MERCHANT_ContractOutput output = {};
+ struct TALER_MERCHANT_ContractOutput output = {
+ .details.token.count = 1
+ };
if (GNUNET_OK !=
TALER_MERCHANT_parse_choice_output ((json_t *) joutput,
@@ -3800,6 +4444,8 @@ merge_inventory (struct OrderContext *oc)
}
+/* ***************** ORDER_PHASE_PARSE_REQUEST **************** */
+
/**
* Parse the client request. Upon success,
* continue processing by calling parse_order().
@@ -3807,7 +4453,7 @@ merge_inventory (struct OrderContext *oc)
* @param[in,out] oc order context to process
*/
static void
-parse_request (struct OrderContext *oc)
+phase_parse_request (struct OrderContext *oc)
{
const json_t *ip = NULL;
const json_t *uuid = NULL;
@@ -4020,6 +4666,9 @@ parse_request (struct OrderContext *oc)
}
+/* ***************** Main handler **************** */
+
+
MHD_RESULT
TMH_private_post_orders (
const struct TMH_RequestHandler *rh,
@@ -4044,38 +4693,41 @@ TMH_private_post_orders (
switch (oc->phase)
{
case ORDER_PHASE_PARSE_REQUEST:
- parse_request (oc);
+ phase_parse_request (oc);
break;
case ORDER_PHASE_PARSE_ORDER:
- parse_order (oc);
+ phase_parse_order (oc);
break;
case ORDER_PHASE_PARSE_CHOICES:
- parse_choices (oc);
+ phase_parse_choices (oc);
break;
case ORDER_PHASE_MERGE_INVENTORY:
- merge_inventory (oc);
+ phase_merge_inventory (oc);
break;
case ORDER_PHASE_ADD_PAYMENT_DETAILS:
- add_payment_details (oc);
+ phase_add_payment_details (oc);
break;
case ORDER_PHASE_SET_EXCHANGES:
- if (set_exchanges (oc))
+ if (phase_set_exchanges (oc))
return MHD_YES;
break;
+ case ORDER_PHASE_SELECT_WIRE_METHOD:
+ phase_select_wire_method (oc);
+ break;
case ORDER_PHASE_SET_MAX_FEE:
- set_max_fee (oc);
+ phase_set_max_fee (oc);
break;
case ORDER_PHASE_SERIALIZE_ORDER:
- serialize_order (oc);
+ phase_serialize_order (oc);
break;
case ORDER_PHASE_CHECK_CONTRACT:
- check_contract (oc);
+ phase_check_contract (oc);
break;
case ORDER_PHASE_SALT_FORGETTABLE:
- salt_forgettable (oc);
+ phase_salt_forgettable (oc);
break;
case ORDER_PHASE_EXECUTE_ORDER:
- execute_order (oc);
+ phase_execute_order (oc);
break;
case ORDER_PHASE_FINISHED_MHD_YES:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
diff --git a/src/backenddb/merchant-0020.sql b/src/backenddb/merchant-0020.sql
@@ -26,6 +26,9 @@ SELECT _v.register_patch('merchant-0020', NULL, NULL);
SET search_path TO merchant;
+-- delete existing login tokens as we don't have a description, this
+-- logs out all users but should be safe as a migration.
+DELETE FROM merchant_login_tokens;
ALTER TABLE merchant_login_tokens
ADD description TEXT NOT NULL;
diff --git a/src/backenddb/pg_lookup_statistics_counter_by_interval.c b/src/backenddb/pg_lookup_statistics_counter_by_interval.c
@@ -155,7 +155,7 @@ TMH_PG_lookup_statistics_counter_by_interval (
struct LookupCounterStatisticsContext context = {
.cb = cb,
.cb_cls = cb_cls,
- /* Can be overwritten by the lookup_token_families_cb */
+ /* Can be overwritten by the lookup_statistics_counter_by_interval_cb */
.extract_failed = false,
.description = NULL
};