gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-merchant] branch master updated (829354b0 -> 28b80ac7)


From: gnunet
Subject: [taler-merchant] branch master updated (829354b0 -> 28b80ac7)
Date: Fri, 13 Oct 2023 21:16:55 +0200

This is an automated email from the git hooks/post-receive script.

grothoff pushed a change to branch master
in repository merchant.

    from 829354b0 -more key fixes
     new 98e316c8 -first hack at multicurrency support in merchant
     new 586342ce store expected currency as per configuration with exchange
     new a8b2456e return only accepted currencies from /config
     new a5f50083 work towards multi-currency support
     new 0f3490dc more work on multicurrency support: use checks everywhere...
     new 28b80ac7 add currency specification parsing to /config logic in 
libtalermerchant

The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/backend/kudos.conf                             |   4 -
 src/backend/taler-merchant-exchange.c              |  29 +++-
 src/backend/taler-merchant-httpd_config.c          |  10 +-
 src/backend/taler-merchant-httpd_exchanges.c       | 102 ++++++++++--
 src/backend/taler-merchant-httpd_exchanges.h       |  20 ++-
 src/backend/taler-merchant-httpd_get-orders-ID.c   | 135 +++++++++++----
 src/backend/taler-merchant-httpd_helper.c          |  15 +-
 .../taler-merchant-httpd_post-orders-ID-pay.c      | 181 ++++++++++++++-------
 .../taler-merchant-httpd_post-rewards-ID-pickup.c  |  71 ++++++--
 .../taler-merchant-httpd_post-using-templates.c    |  30 +++-
 .../taler-merchant-httpd_private-get-orders-ID.c   |  98 ++++++++++-
 .../taler-merchant-httpd_private-get-orders.c      |  17 +-
 .../taler-merchant-httpd_private-get-rewards-ID.c  |  22 +--
 ...aler-merchant-httpd_private-patch-products-ID.c |   5 +-
 ...-merchant-httpd_private-post-orders-ID-refund.c | 137 +++++++---------
 .../taler-merchant-httpd_private-post-orders.c     | 125 +++++++++++---
 .../taler-merchant-httpd_private-post-products.c   |   5 +-
 ...tpd_private-post-reserves-ID-authorize-reward.c |   5 +-
 .../taler-merchant-httpd_private-post-reserves.c   |   5 +-
 .../taler-merchant-httpd_private-post-templates.c  |   2 +-
 .../taler-merchant-httpd_private-post-transfers.c  |   5 +-
 src/backenddb/pg_authorize_reward.c                |  21 +++
 src/backenddb/pg_increase_refund.c                 |  16 ++
 src/backenddb/pg_insert_pickup.c                   |   7 +
 src/backenddb/plugin_merchantdb_postgres.c         |   7 +
 src/include/taler_merchant_service.h               |  17 +-
 src/include/taler_merchantdb_plugin.h              |   5 +
 src/lib/merchant_api_common.c                      |   2 +-
 src/lib/merchant_api_get_config.c                  |  40 ++++-
 src/testing/test_merchant_api.conf                 |   3 +
 src/testing/test_merchant_instance_auth.sh         |   3 +-
 src/testing/test_merchant_kyc.sh                   |  11 +-
 src/testing/test_merchant_order_creation.sh        |   6 +-
 src/testing/test_merchant_reserve_creation.sh      |   4 +-
 src/testing/test_template.conf                     |   3 +
 35 files changed, 854 insertions(+), 314 deletions(-)

diff --git a/src/backend/kudos.conf b/src/backend/kudos.conf
index 424209d0..71ed232a 100644
--- a/src/backend/kudos.conf
+++ b/src/backend/kudos.conf
@@ -1,9 +1,5 @@
-
-
 # Trust Taler project for "KUDOS" currency so that demos work out-of-the-box
 [merchant-exchange-kudos]
 EXCHANGE_BASE_URL = https://exchange.demo.taler.net/
 MASTER_KEY = JFX1NE38C65A5XT8VSNQXX7R7BBG4GNZ63F5T7Y6859V4J8KBKF0
-# If currency does not match [TALER] section, the exchange
-# will be ignored!
 CURRENCY = KUDOS
diff --git a/src/backend/taler-merchant-exchange.c 
b/src/backend/taler-merchant-exchange.c
index 4db8f92d..bc475031 100644
--- a/src/backend/taler-merchant-exchange.c
+++ b/src/backend/taler-merchant-exchange.c
@@ -613,14 +613,18 @@ check_wire_fee (struct Inquiry *w,
   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     break;
   }
-  if (0 <= TALER_amount_cmp (&fees.wire,
-                             wire_fee))
+  if ( (GNUNET_OK !=
+        TALER_amount_cmp_currency (&fees.wire,
+                                   wire_fee)) ||
+       (0 > TALER_amount_cmp (&fees.wire,
+                               wire_fee)) )
   {
+    GNUNET_break_op (0);
     GNUNET_free (wire_method);
-    return GNUNET_OK;   /* expected_fee >= wire_fee */
+    return GNUNET_SYSERR;   /* expected_fee >= wire_fee */
   }
   GNUNET_free (wire_method);
-  return GNUNET_SYSERR;
+  return GNUNET_OK;
 }
 
 
@@ -698,8 +702,14 @@ check_transfer (void *cls,
     GNUNET_break (0);
     return;   /* already had a serious issue; odd that we're called more than 
once as well... */
   }
-  if ( (0 != TALER_amount_cmp (amount_with_fee,
+  if ( (GNUNET_OK !=
+        TALER_amount_cmp_currency (amount_with_fee,
+                                   &ttd->coin_value)) ||
+       (0 != TALER_amount_cmp (amount_with_fee,
                                &ttd->coin_value)) ||
+       (GNUNET_OK !=
+        TALER_amount_cmp_currency (deposit_fee,
+                                   &ttd->coin_fee)) ||
        (0 != TALER_amount_cmp (deposit_fee,
                                &ttd->coin_fee)) )
   {
@@ -905,9 +915,12 @@ wire_transfer_cb (void *cls,
     return;
   }
 
-  if (0 !=
-      TALER_amount_cmp (&td->total_amount,
-                        &w->total))
+  if ( (GNUNET_OK !=
+        TALER_amount_cmp_currency (&td->total_amount,
+                                   &w->total)) ||
+       (0 !=
+        TALER_amount_cmp (&td->total_amount,
+                          &w->total)) )
   {
     GNUNET_break_op (0);
     update_transaction_status (w,
diff --git a/src/backend/taler-merchant-httpd_config.c 
b/src/backend/taler-merchant-httpd_config.c
index 461aeb06..f1075ff5 100644
--- a/src/backend/taler-merchant-httpd_config.c
+++ b/src/backend/taler-merchant-httpd_config.c
@@ -63,11 +63,11 @@ MH_handler_config (struct TMH_RequestHandler *rh,
     {
       const struct TALER_CurrencySpecification *cspec = &TMH_cspecs[i];
 
-      /* FIXME: filter by currencies with configured exchange? */
-      GNUNET_assert (0 ==
-                     json_object_set_new (specs,
-                                          cspec->currency,
-                                          TALER_CONFIG_currency_specs_to_json 
(cspec)));
+      if (TMH_test_exchange_configured_for_currency (cspec->currency))
+        GNUNET_assert (0 ==
+                       json_object_set_new (specs,
+                                            cspec->currency,
+                                            
TALER_CONFIG_currency_specs_to_json (cspec)));
     }
     response = TALER_MHD_MAKE_JSON_PACK (
       GNUNET_JSON_pack_string ("currency",
diff --git a/src/backend/taler-merchant-httpd_exchanges.c 
b/src/backend/taler-merchant-httpd_exchanges.c
index 22eb0f40..0ac2234c 100644
--- a/src/backend/taler-merchant-httpd_exchanges.c
+++ b/src/backend/taler-merchant-httpd_exchanges.c
@@ -37,6 +37,9 @@
 #define RETRY_BACKOFF_THRESHOLD GNUNET_TIME_relative_multiply ( \
     GNUNET_TIME_UNIT_SECONDS, 60)
 
+#define FAST_FAIL_THRESHOLD GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_SECONDS, 2)
+
 
 /**
  * Information we keep for a pending #MMH_EXCHANGES_keys4exchange() operation.
@@ -229,6 +232,11 @@ struct TMH_Exchange
    */
   char *url;
 
+  /**
+   * Currency offered by the exchange according to OUR configuration.
+   */
+  char *currency;
+
   /**
    * A connection to this exchange
    */
@@ -791,6 +799,21 @@ TMH_EXCHANGES_keys4exchange (
                                  exchange);
     return fo;
   }
+  if ( (NULL == exchange->conn) &&
+       (GNUNET_TIME_relative_cmp (
+          GNUNET_TIME_absolute_get_remaining (
+            exchange->first_retry),
+          >,
+          FAST_FAIL_THRESHOLD)) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Already waiting for `%skeys' for a while, failing query 
instantly\n",
+                exchange->url);
+    GNUNET_assert (NULL == fo->at);
+    fo->at = GNUNET_SCHEDULER_add_now (&return_keys,
+                                       fo);
+    return fo;
+  }
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Next /keys request scheduled for %s\n",
               GNUNET_TIME_absolute2s (
@@ -906,6 +929,7 @@ free_exchange_entry (struct TMH_Exchange *exchange)
   }
   GNUNET_assert (NULL == exchange->keys_head);
   GNUNET_assert (NULL == exchange->keys_tail);
+  GNUNET_free (exchange->currency);
   GNUNET_free (exchange->url);
   GNUNET_free (exchange);
 }
@@ -1113,6 +1137,20 @@ keys_mgmt_cb (void *cls,
     TALER_EXCHANGE_keys_decref (keys);
     return;
   }
+  if (NULL == exchange->currency)
+    exchange->currency = GNUNET_strdup (keys->currency);
+  if (0 != strcmp (exchange->currency,
+                     keys->currency))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "/keys response from `%s' is for currency `%s', but we 
expected `%s'\n",
+                exchange->url,
+                keys->currency,
+                exchange->currency);
+    fail_and_retry (exchange);
+    TALER_EXCHANGE_keys_decref (keys);
+    return;
+  }
   TMH_db->preflight (TMH_db->cls);
   for (unsigned int r = 0; r<MAX_RETRIES; r++)
   {
@@ -1259,6 +1297,26 @@ TMH_exchange_get_trusted (TMH_ExchangeCallback cb,
 }
 
 
+bool
+TMH_test_exchange_configured_for_currency (
+  const char *currency)
+{
+  for (const struct TMH_Exchange *exchange = exchange_head;
+       NULL != exchange;
+       exchange = exchange->next)
+  {
+    if (! exchange->trusted)
+      continue;
+    if (NULL == exchange->currency)
+      continue;
+    if (0 == strcmp (currency,
+                     exchange->currency))
+      return true;
+  }
+  return false;
+}
+
+
 /**
  * Function called on each configuration section. Finds sections
  * about exchanges, parses the entries.
@@ -1282,40 +1340,40 @@ accept_exchanges (void *cls,
                         "merchant-exchange-",
                         strlen ("merchant-exchange-")))
     return;
+  if (GNUNET_YES ==
+      GNUNET_CONFIGURATION_get_value_yesno (cfg,
+                                            section,
+                                            "DISABLED"))
+    return;
   if (GNUNET_OK !=
       GNUNET_CONFIGURATION_get_value_string (cfg,
                                              section,
-                                             "CURRENCY",
-                                             &currency))
+                                             "EXCHANGE_BASE_URL",
+                                             &url))
   {
     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                                section,
-                               "CURRENCY");
-    return;
-  }
-  if (0 != strcasecmp (currency,
-                       TMH_currency))
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Exchange given in section `%s' is for another currency. 
Skipping.\n",
-                section);
-    GNUNET_free (currency);
+                               "EXCHANGE_BASE_URL");
     return;
   }
-  GNUNET_free (currency);
   if (GNUNET_OK !=
       GNUNET_CONFIGURATION_get_value_string (cfg,
                                              section,
-                                             "EXCHANGE_BASE_URL",
-                                             &url))
+                                             "CURRENCY",
+                                             &currency))
   {
     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                                section,
-                               "EXCHANGE_BASE_URL");
+                               "CURRENCY");
+    GNUNET_free (url);
     return;
   }
   exchange = lookup_exchange (url);
   GNUNET_free (url);
+  if (NULL == exchange->currency)
+    exchange->currency = currency;
+  else
+    GNUNET_free (currency);
   if (GNUNET_OK ==
       GNUNET_CONFIGURATION_get_value_string (cfg,
                                              section,
@@ -1405,7 +1463,17 @@ update_exchange_keys (void *cls,
     GNUNET_break (0);
     return;
   }
-
+  if (NULL == exchange->currency)
+    exchange->currency = GNUNET_strdup (keys->currency);
+  if (0 != strcmp (keys->currency,
+                   exchange->currency))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "/keys cached in our database are for currency `%s', but we 
expected `%s'\n",
+                keys->currency,
+                exchange->currency);
+    return;
+  }
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Loaded /keys from database with %u accounts\n",
               keys->accounts_len);
diff --git a/src/backend/taler-merchant-httpd_exchanges.h 
b/src/backend/taler-merchant-httpd_exchanges.h
index d8202922..86c8374d 100644
--- a/src/backend/taler-merchant-httpd_exchanges.h
+++ b/src/backend/taler-merchant-httpd_exchanges.h
@@ -140,8 +140,7 @@ TMH_EXCHANGES_get_master_pub (
 
 
 /**
- * Lookup current wire fee by @a exchange_url and
- * @a wire_method.
+ * Lookup current wire fee by @a exchange_url and @a wire_method.
  *
  * @param exchange the exchange to check
  * @param wire_method wire method to lookup fee by
@@ -158,9 +157,8 @@ TMH_EXCHANGES_lookup_wire_fee (
 
 
 /**
- * Check if we would trust @a ex to deposit funds
- * into our account @a wm. Checks that both @a ex
- * is trusted and that @a ex allows wire transfers
+ * Check if we would trust @a ex to deposit funds into our account @a
+ * wm. Checks that both @a ex is trusted and that @a ex allows wire transfers
  * into the account given in @a wm.
  *
  * @param exchange the exchange to check
@@ -173,4 +171,16 @@ TMH_exchange_check_debit (
   const struct TMH_WireMethod *wm);
 
 
+/**
+ * Check if we support the given currency (by having an
+ * exchange configured with it).
+ *
+ * @param currency currency to check
+ * @return true if the currency is supported
+ */
+bool
+TMH_test_exchange_configured_for_currency (
+  const char *currency);
+
+
 #endif
diff --git a/src/backend/taler-merchant-httpd_get-orders-ID.c 
b/src/backend/taler-merchant-httpd_get-orders-ID.c
index 9c60f7a9..af5513d9 100644
--- a/src/backend/taler-merchant-httpd_get-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_get-orders-ID.c
@@ -119,6 +119,22 @@ struct GetOrderData
    */
   json_t *contract_terms;
 
+  /**
+   * Merchant base URL from @e contract_terms.
+   */
+  const char *merchant_base_url;
+
+  /**
+   * Public reorder URL from @e contract_terms.
+   * Could be NULL if contract does not have one.
+   */
+  const char *public_reorder_url;
+
+  /**
+   * Total amount in contract.
+   */
+  struct TALER_Amount contract_total;
+
   /**
    * Total refunds granted for this payment. Only initialized
    * if @e refunded is set to true.
@@ -180,6 +196,16 @@ struct GetOrderData
    */
   bool generate_html;
 
+  /**
+   * Did we parse the contract terms?
+   */
+  bool contract_parsed;
+
+  /**
+   * Set to true if the refunds found in the DB have
+   * a different currency then the main contract.
+   */
+  bool bad_refund_currency_in_db;
 };
 
 
@@ -316,6 +342,9 @@ suspend_god (struct GetOrderData *god)
     json_decref (god->contract_terms);
     god->fulfillment_url = NULL;
     god->contract_terms = NULL;
+    god->contract_parsed = false;
+    god->merchant_base_url = NULL;
+    god->public_reorder_url = NULL;
   }
   GNUNET_assert (! god->suspended);
   god->suspended = GNUNET_YES;
@@ -712,6 +741,16 @@ process_refunds_cb (void *cls,
               TALER_B2S (coin_pub),
               reason);
   god->refund_pending |= pending;
+  if ( (GNUNET_OK !=
+        TALER_amount_cmp_currency (&god->refund_taken,
+                                   refund_amount)) ||
+       (GNUNET_OK !=
+        TALER_amount_cmp_currency (&god->refund_amount,
+                                   refund_amount)) )
+  {
+    god->bad_refund_currency_in_db = true;
+    return;
+  }
   if (! pending)
   {
     GNUNET_assert (0 <=
@@ -767,7 +806,6 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
   bool contract_match = false;
   bool token_match = false;
   bool contract_available = false;
-  const char *merchant_base_url;
 
   (void) rh;
   if (NULL == god)
@@ -945,10 +983,9 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
   if (NULL != god->contract_terms)
   {
     contract_available = true;
-
-    if (GNUNET_YES == GNUNET_is_zero (&god->h_contract_terms))
+    if (GNUNET_YES ==
+        GNUNET_is_zero (&god->h_contract_terms))
     {
-
       if (GNUNET_OK !=
           TALER_JSON_contract_hash (god->contract_terms,
                                     &god->h_contract_terms))
@@ -959,11 +996,9 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
                                            
TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
                                            "contract terms");
       }
-
     }
     else
     {
-
       struct TALER_PrivateContractHashP h;
 
       if (GNUNET_OK !=
@@ -988,9 +1023,7 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
           TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
           NULL);
       }
-
     }
-
   }
 
   if (contract_available)
@@ -1040,21 +1073,46 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
   } /* end unclaimed order logic */
 
   GNUNET_assert (NULL != god->contract_terms);
-  merchant_base_url = json_string_value (json_object_get (god->contract_terms,
-                                                          
"merchant_base_url"));
-  if (NULL == merchant_base_url)
+  if (! god->contract_parsed)
   {
-    GNUNET_break (0);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       
TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
-                                       order_id);
+    struct GNUNET_JSON_Specification espec[] = {
+      TALER_JSON_spec_amount_any ("amount",
+                                  &god->contract_total),
+      GNUNET_JSON_spec_string ("merchant_base_url",
+                               &god->merchant_base_url),
+      GNUNET_JSON_spec_mark_optional (
+        GNUNET_JSON_spec_string ("fulfillment_url",
+                                 &god->fulfillment_url),
+        NULL),
+      GNUNET_JSON_spec_mark_optional (
+        GNUNET_JSON_spec_string ("public_reorder_url",
+                                 &god->public_reorder_url),
+        NULL),
+      GNUNET_JSON_spec_end ()
+    };
+    enum GNUNET_GenericReturnValue res;
+    const char *ename;
+    unsigned int eline;
+
+    res = GNUNET_JSON_parse (god->contract_terms,
+                             espec,
+                             &ename,
+                             &eline);
+    if (GNUNET_OK != res)
+    {
+      GNUNET_break (0);
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to parse contract %s in DB at field %s\n",
+                  order_id,
+                  ename);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+                                         order_id);
+    }
+    god->contract_parsed = true;
   }
 
-  if (NULL == god->fulfillment_url)
-    god->fulfillment_url = json_string_value (json_object_get (
-                                                god->contract_terms,
-                                                "fulfillment_url"));
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Token match: %d, contract_available: %d, contract match: %d, 
claimed: %d\n",
               token_match,
@@ -1085,15 +1143,11 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
   if (! (token_match ||
          contract_match) )
   {
-    const char *public_reorder_url;
 
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Neither claim token nor contract matched\n");
-    public_reorder_url = json_string_value (json_object_get (
-                                              god->contract_terms,
-                                              "public_reorder_url"));
     /* Client has no rights to this order */
-    if (NULL == public_reorder_url)
+    if (NULL == god->public_reorder_url)
     {
       /* We cannot give the client a new order, just fail */
       if (! GNUNET_is_zero (&god->h_contract_terms))
@@ -1135,7 +1189,7 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
       GNUNET_break (MHD_YES ==
                     MHD_add_response_header (reply,
                                              MHD_HTTP_HEADER_LOCATION,
-                                             public_reorder_url));
+                                             god->public_reorder_url));
       ret = MHD_queue_response (connection,
                                 MHD_HTTP_FOUND,
                                 reply);
@@ -1147,7 +1201,7 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
       connection,
       MHD_HTTP_ACCEPTED,
       GNUNET_JSON_pack_string ("public_reorder_url",
-                               public_reorder_url));
+                               god->public_reorder_url));
   }
 
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -1247,13 +1301,25 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
     }
   }
 
+  if ( (god->sc.awaiting_refund) &&
+       (GNUNET_OK !=
+        TALER_amount_cmp_currency (&god->contract_total,
+                                   &god->sc.refund_expected)) )
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_CONFLICT,
+                                       
TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+                                       god->contract_total.currency);
+  }
+
   /* At this point, we know the contract was paid. Let's check for
      refunds. First, clear away refunds found from previous invocations. */
   GNUNET_assert (GNUNET_OK ==
-                 TALER_amount_set_zero (TMH_currency,
+                 TALER_amount_set_zero (god->contract_total.currency,
                                         &god->refund_amount));
   GNUNET_assert (GNUNET_OK ==
-                 TALER_amount_set_zero (TMH_currency,
+                 TALER_amount_set_zero (god->contract_total.currency,
                                         &god->refund_taken));
   qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
                                         hc->instance->settings.id,
@@ -1268,7 +1334,14 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
                                        "lookup_refunds_detailed");
   }
-
+  if (god->bad_refund_currency_in_db)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                       "currency mix-up between contract price 
and refunds in database");
+  }
   if ( ((god->sc.awaiting_refund) &&
         ( (! god->refunded) ||
           (1 != TALER_amount_cmp (&god->refund_amount,
@@ -1308,7 +1381,7 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
       char *uri;
 
       GNUNET_assert (NULL != god->contract_terms);
-      uri = make_taler_refund_uri (merchant_base_url,
+      uri = make_taler_refund_uri (god->merchant_base_url,
                                    order_id);
       if (NULL == uri)
       {
diff --git a/src/backend/taler-merchant-httpd_helper.c 
b/src/backend/taler-merchant-httpd_helper.c
index 172cb0a1..3e525245 100644
--- a/src/backend/taler-merchant-httpd_helper.c
+++ b/src/backend/taler-merchant-httpd_helper.c
@@ -328,9 +328,8 @@ TMH_products_array_valid (const json_t *products)
                                  &unit),
         NULL),
       GNUNET_JSON_spec_mark_optional (
-        TALER_JSON_spec_amount ("price",
-                                TMH_currency,
-                                &price),
+        TALER_JSON_spec_amount_any ("price",
+                                    &price),
         NULL),
       GNUNET_JSON_spec_mark_optional (
         GNUNET_JSON_spec_string ("image",
@@ -414,6 +413,7 @@ bool
 TMH_template_contract_valid (const json_t *template_contract)
 {
   const char *summary;
+  const char *currency;
   struct TALER_Amount amount = { .value = 0};
   uint32_t minimum_age = 0;
   struct GNUNET_TIME_Relative pay_duration = { 0 };
@@ -423,9 +423,12 @@ TMH_template_contract_valid (const json_t 
*template_contract)
                                &summary),
       NULL),
     GNUNET_JSON_spec_mark_optional (
-      TALER_JSON_spec_amount ("amount",
-                              TMH_currency,
-                              &amount),
+      GNUNET_JSON_spec_string ("currency",
+                               &currency),
+      NULL),
+    GNUNET_JSON_spec_mark_optional (
+      TALER_JSON_spec_amount_any ("amount",
+                                  &amount),
       NULL),
     GNUNET_JSON_spec_uint32 ("minimum_age",
                              &minimum_age),
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c 
b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
index c99a6c64..9edc553c 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -399,6 +399,18 @@ struct PayContext
    */
   enum GNUNET_GenericReturnValue suspended;
 
+  /**
+   * Set to true if the deposit currency of a coin
+   * does not match the contract currency.
+   */
+  bool deposit_currency_mismatch;
+  
+  /**
+   * Set to true if the database contains a (bogus)
+   * refund for a different currency.
+   */
+  bool refund_currency_mismatch;
+  
 };
 
 
@@ -909,11 +921,13 @@ batch_deposit_transaction (const struct ExchangeGroup *eg,
 {
   const struct PayContext *pc = eg->pc;
   enum GNUNET_DB_QueryStatus qs;
-  struct TALER_Amount total_without_fees = { 0 };
+  struct TALER_Amount total_without_fees;
   uint64_t b_dep_serial;
   uint32_t off = 0;
-  bool found = false;
 
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (pc->amount.currency,
+                                        &total_without_fees));
   for (unsigned int i = 0; i<pc->coins_cnt; i++)
   {
     struct DepositConfirmation *dc = &pc->dc[i];
@@ -929,19 +943,10 @@ batch_deposit_transaction (const struct ExchangeGroup *eg,
                    TALER_amount_subtract (&amount_without_fees,
                                           &dc->cdd.amount,
                                           &dc->deposit_fee));
-    if (! found)
-    {
-      found = true;
-      total_without_fees = amount_without_fees;
-    }
-    else
-    {
-      GNUNET_assert (
-        0 <=
-        TALER_amount_add (&total_without_fees,
-                          &total_without_fees,
-                          &amount_without_fees));
-    }
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&total_without_fees,
+                                     &total_without_fees,
+                                     &amount_without_fees));
   }
   qs = TMH_db->insert_deposit_confirmation (
     TMH_db->cls,
@@ -958,13 +963,6 @@ batch_deposit_transaction (const struct ExchangeGroup *eg,
   if (qs <= 0)
     return qs; /* Entire batch already known or failure, we're done */
 
-  if (! found)
-  {
-    /* All coins already done, but the batch was not? Invariant violation! */
-    GNUNET_break (0);
-    return GNUNET_DB_STATUS_HARD_ERROR;
-  }
-
   for (unsigned int i = 0; i<pc->coins_cnt; i++)
   {
     struct DepositConfirmation *dc = &pc->dc[i];
@@ -1594,13 +1592,26 @@ check_coin_paid (void *cls,
          (0 !=
           strcmp (exchange_url,
                   dc->exchange_url)) ||
+         (GNUNET_OK !=
+          TALER_amount_cmp_currency (amount_with_fee,
+                                     &dc->cdd.amount)) ||
          (0 != TALER_amount_cmp (amount_with_fee,
                                  &dc->cdd.amount)) )
       continue; /* does not match, skip */
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "Deposit of coin `%s' already in our DB.\n",
                 TALER_B2S (coin_pub));
-
+    if ( (GNUNET_OK !=
+          TALER_amount_cmp_currency (&pc->total_paid,
+                                     amount_with_fee)) ||
+         (GNUNET_OK !=
+          TALER_amount_cmp_currency (&pc->total_fees_paid,
+                                     deposit_fee)) )
+    {
+      GNUNET_break_op (0);
+      pc->deposit_currency_mismatch = true;
+      break;
+    }
     GNUNET_assert (0 <=
                    TALER_amount_add (&pc->total_paid,
                                      &pc->total_paid,
@@ -1651,6 +1662,14 @@ check_coin_refunded (void *cls,
     if (0 != GNUNET_memcmp (coin_pub,
                             &dc->cdd.coin_pub))
       continue;
+    if (GNUNET_OK !=
+        TALER_amount_cmp_currency (&pc->total_refunded,
+                                   refund_amount))
+    {
+      GNUNET_break (0);
+      pc->refund_currency_mismatch = true;
+      break;
+    }
     GNUNET_assert (0 <=
                    TALER_amount_add (&pc->total_refunded,
                                      &pc->total_refunded,
@@ -1677,13 +1696,11 @@ check_payment_sufficient (struct PayContext *pc)
   struct TALER_Amount total_needed;
 
   if (0 == pc->coins_cnt)
-  {
-    return ((0 == pc->amount.value) &&
-            (0 == pc->amount.fraction));
-  }
-
-  total_wire_fee = pc->egs[0]->wire_fee;
-  for (unsigned int i = 1; i < pc->num_exchanges; i++)
+    return TALER_amount_is_zero (&pc->amount);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (pc->amount.currency,
+                                        &total_wire_fee));
+  for (unsigned int i = 0; i < pc->num_exchanges; i++)
   {
     if (GNUNET_OK !=
         TALER_amount_cmp_currency (&total_wire_fee,
@@ -1708,18 +1725,34 @@ check_payment_sufficient (struct PayContext *pc)
     }
   }
 
-  acc_fee = pc->dc[0].deposit_fee;
-  acc_amount = pc->dc[0].cdd.amount;
-
   /**
    * This loops calculates what are the deposit fee / total
    * amount with fee / and wire fee, for all the coins.
    */
-  for (unsigned int i = 1; i<pc->coins_cnt; i++)
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (pc->amount.currency,
+                                        &acc_fee));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (pc->amount.currency,
+                                        &acc_amount));
+  for (unsigned int i = 0; i<pc->coins_cnt; i++)
   {
     struct DepositConfirmation *dc = &pc->dc[i];
 
     GNUNET_assert (dc->found_in_db);
+    if ( (GNUNET_OK !=
+          TALER_amount_cmp_currency (&acc_fee,
+                                     &dc->deposit_fee)) ||
+         (GNUNET_OK !=
+          TALER_amount_cmp_currency (&acc_amount,
+                                     &dc->cdd.amount)) )
+    {
+      GNUNET_break_op (0);
+      resume_pay_with_error (pc,
+                             TALER_EC_GENERIC_CURRENCY_MISMATCH,
+                             dc->deposit_fee.currency);
+      return false;
+    }
     if ( (0 >
           TALER_amount_add (&acc_fee,
                             &dc->deposit_fee,
@@ -1778,7 +1811,6 @@ check_payment_sufficient (struct PayContext *pc)
     return false;
   }
 
-
   /* add wire fee to the total fees */
   if (0 >
       TALER_amount_add (&acc_fee,
@@ -2035,6 +2067,14 @@ execute_pay_transaction (struct PayContext *pc)
                              "lookup deposits");
       return;
     }
+    if (pc->deposit_currency_mismatch)
+    {
+      GNUNET_break_op (0);
+      resume_pay_with_error (pc,
+                             TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+                             pc->amount.currency);
+      return;
+    }
   }
 
 
@@ -2062,6 +2102,14 @@ execute_pay_transaction (struct PayContext *pc)
                              "lookup refunds");
       return;
     }
+    if (pc->refund_currency_mismatch)
+    {
+      TMH_db->rollback (TMH_db->cls);
+      resume_pay_with_error (pc,
+                             TALER_EC_GENERIC_DB_FETCH_FAILED,
+                             "refund currency in database does not match order 
currency");
+      return;
+    }
   }
 
   /* Check if there are coins that still need to be processed */
@@ -2256,9 +2304,8 @@ parse_pay (struct PayContext *pc)
                                    &dc->cdd.denom_sig),
         GNUNET_JSON_spec_fixed_auto ("h_denom",
                                      &dc->cdd.h_denom_pub),
-        TALER_JSON_spec_amount ("contribution",
-                                TMH_currency,
-                                &dc->cdd.amount),
+        TALER_JSON_spec_amount_any ("contribution",
+                                    &dc->cdd.amount),
         GNUNET_JSON_spec_string ("exchange_url",
                                  &exchange_url),
         /* if a minimum age was required, the minimum_age_sig and
@@ -2290,7 +2337,6 @@ parse_pay (struct PayContext *pc)
         GNUNET_break_op (0);
         return res;
       }
-
       for (unsigned int j = 0; j<coins_index; j++)
       {
         if (0 ==
@@ -2312,20 +2358,6 @@ parse_pay (struct PayContext *pc)
       dc->index = coins_index;
       dc->pc = pc;
 
-      if (0 !=
-          strcasecmp (dc->cdd.amount.currency,
-                      TMH_currency))
-      {
-        GNUNET_break_op (0);
-        return (MHD_YES ==
-                TALER_MHD_reply_with_error (pc->connection,
-                                            MHD_HTTP_CONFLICT,
-                                            TALER_EC_GENERIC_CURRENCY_MISMATCH,
-                                            TMH_currency))
-              ? GNUNET_NO
-              : GNUNET_SYSERR;
-      }
-
       /* Check the consistency of the (potential) age restriction
        * information. */
       if (dc->no_age_commitment != dc->no_minimum_age_sig)
@@ -2405,6 +2437,9 @@ deposit_paid_check (
          (0 ==
           strcmp (dci->exchange_url,
                   exchange_url)) &&
+         (GNUNET_YES ==
+          TALER_amount_cmp_currency (&dci->cdd.amount,
+                                     amount_with_fee)) &&
          (0 ==
           TALER_amount_cmp (&dci->cdd.amount,
                             amount_with_fee)) )
@@ -2620,16 +2655,14 @@ check_contract (struct PayContext *pc)
   {
     const char *fulfillment_url = NULL;
     struct GNUNET_JSON_Specification espec[] = {
-      TALER_JSON_spec_amount ("amount",
-                              TMH_currency,
-                              &pc->amount),
+      TALER_JSON_spec_amount_any ("amount",
+                                  &pc->amount),
       GNUNET_JSON_spec_mark_optional (
         GNUNET_JSON_spec_string ("fulfillment_url",
                                  &fulfillment_url),
         NULL),
-      TALER_JSON_spec_amount ("max_fee",
-                              TMH_currency,
-                              &pc->max_fee),
+      TALER_JSON_spec_amount_any ("max_fee",
+                                  &pc->max_fee),
       GNUNET_JSON_spec_timestamp ("timestamp",
                                   &pc->timestamp),
       GNUNET_JSON_spec_timestamp ("refund_deadline",
@@ -2661,6 +2694,36 @@ check_contract (struct PayContext *pc)
     }
   }
 
+  if (GNUNET_OK !=
+      TALER_amount_cmp_currency (&pc->max_fee,
+                                 &pc->amount))
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (pc->connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                       "'max_fee' in database does not match 
currency of contract price");
+  }
+
+  for (unsigned int i=0;i<pc->coins_cnt;i++)
+  {
+    struct DepositConfirmation *dc = &pc->dc[i];
+
+    if (GNUNET_OK !=
+        TALER_amount_cmp_currency (&dc->cdd.amount,
+                                   &pc->amount))
+    {
+      GNUNET_break_op (0);
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (pc->connection,
+                                          MHD_HTTP_CONFLICT,
+                                          
TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+                                          pc->amount.currency))
+        ? GNUNET_NO
+        : GNUNET_SYSERR;
+    }
+  }
+
   if (GNUNET_TIME_timestamp_cmp (pc->wire_transfer_deadline,
                                  <,
                                  pc->refund_deadline))
diff --git a/src/backend/taler-merchant-httpd_post-rewards-ID-pickup.c 
b/src/backend/taler-merchant-httpd_post-rewards-ID-pickup.c
index 05347c9f..1a21790d 100644
--- a/src/backend/taler-merchant-httpd_post-rewards-ID-pickup.c
+++ b/src/backend/taler-merchant-httpd_post-rewards-ID-pickup.c
@@ -496,6 +496,7 @@ compute_total_requested (void *cls,
                          struct TMH_Exchange *exchange)
 {
   struct PickupContext *pc = cls;
+  bool have_request = false;
 
   (void) exchange;
   pc->fo = NULL;
@@ -510,9 +511,6 @@ compute_total_requested (void *cls,
     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     return;
   }
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_amount_set_zero (TMH_currency,
-                                        &pc->total_requested));
   for (unsigned int i = 0; i<pc->planchets_length; i++)
   {
     struct TALER_PlanchetDetail *pd = &pc->planchets[i];
@@ -531,24 +529,50 @@ compute_total_requested (void *cls,
       TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
       return;
     }
-
-    if ( (GNUNET_YES !=
+    if (have_request)
+    {
+      if (GNUNET_YES !=
           TALER_amount_cmp_currency (&pc->total_requested,
-                                     &dpk->value)) ||
-         (0 >
+                                     &dpk->value))
+      {
+        pc->http_status = MHD_HTTP_BAD_REQUEST;
+        pc->response =
+          TALER_MHD_make_error (TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+                                "Must not mix currencies when picking up 
rewards");
+        MHD_resume_connection (pc->connection);
+        TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
+        return;
+      }
+      if (0 >
           TALER_amount_add (&pc->total_requested,
                             &pc->total_requested,
-                            &dpk->value)) )
+                            &dpk->value))
+      {
+        pc->http_status = MHD_HTTP_BAD_REQUEST;
+        pc->response =
+          TALER_MHD_make_error 
(TALER_EC_MERCHANT_REWARD_PICKUP_SUMMATION_FAILED,
+                                "Could not add up values to compute pickup 
total");
+        MHD_resume_connection (pc->connection);
+        TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
+        return;
+      }
+    }
+    else
     {
-      pc->http_status = MHD_HTTP_BAD_REQUEST;
-      pc->response =
-        TALER_MHD_make_error (TALER_EC_MERCHANT_REWARD_PICKUP_SUMMATION_FAILED,
-                              "Could not add up values to compute pickup 
total");
-      MHD_resume_connection (pc->connection);
-      TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
-      return;
+      pc->total_requested = dpk->value;
+      have_request = true;
     }
   }
+  if (! have_request)
+  {
+    pc->http_status = MHD_HTTP_BAD_REQUEST;
+    pc->response =
+      TALER_MHD_make_error (TALER_EC_MERCHANT_REWARD_PICKUP_SUMMATION_FAILED,
+                            "Empty request array not allowed");
+    MHD_resume_connection (pc->connection);
+    TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
+    return;
+  }
   pc->tr_initialized = true;
   MHD_resume_connection (pc->connection);
   TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
@@ -896,6 +920,23 @@ RETRY:
                                        
TALER_EC_MERCHANT_REWARD_PICKUP_HAS_EXPIRED,
                                        hc->infix);
   }
+  if (GNUNET_OK !=
+      TALER_amount_cmp_currency (&total_authorized,
+                                 &total_picked_up))
+  {
+    /* This could theoretically happen if the exchange changed
+       its currency between us approving the reward
+       and the client then picks it up with the new
+       exchange currency. And of course the backend
+       would have had to get the new /keys of the
+       exchange already as well. Very theoretical case. */
+    GNUNET_break_op (0);
+    TMH_db->rollback (TMH_db->cls);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_CONFLICT,
+                                       
TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+                                       "picked up amount does not use same 
currency as authorized amount");
+  }
   if (0 >
       TALER_amount_subtract (&total_remaining,
                              &total_authorized,
diff --git a/src/backend/taler-merchant-httpd_post-using-templates.c 
b/src/backend/taler-merchant-httpd_post-using-templates.c
index 67fc4b1e..cdaf917e 100644
--- a/src/backend/taler-merchant-httpd_post-using-templates.c
+++ b/src/backend/taler-merchant-httpd_post-using-templates.c
@@ -87,9 +87,8 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler 
*rh,
                                &summary),
       NULL),
     GNUNET_JSON_spec_mark_optional (
-      TALER_JSON_spec_amount ("amount",
-                              TMH_currency,
-                              &amount),
+      TALER_JSON_spec_amount_any ("amount",
+                                  &amount),
       &no_amount),
     GNUNET_JSON_spec_end ()
   };
@@ -161,7 +160,8 @@ TMH_post_using_templates_ID (const struct 
TMH_RequestHandler *rh,
 
   {
     /* template */
-    const char *tsummary;
+    const char *tsummary = NULL;
+    const char *tcurrency = NULL;
     uint32_t min_age;
     struct GNUNET_TIME_Relative pay_duration;
     struct TALER_Amount tamount;
@@ -172,9 +172,12 @@ TMH_post_using_templates_ID (const struct 
TMH_RequestHandler *rh,
                                  &tsummary),
         NULL),
       GNUNET_JSON_spec_mark_optional (
-        TALER_JSON_spec_amount ("amount",
-                                TMH_currency,
-                                &tamount),
+        GNUNET_JSON_spec_string ("currency",
+                                 &tcurrency),
+        NULL),
+      GNUNET_JSON_spec_mark_optional (
+        TALER_JSON_spec_amount_any ("amount",
+                                    &tamount),
         &no_tamount),
       GNUNET_JSON_spec_uint32 ("minimum_age",
                                &min_age),
@@ -218,6 +221,19 @@ TMH_post_using_templates_ID (const struct 
TMH_RequestHandler *rh,
         NULL);
     }
 
+    if ( (! no_amount) &&
+         (NULL != tcurrency) &&
+         (0 != strcmp (tcurrency,
+                       amount.currency)) )
+    {
+      GNUNET_JSON_parse_free (spec);
+      return TALER_MHD_reply_with_error (
+        connection,
+        MHD_HTTP_CONFLICT,
+        TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+        tcurrency);
+    }
+
     if (no_amount && no_tamount)
     {
       GNUNET_JSON_parse_free (spec);
diff --git a/src/backend/taler-merchant-httpd_private-get-orders-ID.c 
b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
index 20ed5fa7..98bf2ab8 100644
--- a/src/backend/taler-merchant-httpd_private-get-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2017-2022 Taler Systems SA
+  (C) 2017-2023 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -285,6 +285,19 @@ struct GetOrderRequestContext
    */
   bool transfer_status_requested;
 
+  /**
+   * Set to true if our database (incorrectly) has refunds
+   * in a different currency than the currency of the
+   * original payment for the order.
+   */
+  bool refund_currency_mismatch;
+
+  /**
+   * Set to true if our database (incorrectly) has deposits
+   * in a different currency than the currency of the
+   * original payment for the order.
+   */
+  bool deposit_currency_mismatch;
 };
 
 
@@ -484,6 +497,29 @@ deposit_get_cb (void *cls,
         return;
       }
       /* Compute total amount *wired* */
+      if ( (GNUNET_OK !=
+            TALER_amount_cmp_currency (
+              &gorc->deposits_total,
+              &dr->details.ok.coin_contribution)) ||
+           (GNUNET_OK !=
+            TALER_amount_cmp_currency (
+              &gorc->deposit_fees_total,
+              &tq->deposit_fee)) )
+      {
+        /* something very wrong in our database ... */
+        GNUNET_break (0);
+        gorc_report (gorc,
+                     TALER_EC_GENERIC_DB_FETCH_FAILED,
+                     &tq->coin_pub,
+                     NULL);
+        GNUNET_free (tq->exchange_url);
+        GNUNET_free (tq);
+        if (NULL == gorc->tq_head)
+          gorc_resume (gorc,
+                       0,
+                       TALER_EC_NONE);
+        return;
+      }
       if (0 >
           TALER_amount_add (&gorc->deposits_total,
                             &gorc->deposits_total,
@@ -775,12 +811,29 @@ process_refunds_cb (void *cls,
         GNUNET_memcmp (&tq->coin_pub,
                        coin_pub))
     {
+      if (GNUNET_OK !=
+          TALER_amount_cmp_currency (
+            &gorc->deposit_fees_total,
+            &tq->deposit_fee))
+      {
+        gorc->refund_currency_mismatch = true;
+        return;
+      }
+         
       GNUNET_assert (0 <=
                      TALER_amount_subtract (&gorc->deposit_fees_total,
                                             &gorc->deposit_fees_total,
                                             &tq->deposit_fee));
     }
   }
+  if (GNUNET_OK !=
+      TALER_amount_cmp_currency (
+        &gorc->refund_amount,
+        refund_amount))
+  {
+    gorc->refund_currency_mismatch = true;
+    return;
+  }
   GNUNET_assert (0 <=
                  TALER_amount_add (&gorc->refund_amount,
                                    &gorc->refund_amount,
@@ -817,6 +870,18 @@ process_transfer_details (
   json_t *wire_details = gorc->wire_details;
   struct TALER_Amount wired;
 
+  if ( (GNUNET_OK !=
+        TALER_amount_cmp_currency (&gorc->deposits_total,
+                                   deposit_value)) ||
+       (GNUNET_OK !=
+        TALER_amount_cmp_currency (&gorc->deposit_fees_total,
+                                   deposit_fee)) )
+  {
+    GNUNET_break (0);
+    gorc->deposit_currency_mismatch = true;
+    return;
+  }
+       
   /* Compute total amount *wired* */
   GNUNET_assert (0 <
                  TALER_amount_add (&gorc->deposits_total,
@@ -1039,9 +1104,8 @@ TMH_private_get_orders_ID (const struct 
TMH_RequestHandler *rh,
      from the contract terms! */
   {
     struct GNUNET_JSON_Specification spec[] = {
-      TALER_JSON_spec_amount ("amount",
-                              TMH_currency,
-                              &gorc->contract_amount),
+      TALER_JSON_spec_amount_any ("amount",
+                                  &gorc->contract_amount),
       GNUNET_JSON_spec_mark_optional (
         GNUNET_JSON_spec_string ("fulfillment_url",
                                  &gorc->fulfillment_url),
@@ -1235,10 +1299,10 @@ TMH_private_get_orders_ID (const struct 
TMH_RequestHandler *rh,
     /* suspend connection, wait for exchange to check wire transfer status 
there */
     gorc->transfer_status_requested = false;   /* only try ONCE */
     GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_set_zero (TMH_currency,
+                   TALER_amount_set_zero (gorc->contract_amount.currency,
                                           &gorc->deposits_total));
     GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_set_zero (TMH_currency,
+                   TALER_amount_set_zero (gorc->contract_amount.currency,
                                           &gorc->deposit_fees_total));
     TMH_db->lookup_deposits_by_order (TMH_db->cls,
                                       gorc->order_serial,
@@ -1317,7 +1381,7 @@ TMH_private_get_orders_ID (const struct 
TMH_RequestHandler *rh,
   /* Accumulate refunds, if any. */
   {
     GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_set_zero (TMH_currency,
+                   TALER_amount_set_zero (gorc->contract_amount.currency,
                                           &gorc->refund_amount));
     qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
                                           hc->instance->settings.id,
@@ -1333,6 +1397,14 @@ TMH_private_get_orders_ID (const struct 
TMH_RequestHandler *rh,
                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
                                        "detailed refunds");
   }
+  if (gorc->refund_currency_mismatch)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                       "refunds in different currency than 
original order price");
+  }
 
   /* Generate final reply, including wire details if we have them */
   {
@@ -1340,10 +1412,10 @@ TMH_private_get_orders_ID (const struct 
TMH_RequestHandler *rh,
     char *order_status_url;
 
     GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_set_zero (TMH_currency,
+                   TALER_amount_set_zero (gorc->contract_amount.currency,
                                           &gorc->deposits_total));
     GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_set_zero (TMH_currency,
+                   TALER_amount_set_zero (gorc->contract_amount.currency,
                                           &gorc->deposit_fees_total));
     qs = TMH_db->lookup_transfer_details_by_order (TMH_db->cls,
                                                    gorc->order_serial,
@@ -1357,6 +1429,14 @@ TMH_private_get_orders_ID (const struct 
TMH_RequestHandler *rh,
                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
                                          "transfer details");
     }
+    if (gorc->deposit_currency_mismatch)
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                         "deposits in different currency than 
original order price");
+    }
 
     if (! wired)
     {
diff --git a/src/backend/taler-merchant-httpd_private-get-orders.c 
b/src/backend/taler-merchant-httpd_private-get-orders.c
index 4998c029..0e0511cc 100644
--- a/src/backend/taler-merchant-httpd_private-get-orders.c
+++ b/src/backend/taler-merchant-httpd_private-get-orders.c
@@ -251,6 +251,16 @@ process_refunds_cb (void *cls,
 {
   struct TALER_Amount *total_refund_amount = cls;
 
+  if (GNUNET_OK !=
+      TALER_amount_cmp_currency (total_refund_amount,
+                                 refund_amount))
+  {
+    /* Database error, refunds in mixed currency in DB. Not OK! */
+    /* FIXME: we may want to return DB error to the client instead of just
+       ignoring the refund. */
+    GNUNET_break (0);
+    return;
+  }
   GNUNET_assert (0 <=
                  TALER_amount_add (total_refund_amount,
                                    total_refund_amount,
@@ -361,9 +371,8 @@ add_order (void *cls,
   {
     struct GNUNET_TIME_Timestamp rd;
     struct GNUNET_JSON_Specification spec[] = {
-      TALER_JSON_spec_amount ("amount",
-                              TMH_currency,
-                              &order_amount),
+      TALER_JSON_spec_amount_any ("amount",
+                                  &order_amount),
       GNUNET_JSON_spec_timestamp ("refund_deadline",
                                   &rd),
       GNUNET_JSON_spec_string ("summary",
@@ -389,7 +398,7 @@ add_order (void *cls,
       struct TALER_Amount refund_amount;
 
       GNUNET_assert (GNUNET_OK ==
-                     TALER_amount_set_zero (TMH_currency,
+                     TALER_amount_set_zero (order_amount.currency,
                                             &refund_amount));
       qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
                                             po->instance_id,
diff --git a/src/backend/taler-merchant-httpd_private-get-rewards-ID.c 
b/src/backend/taler-merchant-httpd_private-get-rewards-ID.c
index 78b6c2d0..a20be319 100644
--- a/src/backend/taler-merchant-httpd_private-get-rewards-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-rewards-ID.c
@@ -93,6 +93,10 @@ struct RewardContext
    */
   bool fpu;
 
+  /**
+   * True if @e min_amount was provided.
+   */
+  bool have_min_amount;
 };
 
 
@@ -230,9 +234,6 @@ TMH_private_get_rewards_ID (const struct TMH_RequestHandler 
*rh,
         ? 0 == strcasecmp (pstr, "yes")
         : false;
     }
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_set_zero (TMH_currency,
-                                          &tc->min_amount));
     {
       const char *min_amount;
 
@@ -251,16 +252,7 @@ TMH_private_get_rewards_ID (const struct 
TMH_RequestHandler *rh,
                                              
TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                              "min_amount");
         }
-        if (0 !=
-            strcasecmp (tc->min_amount.currency,
-                        TMH_currency))
-        {
-          GNUNET_break_op (0);
-          return TALER_MHD_reply_with_error (connection,
-                                             MHD_HTTP_BAD_REQUEST,
-                                             
TALER_EC_GENERIC_CURRENCY_MISMATCH,
-                                             TMH_currency);
-        }
+        tc->have_min_amount = true;
       }
     }
     TALER_MHD_parse_request_timeout (connection,
@@ -336,6 +328,10 @@ TMH_private_get_rewards_ID (const struct 
TMH_RequestHandler *rh,
                                           expiration.abs_time);
   if ( (NULL != tc->eh) &&
        (GNUNET_TIME_absolute_is_future (tc->timeout)) &&
+       (tc->have_min_amount) &&
+       (GNUNET_YES ==
+        TALER_amount_cmp_currency (&tc->min_amount,
+                                   &total_picked_up)) &&
        (1 == TALER_amount_cmp (&tc->min_amount,
                                &total_picked_up)) )
   {
diff --git a/src/backend/taler-merchant-httpd_private-patch-products-ID.c 
b/src/backend/taler-merchant-httpd_private-patch-products-ID.c
index c4ba755b..7bc327cd 100644
--- a/src/backend/taler-merchant-httpd_private-patch-products-ID.c
+++ b/src/backend/taler-merchant-httpd_private-patch-products-ID.c
@@ -125,9 +125,8 @@ TMH_private_patch_products_ID (const struct 
TMH_RequestHandler *rh,
       NULL),
     GNUNET_JSON_spec_string ("unit",
                              (const char **) &pd.unit),
-    TALER_JSON_spec_amount ("price",
-                            TMH_currency,
-                            &pd.price),
+    TALER_JSON_spec_amount_any ("price",
+                                &pd.price),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_string ("image",
                                (const char **) &pd.image),
diff --git a/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c 
b/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
index fa98fc1a..58fa96f4 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
@@ -121,9 +121,8 @@ TMH_private_post_orders_ID_refund (const struct 
TMH_RequestHandler *rh,
   struct TALER_Amount refund;
   const char *reason;
   struct GNUNET_JSON_Specification spec[] = {
-    TALER_JSON_spec_amount ("refund",
-                            TMH_currency,
-                            &refund),
+    TALER_JSON_spec_amount_any ("refund",
+                                &refund),
     GNUNET_JSON_spec_string ("reason",
                              &reason),
     GNUNET_JSON_spec_end ()
@@ -133,6 +132,20 @@ TMH_private_post_orders_ID_refund (const struct 
TMH_RequestHandler *rh,
   json_t *contract_terms;
   struct GNUNET_TIME_Timestamp timestamp;
 
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     hc->request_body,
+                                     spec);
+    if (GNUNET_OK != res)
+    {
+      return (GNUNET_NO == res)
+             ? MHD_YES
+             : MHD_NO;
+    }
+  }
+
   {
     enum GNUNET_DB_QueryStatus qs;
     uint64_t order_serial;
@@ -146,9 +159,34 @@ TMH_private_post_orders_ID_refund (const struct 
TMH_RequestHandler *rh,
                                         &order_serial,
                                         &paid,
                                         NULL);
-    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+    {
+      if (qs < 0)
+      {
+        GNUNET_break (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                           "lookup_contract_terms");
+      }
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         
TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
+                                         hc->infix);
+    }
+    if (GNUNET_OK !=
+        TALER_JSON_contract_hash (contract_terms,
+                                  &h_contract))
     {
-      struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_break (0);
+      json_decref (contract_terms);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+                                         "Could not hash contract terms");
+    }
+    {
+      struct GNUNET_JSON_Specification cspec[] = {
         GNUNET_JSON_spec_timestamp ("refund_deadline",
                                     &refund_deadline),
         GNUNET_JSON_spec_timestamp ("timestamp",
@@ -158,11 +196,10 @@ TMH_private_post_orders_ID_refund (const struct 
TMH_RequestHandler *rh,
 
       if (GNUNET_YES !=
           GNUNET_JSON_parse (contract_terms,
-                             spec,
+                             cspec,
                              NULL, NULL))
       {
         GNUNET_break (0);
-        GNUNET_JSON_parse_free (spec);
         json_decref (contract_terms);
         return TALER_MHD_reply_with_error (
           connection,
@@ -189,28 +226,6 @@ TMH_private_post_orders_ID_refund (const struct 
TMH_RequestHandler *rh,
            wire the funds, so we will try to give the refund anyway */
       }
     }
-    else
-    {
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_NOT_FOUND,
-                                         
TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
-                                         hc->infix);
-    }
-  }
-
-  {
-    enum GNUNET_GenericReturnValue res;
-
-    res = TALER_MHD_parse_json_data (connection,
-                                     hc->request_body,
-                                     spec);
-    if (GNUNET_OK != res)
-    {
-      json_decref (contract_terms);
-      return (GNUNET_NO == res)
-             ? MHD_YES
-             : MHD_NO;
-    }
   }
 
   TMH_db->preflight (TMH_db->cls);
@@ -294,6 +309,14 @@ TMH_private_post_orders_ID_refund (const struct 
TMH_RequestHandler *rh,
 
   switch (rs)
   {
+  case TALER_MERCHANTDB_RS_BAD_CURRENCY:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Refund amount %s is not in the currency of the original 
payment\n",
+                TALER_amount2s (&refund));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_CONFLICT,
+                                       
TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+                                       "Order was paid in a different 
currency");
   case TALER_MERCHANTDB_RS_TOO_HIGH:
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                 "Refusing refund amount %s that is larger than original 
payment\n",
@@ -309,53 +332,19 @@ TMH_private_post_orders_ID_refund (const struct 
TMH_RequestHandler *rh,
                                        TALER_EC_GENERIC_DB_COMMIT_FAILED,
                                        NULL);
   case TALER_MERCHANTDB_RS_NO_SUCH_ORDER:
-    {
-      /* We know the order exists from the
-         "lookup_contract_terms" at the beginning;
-         so if we get 'no such order' here, it
-         must be read as "no PAID order" */
-      return TALER_MHD_reply_with_error (
-        connection,
-        MHD_HTTP_CONFLICT,
-        TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_ORDER_UNPAID,
-        hc->infix);
-    }
+    /* We know the order exists from the
+       "lookup_contract_terms" at the beginning;
+       so if we get 'no such order' here, it
+       must be read as "no PAID order" */
+    return TALER_MHD_reply_with_error (
+      connection,
+      MHD_HTTP_CONFLICT,
+      TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_ORDER_UNPAID,
+      hc->infix);
   case TALER_MERCHANTDB_RS_SUCCESS:
-    {
-      enum GNUNET_DB_QueryStatus qs;
-      json_t *contract_terms;
-      uint64_t order_serial;
-      bool paid;
-
-      qs = TMH_db->lookup_contract_terms (TMH_db->cls,
-                                          hc->instance->settings.id,
-                                          hc->infix,
-                                          &contract_terms,
-                                          &order_serial,
-                                          &paid,
-                                          NULL);
-      if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
-      {
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_NOT_FOUND,
-                                           
TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
-                                           hc->infix);
-      }
-      if (GNUNET_OK !=
-          TALER_JSON_contract_hash (contract_terms,
-                                    &h_contract))
-      {
-        GNUNET_break (0);
-        json_decref (contract_terms);
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                           
TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
-                                           "Could not hash contract terms");
-      }
-      json_decref (contract_terms);
-    }
+    /* continued below */
     break;
-  }
+  } /* end switch */
 
   {
     struct GNUNET_TIME_Timestamp timestamp;
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c 
b/src/backend/taler-merchant-httpd_private-post-orders.c
index 17ddab67..7d9bc4f0 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -38,6 +38,11 @@
  */
 #define MAX_RETRIES 3
 
+/**
+ * How long do we wait for /keys from the exchange?
+ */
+#define KEYS_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 
2)
+
 /**
  * What is the label under which we find/place the merchant's
  * jurisdiction in the locations list by default?
@@ -124,6 +129,11 @@ struct RekeyExchange
    */
   struct TMH_EXCHANGES_KeysOperation *fo;
 
+  /**
+   * Timeout task handle.
+   */
+  struct GNUNET_SCHEDULER_Task *tx;
+ 
 };
 
 
@@ -419,6 +429,7 @@ clean_order (void *cls)
                                  oc->pending_reload_tail,
                                  rx);
     TMH_EXCHANGES_keys4exchange_cancel (rx->fo);
+    GNUNET_SCHEDULER_cancel (rx->tx);
     GNUNET_free (rx->url);
     GNUNET_free (rx);
   }
@@ -883,8 +894,6 @@ update_stefan (struct OrderContext *oc,
 {
   struct TALER_Amount net;
 
-  if (0 == keys->num_denom_keys)
-    sleep (600);
   if (GNUNET_SYSERR !=
       TALER_EXCHANGE_keys_stefan_b2n (keys,
                                       &oc->brutto,
@@ -965,6 +974,28 @@ get_acceptable (void *cls,
 }
 
 
+/**
+ * Exchange `/keys` processing is done, resume handling
+ * the order.
+ *
+ * @param[in,out] oc context to resume
+ */
+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->exchanges));
+  GNUNET_assert (GNUNET_YES == oc->suspended);
+  GNUNET_CONTAINER_DLL_remove (oc_head,
+                               oc_tail,
+                               oc);
+  oc->suspended = GNUNET_NO;
+  MHD_resume_connection (oc->connection);
+  TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
+}
+
+
 /**
  * Function called with the result of a #TMH_EXCHANGES_keys4exchange()
  * operation.
@@ -985,13 +1016,15 @@ keys_cb (
     &oc->hc->instance->settings;
 
   rx->fo = NULL;
+  GNUNET_SCHEDULER_cancel (rx->tx);
+  rx->tx = NULL;
   GNUNET_CONTAINER_DLL_remove (oc->pending_reload_head,
                                oc->pending_reload_tail,
                                rx);
   if (NULL == keys)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Failed to download %s/keys\n",
+                "Failed to download %skeys\n",
                 rx->url);
   }
   else
@@ -1001,24 +1034,41 @@ keys_cb (
           TALER_amount_is_valid (&oc->max_fee)) )
       update_stefan (oc,
                      keys);
+    get_acceptable (oc,
+                    rx->url,
+                    exchange);
   }
-  get_acceptable (oc,
-                  rx->url,
-                  exchange);
   GNUNET_free (rx->url);
   GNUNET_free (rx);
   if (NULL != oc->pending_reload_head)
     return;
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Resuming order processing after /keys downloads (now have %u 
accounts)\n",
-              (unsigned int) json_array_size (oc->exchanges));
-  GNUNET_assert (GNUNET_YES == oc->suspended);
-  GNUNET_CONTAINER_DLL_remove (oc_head,
-                               oc_tail,
-                               oc);
-  oc->suspended = GNUNET_NO;
-  MHD_resume_connection (oc->connection);
-  TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
+  resume_with_keys (oc);
+}
+
+
+/**
+ * A `/keys` request to an exchange is taking too
+ * long, ignore the exchange for now.
+ *
+ * @param cls a `struct RekeyExchange *`
+ */
+static void
+keys_timeout (void *cls)
+{
+  struct RekeyExchange *rx = cls;
+  struct OrderContext *oc = rx->oc;
+
+  rx->tx = NULL;
+  TMH_EXCHANGES_keys4exchange_cancel (rx->fo);
+  rx->fo = NULL;
+  GNUNET_CONTAINER_DLL_remove (oc->pending_reload_head,
+                               oc->pending_reload_tail,
+                               rx);
+  GNUNET_free (rx->url);
+  GNUNET_free (rx);
+  if (NULL != oc->pending_reload_head)
+    return;
+  resume_with_keys (oc);
 }
 
 
@@ -1052,6 +1102,10 @@ get_exchange_keys (void *cls,
                                         oc->forced_reload,
                                         &keys_cb,
                                         rx);
+  rx->tx
+    = GNUNET_SCHEDULER_add_delayed (KEYS_TIMEOUT,
+                                    &keys_timeout,
+                                    rx);
 }
 
 
@@ -1078,7 +1132,7 @@ set_max_fee (struct OrderContext *oc)
       stefan = oc->max_stefan_fee;
     else
       GNUNET_assert (GNUNET_OK ==
-                     TALER_amount_set_zero (TMH_currency,
+                     TALER_amount_set_zero (oc->brutto.currency,
                                             &stefan));
     GNUNET_assert (0 ==
                    json_object_set_new (
@@ -1086,7 +1140,6 @@ set_max_fee (struct OrderContext *oc)
                      "max_fee",
                      TALER_JSON_from_amount (&stefan)));
   }
-
   oc->phase++;
 }
 
@@ -1185,6 +1238,7 @@ patch_order (struct OrderContext *oc)
    * mostly because in GNUnet relative times can't
    * be negative.  */
   struct GNUNET_TIME_Relative auto_refund;
+  bool no_fee;
   struct GNUNET_JSON_Specification spec[] = {
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_string ("merchant_base_url",
@@ -1198,9 +1252,8 @@ patch_order (struct OrderContext *oc)
       GNUNET_JSON_spec_string ("order_id",
                                &order_id),
       NULL),
-    TALER_JSON_spec_amount ("amount",
-                            TMH_currency,
-                            &oc->brutto),
+    TALER_JSON_spec_amount_any ("amount",
+                                &oc->brutto),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_string ("fulfillment_url",
                                &fulfillment_url),
@@ -1222,10 +1275,9 @@ patch_order (struct OrderContext *oc)
                                   &wire_deadline),
       NULL),
     GNUNET_JSON_spec_mark_optional (
-      TALER_JSON_spec_amount ("max_fee",
-                              TMH_currency,
-                              &oc->max_fee),
-      NULL),
+      TALER_JSON_spec_amount_any ("max_fee",
+                                  &oc->max_fee),
+      &no_fee),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_timestamp ("delivery_date",
                                   &delivery_date),
@@ -1252,6 +1304,29 @@ patch_order (struct OrderContext *oc)
                      ret);
     return;
   }
+  if (! TMH_test_exchange_configured_for_currency (oc->brutto.currency))
+  {
+    GNUNET_break_op (0);
+    GNUNET_JSON_parse_free (spec);
+    reply_with_error (oc,
+                      MHD_HTTP_BAD_REQUEST,
+                      TALER_EC_GENERIC_CURRENCY_MISMATCH,
+                      "no trusted exchange for this currency");
+    return;
+  }
+  if ( (! no_fee) &&
+       (GNUNET_OK !=
+        TALER_amount_cmp_currency (&oc->brutto,
+                                   &oc->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;
+  }
 
   /* Add order_id if it doesn't exist. */
   if (NULL == order_id)
diff --git a/src/backend/taler-merchant-httpd_private-post-products.c 
b/src/backend/taler-merchant-httpd_private-post-products.c
index 0c20cdac..3cad91a9 100644
--- a/src/backend/taler-merchant-httpd_private-post-products.c
+++ b/src/backend/taler-merchant-httpd_private-post-products.c
@@ -93,9 +93,8 @@ TMH_private_post_products (const struct TMH_RequestHandler 
*rh,
       NULL),
     GNUNET_JSON_spec_string ("unit",
                              (const char **) &pd.unit),
-    TALER_JSON_spec_amount ("price",
-                            TMH_currency,
-                            &pd.price),
+    TALER_JSON_spec_amount_any ("price",
+                                &pd.price),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_string ("image",
                                (const char **) &pd.image),
diff --git 
a/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-reward.c 
b/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-reward.c
index a859ece8..0d102a2b 100644
--- 
a/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-reward.c
+++ 
b/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-reward.c
@@ -51,9 +51,8 @@ authorize_reward (const struct TMH_RequestHandler *rh,
   struct TALER_Amount amount;
   {
     struct GNUNET_JSON_Specification spec[] = {
-      TALER_JSON_spec_amount ("amount",
-                              TMH_currency,
-                              &amount),
+      TALER_JSON_spec_amount_any ("amount",
+                                  &amount),
       GNUNET_JSON_spec_string ("justification",
                                &justification),
       GNUNET_JSON_spec_string ("next_url",
diff --git a/src/backend/taler-merchant-httpd_private-post-reserves.c 
b/src/backend/taler-merchant-httpd_private-post-reserves.c
index db40b017..027e2996 100644
--- a/src/backend/taler-merchant-httpd_private-post-reserves.c
+++ b/src/backend/taler-merchant-httpd_private-post-reserves.c
@@ -309,9 +309,8 @@ TMH_private_post_reserves (const struct TMH_RequestHandler 
*rh,
                                  &rc->exchange_url),
         GNUNET_JSON_spec_string ("wire_method",
                                  &rc->wire_method),
-        TALER_JSON_spec_amount ("initial_balance",
-                                TMH_currency,
-                                &rc->initial_balance),
+        TALER_JSON_spec_amount_any ("initial_balance",
+                                    &rc->initial_balance),
         GNUNET_JSON_spec_end ()
       };
       enum GNUNET_GenericReturnValue res;
diff --git a/src/backend/taler-merchant-httpd_private-post-templates.c 
b/src/backend/taler-merchant-httpd_private-post-templates.c
index 4a5d8133..a064769c 100644
--- a/src/backend/taler-merchant-httpd_private-post-templates.c
+++ b/src/backend/taler-merchant-httpd_private-post-templates.c
@@ -189,7 +189,7 @@ TMH_private_post_templates (const struct TMH_RequestHandler 
*rh,
     /* idempotency check: is etp == tp? */
     {
       bool eq;
-      
+
       eq = templates_equal (&tp,
                             &etp);
       TALER_MERCHANTDB_template_details_free (&etp);
diff --git a/src/backend/taler-merchant-httpd_private-post-transfers.c 
b/src/backend/taler-merchant-httpd_private-post-transfers.c
index 2d99f35e..11f76e29 100644
--- a/src/backend/taler-merchant-httpd_private-post-transfers.c
+++ b/src/backend/taler-merchant-httpd_private-post-transfers.c
@@ -45,9 +45,8 @@ TMH_private_post_transfers (const struct TMH_RequestHandler 
*rh,
   struct TALER_WireTransferIdentifierRawP wtid;
   struct TALER_Amount amount;
   struct GNUNET_JSON_Specification spec[] = {
-    TALER_JSON_spec_amount ("credit_amount",
-                            TMH_currency,
-                            &amount),
+    TALER_JSON_spec_amount_any ("credit_amount",
+                                &amount),
     GNUNET_JSON_spec_fixed_auto ("wtid",
                                  &wtid),
     GNUNET_JSON_spec_string ("payto_uri",
diff --git a/src/backenddb/pg_authorize_reward.c 
b/src/backenddb/pg_authorize_reward.c
index 6ebf4796..5038f4fa 100644
--- a/src/backenddb/pg_authorize_reward.c
+++ b/src/backenddb/pg_authorize_reward.c
@@ -120,6 +120,19 @@ lookup_reserve_for_reward_cb (void *cls,
       lac->ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
       return;
     }
+    if ( (GNUNET_YES !=
+          TALER_amount_cmp_currency (&initial_balance,
+                                     &committed_amount)) ||
+         (GNUNET_YES !=
+          TALER_amount_cmp_currency (&initial_balance,
+                                     &lac->required_amount)) )
+    {
+      /* insufficient balance */
+      if (lac->ok)
+        continue;  /* got another reserve */
+      lac->ec = TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH;
+      continue;
+    }
     if (0 >
         TALER_amount_subtract (&remaining,
                                &initial_balance,
@@ -342,6 +355,14 @@ RETRY:
       TMH_PG_rollback (pg);
       return TALER_EC_GENERIC_DB_INVARIANT_FAILURE;
     }
+    if (GNUNET_YES !=
+        TALER_amount_cmp_currency (&remaining,
+                                   amount))
+    {
+      TMH_PG_rollback (pg);
+      return TALER_EC_GENERIC_CURRENCY_MISMATCH;
+    }
+
     if (0 >
         TALER_amount_cmp (&remaining,
                           amount))
diff --git a/src/backenddb/pg_increase_refund.c 
b/src/backenddb/pg_increase_refund.c
index d274b1e9..eef7adc6 100644
--- a/src/backenddb/pg_increase_refund.c
+++ b/src/backenddb/pg_increase_refund.c
@@ -91,6 +91,14 @@ process_refund_cb (void *cls,
       ictx->err = true;
       return;
     }
+    if (GNUNET_OK !=
+        TALER_amount_cmp_currency (&ictx->refunded_amount,
+                                   &acc))
+    {
+      GNUNET_break (0);
+      ictx->err = true;
+      return;
+    }
     if (0 >
         TALER_amount_add (&ictx->refunded_amount,
                           &ictx->refunded_amount,
@@ -222,6 +230,14 @@ process_deposits_for_refund_cb (
       return;
     }
 
+    if (0 != strcmp (rcd[i].deposited_with_fee.currency,
+                     ctx->refund->currency))
+    {
+      GNUNET_break_op (0);
+      ctx->rs = TALER_MERCHANTDB_RS_BAD_CURRENCY;
+      return;
+    }
+
     {
       enum GNUNET_DB_QueryStatus ires;
       struct GNUNET_PQ_QueryParam params[] = {
diff --git a/src/backenddb/pg_insert_pickup.c b/src/backenddb/pg_insert_pickup.c
index 73b97c6d..4094d748 100644
--- a/src/backenddb/pg_insert_pickup.c
+++ b/src/backenddb/pg_insert_pickup.c
@@ -134,6 +134,13 @@ TMH_PG_insert_pickup (
       if (0 > qs)
         return qs;
     }
+    if (GNUNET_OK !=
+        TALER_amount_cmp_currency (&reserve_picked_up,
+                                   total_requested))
+    {
+      GNUNET_break (0);
+      return GNUNET_DB_STATUS_HARD_ERROR;
+    }
     if (0 >=
         TALER_amount_add (&reserve_picked_up,
                           &reserve_picked_up,
diff --git a/src/backenddb/plugin_merchantdb_postgres.c 
b/src/backenddb/plugin_merchantdb_postgres.c
index 30def849..c2c99cd1 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -1172,6 +1172,13 @@ postgres_lookup_transfer (
   {
     *have_exchange_sig = ! no_sig;
     *verified = (0 != verified8);
+    if (GNUNET_OK !=
+        TALER_amount_cmp_currency (&credit_amount,
+                                   wire_fee))
+    {
+      GNUNET_break (0);
+      return GNUNET_DB_STATUS_HARD_ERROR;
+    }
     if ( (! no_sig) &&
          (0 >
           TALER_amount_add (total_amount,
diff --git a/src/include/taler_merchant_service.h 
b/src/include/taler_merchant_service.h
index 6d79d45e..b3e15025 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -211,7 +211,7 @@ struct TALER_MERCHANT_RefundUriData
  * @param[out] parse_data data extracted from the URI. Must be free'd.
  * @return #GNUNET_SYSERR if @e refund_uri is malformed, #GNUNET_OK otherwise.
  */
-int
+enum GNUNET_GenericReturnValue
 TALER_MERCHANT_parse_refund_uri (
   const char *refund_uri,
   struct TALER_MERCHANT_RefundUriData *parse_data);
@@ -288,7 +288,8 @@ enum TALER_MERCHANT_VersionCompatibility
 struct TALER_MERCHANT_ConfigInformation
 {
   /**
-   * Currency used/supported by the merchant.
+   * Default currency of the merchant.  See cspecs
+   * for all currencies supported by the merchant.
    */
   const char *currency;
 
@@ -333,6 +334,18 @@ struct TALER_MERCHANT_ConfigResponse
        * protocol compatibility information
        */
       enum TALER_MERCHANT_VersionCompatibility compat;
+
+      /**
+       * Length of the @e cspecs array.
+       */
+      unsigned int num_cspecs;
+
+      /**
+       * Array with rendering specifications for the currencies
+       * supported by this merchant backend.
+       */
+      const struct TALER_CurrencySpecification *cspecs;
+      
     } ok;
   } details;
 };
diff --git a/src/include/taler_merchantdb_plugin.h 
b/src/include/taler_merchantdb_plugin.h
index fd29edba..7d589123 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -711,6 +711,11 @@ typedef void
 enum TALER_MERCHANTDB_RefundStatus
 {
 
+  /**
+   * Refund amount currency does not match original payment.
+   */
+  TALER_MERCHANTDB_RS_BAD_CURRENCY = -4,
+
   /**
    * Refund amount exceeds original payment.
    */
diff --git a/src/lib/merchant_api_common.c b/src/lib/merchant_api_common.c
index 00e0358d..f5569ce3 100644
--- a/src/lib/merchant_api_common.c
+++ b/src/lib/merchant_api_common.c
@@ -245,7 +245,7 @@ TALER_MERCHANT_parse_pay_uri_free (
 }
 
 
-int
+enum GNUNET_GenericReturnValue
 TALER_MERCHANT_parse_refund_uri (
   const char *refund_uri,
   struct TALER_MERCHANT_RefundUriData *parse_data)
diff --git a/src/lib/merchant_api_get_config.c 
b/src/lib/merchant_api_get_config.c
index 8eddf9d7..708b7907 100644
--- a/src/lib/merchant_api_get_config.c
+++ b/src/lib/merchant_api_get_config.c
@@ -104,7 +104,10 @@ handle_config_finished (void *cls,
   {
   case MHD_HTTP_OK:
     {
+      const json_t *jcs;
       struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_object_const ("currencies",
+                                       &jcs),
         GNUNET_JSON_spec_string ("currency",
                                  &cr.details.ok.ci.currency),
         GNUNET_JSON_spec_string ("version",
@@ -153,8 +156,41 @@ handle_config_finished (void *cls,
           }
         }
       }
-      vgh->cb (vgh->cb_cls,
-               &cr);
+      {
+        unsigned int nspec = json_object_size (jcs);
+        struct TALER_CurrencySpecification *cspecs;
+        unsigned int off = 0;
+        json_t *obj;
+        const char *curr;
+
+        cspecs = GNUNET_new_array (nspec,
+                                   struct TALER_CurrencySpecification);
+        cr.details.ok.num_cspecs = nspec;
+        cr.details.ok.cspecs = cspecs;
+        json_object_foreach ((json_t *) jcs, curr, obj)
+        {
+          struct TALER_CurrencySpecification *cs = &cspecs[off++];
+          struct GNUNET_JSON_Specification cspec[] = {
+            TALER_JSON_spec_currency_specification (curr,
+                                                    cs),
+            GNUNET_JSON_spec_end ()
+          };
+
+          if (GNUNET_OK !=
+              GNUNET_JSON_parse (jcs,
+                                 cspec,
+                                 NULL, NULL))
+          {
+            cr.hr.http_status = 0;
+            cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+            break;
+          }
+        }
+        vgh->cb (vgh->cb_cls,
+                 &cr);
+        TALER_CONFIG_free_currencies (nspec,
+                                      cspecs);
+      }
       TALER_MERCHANT_config_get_cancel (vgh);
       return;
     }
diff --git a/src/testing/test_merchant_api.conf 
b/src/testing/test_merchant_api.conf
index bf84e4ec..00f287fb 100644
--- a/src/testing/test_merchant_api.conf
+++ b/src/testing/test_merchant_api.conf
@@ -7,6 +7,9 @@ TALER_TEST_HOME = test_merchant_api_home/
 CURRENCY = EUR
 CURRENCY_ROUND_UNIT = EUR:0.01
 
+[merchant-exchange-kudos]
+DISABLED = YES
+
 [taler-helper-crypto-rsa]
 LOOKAHEAD_SIGN = 10 days
 
diff --git a/src/testing/test_merchant_instance_auth.sh 
b/src/testing/test_merchant_instance_auth.sh
index 58cee79d..ce6b1e07 100755
--- a/src/testing/test_merchant_instance_auth.sh
+++ b/src/testing/test_merchant_instance_auth.sh
@@ -33,7 +33,6 @@ function my_cleanup()
 
 . setup.sh
 
-# Launch only the merchant.
 setup -c test_template.conf -m
 CONF="test_template.conf.edited"
 LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX)
@@ -69,6 +68,8 @@ kill -TERM "$SETUP_PID"
 wait
 unset SETUP_PID
 
+setup -c test_template.conf -ef -u "exchange-account-2"
+
 NEW_SECRET=secret-token:different_value
 
 taler-merchant-httpd -a "${NEW_SECRET}" -c "${CONF}" -L DEBUG 2> 
taler-merchant-httpd.log &
diff --git a/src/testing/test_merchant_kyc.sh b/src/testing/test_merchant_kyc.sh
index 663590b1..c76f1387 100755
--- a/src/testing/test_merchant_kyc.sh
+++ b/src/testing/test_merchant_kyc.sh
@@ -21,7 +21,7 @@ set -eu
 . setup.sh
 
 # Launch system.
-setup -c "test_template.conf" -m -u "exchange-account-1"
+setup -c "test_template.conf" -mef -u "exchange-account-2"
 LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX)
 
 echo -n "Configuring a merchant default instance ..."
@@ -89,12 +89,13 @@ STATUS=$(curl 
'http://localhost:9966/instances/default/private/orders' \
 
 if [ "$STATUS" != "200" ]
 then
-    echo 'should respond 200 OK, order created. got:' $STATUS `cat 
"$LAST_RESPONSE"`
+    echo "Should respond 200 OK, order created. got: $STATUS"
+    jq < "$LAST_RESPONSE"
     exit 1
 fi
 
-ORDER_ID=`jq -r .order_id < "$LAST_RESPONSE"`
-TOKEN=`jq -r .token < "$LAST_RESPONSE"`
+ORDER_ID=$(jq -r .order_id < "$LAST_RESPONSE")
+TOKEN=$(jq -r .token < "$LAST_RESPONSE")
 
 if [ "$TOKEN" != "null" ]
 then
@@ -108,7 +109,7 @@ echo -n "Checking created order without TOKEN..."
 STATUS=$(curl http://localhost:9966/orders/$ORDER_ID \
     -w "%{http_code}" -s -o "$LAST_RESPONSE")
 
-PAY_URI=`jq -r .taler_pay_uri < "$LAST_RESPONSE"`
+PAY_URI=$(jq -r .taler_pay_uri < "$LAST_RESPONSE")
 
 if [ "$PAY_URI" == "null" ]
 then
diff --git a/src/testing/test_merchant_order_creation.sh 
b/src/testing/test_merchant_order_creation.sh
index 3fadd358..02185c62 100755
--- a/src/testing/test_merchant_order_creation.sh
+++ b/src/testing/test_merchant_order_creation.sh
@@ -326,7 +326,7 @@ fi
 echo "OK"
 
 #
-# CREATE INVALID ORDER
+# Create product in another currency
 #
 
 
@@ -334,9 +334,9 @@ STATUS=$(curl 
'http://localhost:9966/instances/default/private/products' \
     -d '{"product_id":"1","description":"product with id 1 and price 
:15","price":"USD:15","total_stock":1,"description_i18n":{},"unit":"","image":"","taxes":[],"address":{},"next_restock":{"t_s":"never"}}'
 \
     -w "%{http_code}" -s -o /dev/null)
 
-if [ "$STATUS" != "400" ]
+if [ "$STATUS" != "204" ]
 then
-    exit_fail "Expected 400 bad request, product price is in another currency. 
got: $STATUS"
+    exit_fail "Expected 204 no content. got: $STATUS"
 fi
 
 #
diff --git a/src/testing/test_merchant_reserve_creation.sh 
b/src/testing/test_merchant_reserve_creation.sh
index c910ba9c..8fafb551 100755
--- a/src/testing/test_merchant_reserve_creation.sh
+++ b/src/testing/test_merchant_reserve_creation.sh
@@ -220,9 +220,9 @@ STATUS=$(curl 
'http://localhost:9966/instances/default/private/reserves' \
     -d 
'{"initial_balance":"INVALID:2","exchange_url":"http://localhost:8081/","wire_method":"iban"}'
 \
     -w "%{http_code}" -s -o "$LAST_RESPONSE")
 
-if [ "$STATUS" != "400" ]
+if [ "$STATUS" != "409" ]
 then
-    exit_fail "Expected 400, bad currency. got: $STATUS"
+    exit_fail "Expected 409, bad currency. got: $STATUS"
 fi
 echo "FAILED (which is expected)"
 
diff --git a/src/testing/test_template.conf b/src/testing/test_template.conf
index 3723f898..a278ea58 100644
--- a/src/testing/test_template.conf
+++ b/src/testing/test_template.conf
@@ -5,6 +5,9 @@ TALER_TEST_HOME = test_merchant_api_home/
 CURRENCY = TESTKUDOS
 CURRENCY_ROUND_UNIT = TESTKUDOS:0.01
 
+[merchant-exchange-kudos]
+DISABLED = YES
+
 [exchange]
 AML_THRESHOLD = TESTKUDOS:1000000
 MAX_KEYS_CACHING = forever

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]