gnunet-svn
[Top][All Lists]
Advanced

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

[GNUnet-SVN] [taler-merchant] branch master updated: implement /tip-quer


From: gnunet
Subject: [GNUnet-SVN] [taler-merchant] branch master updated: implement /tip-query and fix tip db issues (uniqueness)
Date: Thu, 01 Feb 2018 03:38:18 +0100

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

dold pushed a commit to branch master
in repository merchant.

The following commit(s) were added to refs/heads/master by this push:
     new 77f74fb  implement /tip-query and fix tip db issues (uniqueness)
77f74fb is described below

commit 77f74fbef5bd6a50f68e61b1642f1701a926abf3
Author: Florian Dold <address@hidden>
AuthorDate: Thu Feb 1 03:38:07 2018 +0100

    implement /tip-query and fix tip db issues (uniqueness)
---
 src/backend/taler-merchant-httpd_tip-authorize.c |   1 +
 src/backend/taler-merchant-httpd_tip-query.c     | 499 ++++++++++++++++++++---
 src/backend/taler-merchant-httpd_tip-query.h     |   4 +-
 src/backenddb/plugin_merchantdb_postgres.c       | 175 +++++++-
 src/include/taler_merchant_service.h             |  68 ++-
 src/include/taler_merchantdb_plugin.h            |  16 +
 src/lib/Makefile.am                              |   3 +-
 src/lib/merchant_api_tip_query.c                 | 244 +++++++++++
 src/lib/test_merchant_api.c                      | 169 +++++++-
 9 files changed, 1112 insertions(+), 67 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_tip-authorize.c 
b/src/backend/taler-merchant-httpd_tip-authorize.c
index ea2c706..7193861 100644
--- a/src/backend/taler-merchant-httpd_tip-authorize.c
+++ b/src/backend/taler-merchant-httpd_tip-authorize.c
@@ -207,6 +207,7 @@ handle_status (void *cls,
                       qs);
         }
       }
+      break;
     case TALER_EXCHANGE_RTT_WITHDRAWAL:
       /* expected */
       break;
diff --git a/src/backend/taler-merchant-httpd_tip-query.c 
b/src/backend/taler-merchant-httpd_tip-query.c
index 6381c8a..7139b3f 100644
--- a/src/backend/taler-merchant-httpd_tip-query.c
+++ b/src/backend/taler-merchant-httpd_tip-query.c
@@ -1,9 +1,9 @@
 /*
   This file is part of TALER
-  (C) 2017 Taler Systems SA
+  (C) 2018 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
+  terms of the GNU Affero General Public License as published by the Free 
Software
   Foundation; either version 3, or (at your option) any later version.
 
   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
@@ -15,15 +15,14 @@
 */
 /**
  * @file backend/taler-merchant-httpd_tip-query.c
- * @brief implementation of /tip-query handler
+ * @brief implement API for authorizing tips to be paid to visitors
  * @author Christian Grothoff
  * @author Florian Dold
  */
 #include "platform.h"
-#include <microhttpd.h>
 #include <jansson.h>
+#include <taler/taler_util.h>
 #include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
 #include "taler-merchant-httpd.h"
 #include "taler-merchant-httpd_mhd.h"
 #include "taler-merchant-httpd_parsing.h"
@@ -33,8 +32,346 @@
 
 
 /**
- * Manages a /tip-query call, checking if a tip authorization
- * exists and, if so, returning its details.
+ * Maximum number of retries for database operations.
+ */
+#define MAX_RETRIES 5
+
+
+struct TipQueryContext
+{
+  /**
+   * This field MUST be first.
+   * FIXME: Explain why!
+   */
+  struct TM_HandlerContext hc;
+
+  /**
+   * HTTP connection we are handling.
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * Merchant instance to use.
+   */
+  const char *instance;
+
+  /**
+   * Handle to pending /reserve/status request.
+   */
+  struct TALER_EXCHANGE_ReserveStatusHandle *rsh;
+
+  /**
+   * Handle for operation to obtain exchange handle.
+   */
+  struct TMH_EXCHANGES_FindOperation *fo;
+
+  /**
+   * Reserve expiration time as provided by the exchange.
+   * Set in #exchange_cont.
+   */
+  struct GNUNET_TIME_Relative idle_reserve_expiration_time;
+
+  /**
+   * Tip amount requested.
+   */
+  struct TALER_Amount amount_deposited;
+
+  /**
+   * Tip amount requested.
+   */
+  struct TALER_Amount amount_withdrawn;
+
+  /**
+   * Amount authorized.
+   */
+  struct TALER_Amount amount_authorized;
+
+  /**
+   * Private key used by this merchant for the tipping reserve.
+   */
+  struct TALER_ReservePrivateKeyP reserve_priv;
+
+  /**
+   * No tips were authorized yet.
+   */
+  int none_authorized;
+
+  /**
+   * Response to return, NULL if we don't have one yet.
+   */
+  struct MHD_Response *response;
+
+  /**
+   * HTTP status code to use for the reply, i.e 200 for "OK".
+   * Special value UINT_MAX is used to indicate hard errors
+   * (no reply, return #MHD_NO).
+   */
+  unsigned int response_code;
+
+  /**
+   * #GNUNET_NO if the @e connection was not suspended,
+   * #GNUNET_YES if the @e connection was suspended,
+   * #GNUNET_SYSERR if @e connection was resumed to as
+   * part of #MH_force_pc_resume during shutdown.
+   */
+  int suspended;
+};
+
+
+/**
+ * Custom cleanup routine for a `struct TipQueryContext`.
+ *
+ * @param hc the `struct TMH_JsonParseContext` to clean up.
+ */
+static void
+cleanup_tqc (struct TM_HandlerContext *hc)
+{
+  struct TipQueryContext *tqc = (struct TipQueryContext *) hc;
+
+  if (NULL != tqc->rsh)
+  {
+    TALER_EXCHANGE_reserve_status_cancel (tqc->rsh);
+    tqc->rsh = NULL;
+  }
+  if (NULL != tqc->fo)
+  {
+    TMH_EXCHANGES_find_exchange_cancel (tqc->fo);
+    tqc->fo = NULL;
+  }
+  GNUNET_free (tqc);
+}
+
+
+/**
+ * Resume the given context and send the given response.  Stores the response
+ * in the @a pc and signals MHD to resume the connection.  Also ensures MHD
+ * runs immediately.
+ *
+ * @param pc payment context
+ * @param response_code response code to use
+ * @param response response data to send back
+ */
+static void
+resume_with_response (struct TipQueryContext *tqc,
+                      unsigned int response_code,
+                      struct MHD_Response *response)
+{
+  tqc->response_code = response_code;
+  tqc->response = response;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Resuming /tip-query response (%u)\n",
+              response_code);
+  GNUNET_assert (GNUNET_YES == tqc->suspended);
+  tqc->suspended = GNUNET_NO;
+  MHD_resume_connection (tqc->connection);
+  TMH_trigger_daemon (); /* we resumed, kick MHD */
+}
+
+
+/**
+ * Function called with the result of the /reserve/status request
+ * for the tipping reserve.  Update our database balance with the
+ * result.
+ *
+ * @param cls closure with a `struct TipAuthContext *'
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful 
status request
+ *                    0 if the exchange's reply is bogus (fails to follow the 
protocol)
+ * @param ec taler-specific error code, #TALER_EC_NONE on success
+ * @param[in] json original response in JSON format (useful only for 
diagnostics)
+ * @param balance current balance in the reserve, NULL on error
+ * @param history_length number of entries in the transaction history, 0 on 
error
+ * @param history detailed transaction history, NULL on error
+ */
+static void
+handle_status (void *cls,
+               unsigned int http_status,
+               enum TALER_ErrorCode ec,
+               const json_t *json,
+               const struct TALER_Amount *balance,
+               unsigned int history_length,
+               const struct TALER_EXCHANGE_ReserveHistory *history)
+{
+  struct TipQueryContext *tqc = cls;
+  struct GNUNET_TIME_Absolute expiration;
+
+  tqc->rsh = NULL;
+  if (MHD_HTTP_OK != http_status)
+  {
+    GNUNET_break_op (0);
+    resume_with_response (tqc, MHD_HTTP_SERVICE_UNAVAILABLE,
+                          TMH_RESPONSE_make_error (TALER_EC_NONE /* FIXME */,
+                                                   "Unable to obtain reserve 
status from exchange"));
+    return;
+  }
+
+  if (0 == history_length)
+  {
+    GNUNET_break_op (0);
+    resume_with_response (tqc, MHD_HTTP_SERVICE_UNAVAILABLE,
+                          TMH_RESPONSE_make_error (TALER_EC_NONE /* FIXME */,
+                                                   "Exchange returned empty 
reserve history"));
+    return;
+  }
+
+  if (TALER_EXCHANGE_RTT_DEPOSIT != history[0].type)
+  {
+    GNUNET_break_op (0);
+    resume_with_response (tqc, MHD_HTTP_SERVICE_UNAVAILABLE,
+                          TMH_RESPONSE_make_error (TALER_EC_NONE /* FIXME */,
+                                                   "Exchange returned invalid 
reserve history"));
+    return;
+  }
+
+  if (GNUNET_OK != TALER_amount_get_zero (history[0].amount.currency, 
&tqc->amount_withdrawn))
+  {
+    GNUNET_break_op (0);
+    resume_with_response (tqc, MHD_HTTP_SERVICE_UNAVAILABLE,
+                          TMH_RESPONSE_make_error (TALER_EC_NONE /* FIXME */,
+                                                   "Exchange returned invalid 
reserve history"));
+    return;
+  }
+
+  if (GNUNET_YES == tqc->none_authorized)
+    memcpy (&tqc->amount_authorized, &tqc->amount_withdrawn, sizeof (struct 
TALER_Amount));
+  memcpy (&tqc->amount_deposited, &tqc->amount_withdrawn, sizeof (struct 
TALER_Amount));
+
+  /* Update DB based on status! */
+  for (unsigned int i=0;i<history_length;i++)
+  {
+    switch (history[i].type)
+    {
+    case TALER_EXCHANGE_RTT_DEPOSIT:
+      {
+        enum GNUNET_DB_QueryStatus qs;
+        struct GNUNET_HashCode uuid;
+
+        expiration = GNUNET_TIME_absolute_add 
(history[i].details.in_details.timestamp,
+                                               
tqc->idle_reserve_expiration_time);
+        GNUNET_CRYPTO_hash (history[i].details.in_details.wire_reference,
+                            history[i].details.in_details.wire_reference_size,
+                            &uuid);
+        qs = db->enable_tip_reserve (db->cls,
+                                     &tqc->reserve_priv,
+                                     &uuid,
+                                     &history[i].amount,
+                                     expiration);
+        if (GNUNET_OK != TALER_amount_add (&tqc->amount_deposited,
+                                           &tqc->amount_deposited,
+                                           &history[i].amount))
+        {
+          GNUNET_break_op (0);
+          resume_with_response (tqc, MHD_HTTP_SERVICE_UNAVAILABLE,
+                                TMH_RESPONSE_make_error (TALER_EC_NONE /* 
FIXME */,
+                                                         "Exchange returned 
invalid reserve history (amount overflow)"));
+          return;
+        }
+
+        if (0 > qs)
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      _("Database error updating tipping reserve status: 
%d\n"),
+                      qs);
+        }
+      }
+      break;
+    case TALER_EXCHANGE_RTT_WITHDRAWAL:
+      if (GNUNET_OK != TALER_amount_add (&tqc->amount_withdrawn,
+                                         &tqc->amount_withdrawn,
+                                         &history[i].amount))
+      {
+        GNUNET_break_op (0);
+        resume_with_response (tqc, MHD_HTTP_SERVICE_UNAVAILABLE,
+                              TMH_RESPONSE_make_error (TALER_EC_NONE /* FIXME 
*/,
+                                                       "Exchange returned 
invalid reserve history (amount overflow)"));
+        return;
+      }
+      break;
+    case TALER_EXCHANGE_RTT_PAYBACK:
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  _("Encountered unsupported /payback operation on tipping 
reserve\n"));
+      break;
+    case TALER_EXCHANGE_RTT_CLOSE:
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  _("Exchange closed reserve (due to expiration), balance 
calulation is likely wrong. Please create a fresh reserve.\n"));
+      break;
+    }
+  }
+
+  {
+    struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub;
+    struct TALER_Amount amount_available;
+    GNUNET_CRYPTO_eddsa_key_get_public (&tqc->reserve_priv.eddsa_priv,
+                                        &reserve_pub);
+    if (GNUNET_SYSERR == TALER_amount_subtract (&amount_available, 
&tqc->amount_deposited, &tqc->amount_withdrawn))
+    {
+        GNUNET_break_op (0);
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "amount overflow, deposited %s 
but withdrawn %s\n",
+                    TALER_amount_to_string (&tqc->amount_deposited),
+                    TALER_amount_to_string (&tqc->amount_withdrawn));
+
+        resume_with_response (tqc, MHD_HTTP_SERVICE_UNAVAILABLE,
+                              TMH_RESPONSE_make_error (TALER_EC_NONE /* FIXME 
*/,
+                                                       "Exchange returned 
invalid reserve history (amount overflow)"));
+    }
+    resume_with_response (tqc, MHD_HTTP_OK,
+                          TMH_RESPONSE_make_json_pack ("{s:o, s:o, s:o, s:o, 
s:o}",
+                                                       "reserve_pub",
+                                                       
GNUNET_JSON_from_data_auto (&reserve_pub),
+                                                       "reserve_expiration",
+                                                       
GNUNET_JSON_from_time_abs (expiration),
+                                                       "amount_authorized",
+                                                       TALER_JSON_from_amount 
(&tqc->amount_authorized),
+                                                       "amount_picked_up",
+                                                       TALER_JSON_from_amount 
(&tqc->amount_withdrawn),
+                                                       "amount_available",
+                                                       TALER_JSON_from_amount 
(&amount_available)));
+  }
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * operation.
+ *
+ * @param cls closure with a `struct TipQueryContext *'
+ * @param eh handle to the exchange context
+ * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
+ * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
+ */
+static void
+exchange_cont (void *cls,
+               struct TALER_EXCHANGE_Handle *eh,
+               const struct TALER_Amount *wire_fee,
+               int exchange_trusted)
+{
+  struct TipQueryContext *tqc = cls;
+  struct TALER_ReservePublicKeyP reserve_pub;
+  const struct TALER_EXCHANGE_Keys *keys;
+
+  tqc->fo = NULL;
+  if (NULL == eh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                _("Failed to contact exchange configured for tipping!\n"));
+    MHD_resume_connection (tqc->connection);
+    TMH_trigger_daemon ();
+    return;
+  }
+  keys = TALER_EXCHANGE_get_keys (eh);
+  GNUNET_assert (NULL != keys);
+  tqc->idle_reserve_expiration_time
+    = keys->reserve_closing_delay;
+  GNUNET_CRYPTO_eddsa_key_get_public (&tqc->reserve_priv.eddsa_priv,
+                                      &reserve_pub.eddsa_pub);
+  tqc->rsh = TALER_EXCHANGE_reserve_status (eh,
+                                            &reserve_pub,
+                                            &handle_status,
+                                            tqc);
+}
+
+
+/**
+ * Handle a "/tip-query" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
@@ -45,58 +382,116 @@
  */
 int
 MH_handler_tip_query (struct TMH_RequestHandler *rh,
-                       struct MHD_Connection *connection,
-                       void **connection_cls,
-                       const char *upload_data,
-                       size_t *upload_data_size)
+                          struct MHD_Connection *connection,
+                          void **connection_cls,
+                          const char *upload_data,
+                          size_t *upload_data_size)
 {
-  const char *tip_id_str;
-  struct GNUNET_HashCode tip_id;
-
-  tip_id_str = MHD_lookup_connection_value (connection,
-                                            MHD_GET_ARGUMENT_KIND,
-                                            "tip_id");
-  if (NULL == tip_id_str)
-    return TMH_RESPONSE_reply_arg_missing (connection,
-                                          TALER_EC_PARAMETER_MISSING,
-                                           "tip_id");
+  struct TipQueryContext *tqc;
+  int res;
+  struct MerchantInstance *mi;
 
-  if (GNUNET_OK != GNUNET_STRINGS_string_to_data (tip_id_str,
-                                                  strlen (tip_id_str), &tip_id,
-                                                  sizeof (struct 
GNUNET_HashCode)))
-    return TMH_RESPONSE_reply_arg_invalid (connection,
-                                          TALER_EC_PARAMETER_MISSING,
-                                           "tip_id");
+  if (NULL == *connection_cls)
+  {
+    tqc = GNUNET_new (struct TipQueryContext);
+    tqc->hc.cc = &cleanup_tqc;
+    tqc->connection = connection;
+    *connection_cls = tqc;
+  }
+  else
+  {
+    tqc = *connection_cls;
+  }
 
-  enum GNUNET_DB_QueryStatus qs;
-  struct TALER_Amount tip_amount;
-  struct GNUNET_TIME_Absolute tip_timestamp;
-  char *tip_exchange_url;
+  if (0 != tqc->response_code)
+  {
+    /* We are *done* processing the request, just queue the response (!) */
+    if (UINT_MAX == tqc->response_code)
+    {
+      GNUNET_break (0);
+      return MHD_NO; /* hard error */
+    }
+    res = MHD_queue_response (connection,
+                             tqc->response_code,
+                             tqc->response);
+    MHD_destroy_response (tqc->response);
+    tqc->response = NULL;
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+               "Queueing response (%u) for /tip-query (%s).\n",
+               (unsigned int) tqc->response_code,
+               res ? "OK" : "FAILED");
+    return res;
+  }
 
-  qs = db->lookup_tip_by_id (db->cls,
-                             &tip_id,
-                             &tip_exchange_url,
-                             &tip_amount,
-                             &tip_timestamp);
+  tqc->instance = MHD_lookup_connection_value (connection,
+                                               MHD_GET_ARGUMENT_KIND,
+                                               "instance");
+  if (NULL == tqc->instance)
+    return TMH_RESPONSE_reply_arg_missing (connection,
+                                          TALER_EC_PARAMETER_MISSING,
+                                           "instance");
 
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  mi = TMH_lookup_instance (tqc->instance);
+  if (NULL == mi)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Instance `%s' not configured\n",
+                tqc->instance);
+    return TMH_RESPONSE_reply_not_found (connection,
+                                        
TALER_EC_TIP_AUTHORIZE_INSTANCE_UNKNOWN,
+                                        "unknown instance");
+  }
+  if (NULL == mi->tip_exchange)
   {
-    return TMH_RESPONSE_reply_rc (connection,
-                                  MHD_HTTP_NOT_FOUND,
-                                  TALER_EC_TIP_QUERY_TIP_ID_UNKNOWN,
-                                  "tip id not found");
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Instance `%s' not configured for tipping\n",
+                tqc->instance);
+    return TMH_RESPONSE_reply_not_found (connection,
+                                        
TALER_EC_TIP_AUTHORIZE_INSTANCE_DOES_NOT_TIP,
+                                        "exchange for tipping not configured 
for the instance");
   }
-  else if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+  tqc->reserve_priv = mi->tip_reserve;
+
   {
-      return TMH_RESPONSE_reply_json_pack (connection,
-                                           MHD_HTTP_OK,
-                                           "{s:s, s:s}",
-                                           "exchange", tip_exchange_url,
-                                           "timestamp", 
GNUNET_JSON_from_time_abs (tip_timestamp),
-                                           "amount", TALER_JSON_from_amount 
(&tip_amount));
+    int qs;
+    for (unsigned int i=0;i<MAX_RETRIES;i++)
+    {
+      qs = db->get_authorized_tip_amount (db->cls,
+                                          &tqc->reserve_priv,
+                                          &tqc->amount_authorized);
+      if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+        break;
+    }
+    if (0 > qs)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Database hard error on get_authorized_tip_amount\n");
+      return TMH_RESPONSE_reply_internal_error (connection,
+                                                TALER_EC_NONE /* FIXME */,
+                                                "Merchant database error");
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    {
+      // We'll amount_authorized to zero later once
+      // we know the currency
+      tqc->none_authorized = GNUNET_YES;
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "DB::::: authorized amount: 
NONE\n");
+    }
+    else
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "DB::::: authorized amount: %s\n", 
TALER_amount_to_string (&tqc->amount_authorized));
+    }
   }
-  return TMH_RESPONSE_reply_rc (connection,
-                                MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                TALER_EC_INTERNAL_INVARIANT_FAILURE,
-                                "tip lookup failure");
+
+
+  MHD_suspend_connection (connection);
+  tqc->suspended = GNUNET_YES;
+
+  tqc->fo = TMH_EXCHANGES_find_exchange (mi->tip_exchange,
+                                         NULL,
+                                         &exchange_cont,
+                                         tqc);
+  return MHD_YES;
 }
+
+/* end of taler-merchant-httpd_tip-query.c */
diff --git a/src/backend/taler-merchant-httpd_tip-query.h 
b/src/backend/taler-merchant-httpd_tip-query.h
index ec8358d..f3a9ebf 100644
--- a/src/backend/taler-merchant-httpd_tip-query.h
+++ b/src/backend/taler-merchant-httpd_tip-query.h
@@ -16,7 +16,6 @@
 /**
  * @file backend/taler-merchant-httpd_tip-query.h
  * @brief headers for /tip-query handler
- * @author Christian Grothoff
  * @author Florian Dold
  */
 #ifndef TALER_MERCHANT_HTTPD_TIP_QUERY_H
@@ -25,8 +24,7 @@
 #include "taler-merchant-httpd.h"
 
 /**
- * Manages a /tip-query call, checking if a tip authorization
- * exists and, if so, returning its details.
+ * Manages a /tip-query call.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
diff --git a/src/backenddb/plugin_merchantdb_postgres.c 
b/src/backenddb/plugin_merchantdb_postgres.c
index 0a3ce5f..0ee4aa0 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -214,7 +214,7 @@ postgres_initialize (void *cls)
     /* table where we remember when tipping reserves where established / 
enabled */
     GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS 
merchant_tip_reserve_credits ("
                             " reserve_priv BYTEA NOT NULL CHECK 
(LENGTH(reserve_priv)=32)"
-                            ",credit_uuid BYTEA NOT NULL CHECK 
(LENGTH(credit_uuid)=64)"
+                            ",credit_uuid BYTEA UNIQUE NOT NULL CHECK 
(LENGTH(credit_uuid)=64)"
                             ",timestamp INT8 NOT NULL"
                             ",amount_val INT8 NOT NULL"
                             ",amount_frac INT4 NOT NULL"
@@ -585,6 +585,16 @@ postgres_initialize (void *cls)
                             " FROM merchant_tip_reserves"
                             " WHERE reserve_priv=$1",
                             1),
+    GNUNET_PQ_make_prepare ("find_tip_authorizations",
+                            "SELECT"
+                            " amount_val"
+                            ",amount_frac"
+                            ",amount_curr"
+                            ",justification"
+                            ",tip_id"
+                            " FROM merchant_tips"
+                            " WHERE reserve_priv=$1",
+                            1),
     GNUNET_PQ_make_prepare ("update_tip_reserve_balance",
                             "UPDATE merchant_tip_reserves SET"
                             " expiration=$2"
@@ -675,6 +685,11 @@ postgres_initialize (void *cls)
                             " VALUES "
                             "($1, $2, $3, $4, $5, $6)",
                             6),
+    GNUNET_PQ_make_prepare ("lookup_tip_credit_uuid",
+                            "SELECT 1 "
+                            "FROM merchant_tip_reserve_credits "
+                            "WHERE credit_uuid=$1 AND reserve_priv=$2",
+                            2),
     GNUNET_PQ_PREPARED_STATEMENT_END
   };
 
@@ -1425,6 +1440,128 @@ postgres_find_contract_terms_by_date_and_range (void 
*cls,
 
 
 /**
+ * Closure for #find_tip_authorizations_cb().
+ */
+struct GetAuthorizedTipAmountContext
+{
+  /**
+   * Total authorized amount.
+   */
+  struct TALER_Amount authorized_amount;
+
+  /**
+   * Transaction status code to set.
+   */
+  enum GNUNET_DB_QueryStatus qs;
+
+};
+
+
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct GetAuthorizedTipAmountContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+find_tip_authorizations_cb (void *cls,
+                            PGresult *result,
+                            unsigned int num_results)
+{
+  struct GetAuthorizedTipAmountContext *ctx = cls;
+  unsigned int i;
+
+  for (i = 0; i < num_results; i++)
+  {
+    struct TALER_Amount amount;
+    char *just;
+    struct GNUNET_HashCode h;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_string ("justification", &just),
+      GNUNET_PQ_result_spec_auto_from_type ("tip_id", &h),
+      TALER_PQ_result_spec_amount ("amount",
+                                    &amount),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      ctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      return;
+    }
+
+    if (0 == i)
+    {
+      memcpy (&ctx->authorized_amount, &amount, sizeof (struct TALER_Amount));
+    }
+    else if (GNUNET_OK !=
+             TALER_amount_add (&ctx->authorized_amount,
+                               &ctx->authorized_amount, &amount))
+    {
+      GNUNET_break (0);
+      ctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      return;
+    }
+  }
+
+  if (0 == i)
+  {
+    ctx->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+  }
+  else
+  {
+    /* one aggregated result */
+    ctx->qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+  }
+}
+
+
+/**
+ * Get the total amount of authorized tips for a tipping reserve.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param reserve_priv which reserve to check
+ * @param[out] authorzed_amount amount we've authorized so far for tips
+ * @return transaction status, usually
+ *      #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
+ *      #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the reserve_priv
+ *      does not identify a known tipping reserve
+ */
+enum GNUNET_DB_QueryStatus
+postgres_get_authorized_tip_amount (void *cls,
+                                    const struct TALER_ReservePrivateKeyP 
*reserve_priv,
+                                    struct TALER_Amount *authorized_amount)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (reserve_priv),
+    GNUNET_PQ_query_param_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+  struct GetAuthorizedTipAmountContext ctx = { 0 };
+
+  check_connection (pg);
+  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                            "find_tip_authorizations",
+                                            params,
+                                            &find_tip_authorizations_cb,
+                                            &ctx);
+  if (0 >= qs)
+    return qs;
+  memcpy (authorized_amount, &ctx.authorized_amount, sizeof (struct 
TALER_Amount));
+  return ctx.qs;
+}
+
+
+/**
  * Return proposals whose timestamp are older than `date`.
  * The rows are sorted having the youngest first.
  *
@@ -2830,6 +2967,35 @@ postgres_enable_tip_reserve (void *cls,
 
   /* ensure that credit_uuid is new/unique */
   {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_auto_from_type (credit_uuid),
+      GNUNET_PQ_query_param_auto_from_type (reserve_priv),
+      GNUNET_PQ_query_param_end
+    };
+
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_end
+    };
+    qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_tip_credit_uuid",
+                                                   params,
+                                                   rs);
+    if (0 > qs)
+    {
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      postgres_rollback (pg);
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        goto RETRY;
+      return qs;
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+    {
+      /* UUID already exists, we are done! */
+      return qs;
+    }
+  }
+
+  {
     struct GNUNET_TIME_Absolute now;
     struct GNUNET_PQ_QueryParam params[] = {
       GNUNET_PQ_query_param_auto_from_type (reserve_priv),
@@ -2852,12 +3018,6 @@ postgres_enable_tip_reserve (void *cls,
         goto RETRY;
       return qs;
     }
-    /* UUID already exists, we are done! */
-    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-    {
-      postgres_rollback (pg);
-      return qs;
-    }
   }
 
   /* Obtain existing reserve balance */
@@ -3405,6 +3565,7 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
   plugin->find_contract_terms = &postgres_find_contract_terms;
   plugin->find_contract_terms_history = &postgres_find_contract_terms_history;
   plugin->find_contract_terms_by_date = &postgres_find_contract_terms_by_date;
+  plugin->get_authorized_tip_amount = &postgres_get_authorized_tip_amount;
   plugin->find_contract_terms_by_date_and_range = 
&postgres_find_contract_terms_by_date_and_range;
   plugin->find_contract_terms_from_hash = 
&postgres_find_contract_terms_from_hash;
   plugin->find_paid_contract_terms_from_hash = 
&postgres_find_paid_contract_terms_from_hash;
diff --git a/src/include/taler_merchant_service.h 
b/src/include/taler_merchant_service.h
index 3462288..2dd0b20 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -922,7 +922,7 @@ TALER_MERCHANT_tip_pickup_cancel (struct 
TALER_MERCHANT_TipPickupOperation *tp);
 
 
 /**
- * Handle for a /tip-pickup operation.
+ * Handle for a /check-payment operation.
  */
 struct TALER_MERCHANT_CheckPaymentOperation;
 
@@ -980,14 +980,76 @@ TALER_MERCHANT_check_payment (struct GNUNET_CURL_Context 
*ctx,
                               TALER_MERCHANT_CheckPaymentCallback 
check_payment_cb,
                               void *check_payment_cls);
 
-
 /**
  * Cancel a GET /check-payment request.
  *
+ * @param cpo handle to the request to be canceled
+ */
+void
+TALER_MERCHANT_check_payment_cancel (struct 
TALER_MERCHANT_CheckPaymentOperation *cpo);
+
+
+/* ********************** /tip-query ************************* */
+
+/**
+ * Handle for a /tip-query operation.
+ */
+struct TALER_MERCHANT_TipQueryOperation;
+
+
+/**
+ * Callback to process a GET /tip-query request
+ *
+ * @param cls closure
+ * @param http_status HTTP status code for this request
+ * @param ec Taler-specific error code
+ * @param raw raw response body
+ */
+typedef void
+(*TALER_MERCHANT_TipQueryCallback) (void *cls,
+                                    unsigned int http_status,
+                                    enum TALER_ErrorCode ec,
+                                    const json_t *raw,
+                                    struct GNUNET_TIME_Absolute 
reserve_expiration,
+                                    struct TALER_ReservePublicKeyP 
*reserve_pub,
+                                    struct TALER_Amount *amount_authorized,
+                                    struct TALER_Amount *amount_available,
+                                    struct TALER_Amount *amount_picked_up);
+
+
+/**
+ * Cancel a GET /tip-query request.
+ *
  * @param cph handle to the request to be canceled
  */
 void
-TALER_MERCHANT_check_payment_cancel (struct 
TALER_MERCHANT_CheckPaymentOperation *cph);
+TALER_MERCHANT_tip_query_cancel (struct TALER_MERCHANT_TipQueryOperation *tqo);
+
+
+/**
+ * Issue a /tip-query request to the backend.  Informs the backend
+ * that a customer wants to pick up a tip.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param instance instance to query
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_TipQueryOperation *
+TALER_MERCHANT_tip_query (struct GNUNET_CURL_Context *ctx,
+                          const char *backend_url,
+                          const char *instance,
+                          TALER_MERCHANT_TipQueryCallback query_cb,
+                          void *query_cb_cls);
+
+
+/**
+ * Cancel a GET /tip-query request.
+ *
+ * @param tqo handle to the request to be canceled
+ */
+void
+TALER_MERCHANT_tip_query_cancel (struct TALER_MERCHANT_TipQueryOperation *tqh);
 
 
 #endif  /* _TALER_MERCHANT_SERVICE_H */
diff --git a/src/include/taler_merchantdb_plugin.h 
b/src/include/taler_merchantdb_plugin.h
index 0bb1e33..1005e6c 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -738,6 +738,22 @@ struct TALER_MERCHANTDB_Plugin
                    struct GNUNET_TIME_Absolute *expiration,
                    struct GNUNET_HashCode *tip_id);
 
+  /**
+   * Get the total amount of authorized tips for a tipping reserve.
+   *
+   * @param cls closure, typically a connection to the db
+   * @param reserve_priv which reserve to check
+   * @param[out] authorzed_amount amount we've authorized so far for tips
+   * @return transaction status, usually
+   *      #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
+   *      #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the reserve_priv
+   *      does not identify a known tipping reserve
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_authorized_tip_amount)(void *cls,
+                               const struct TALER_ReservePrivateKeyP 
*reserve_priv,
+                               struct TALER_Amount *authorized_amount);
+
 
   /**
    * Find out tip authorization details associated with @a tip_id
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 79f2674..d953081 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -22,7 +22,8 @@ libtalermerchant_la_SOURCES = \
   merchant_api_track_transfer.c \
   merchant_api_history.c \
   merchant_api_refund.c \
-  merchant_api_check_payment.c
+  merchant_api_check_payment.c \
+  merchant_api_tip_query.c
 
 libtalermerchant_la_LIBADD = \
   -ltalerexchange \
diff --git a/src/lib/merchant_api_tip_query.c b/src/lib/merchant_api_tip_query.c
new file mode 100644
index 0000000..b0b2c19
--- /dev/null
+++ b/src/lib/merchant_api_tip_query.c
@@ -0,0 +1,244 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Lesser General Public License as published by the Free 
Software
+  Foundation; either version 2.1, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
details.
+
+  You should have received a copy of the GNU Lesser General Public License 
along with
+  TALER; see the file COPYING.LGPL.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/merchant_api_tip_query.c
+ * @brief Implementation of the /tip-query request of the merchant's HTTP API
+ * @author Florian Dold
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * @brief A handle for tracking /tip-pickup operations
+ */
+struct TALER_MERCHANT_TipQueryOperation
+{
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * JSON encoding of the request to POST.
+   */
+  char *json_enc;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_TipQueryCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Expected number of planchets.
+   */
+  unsigned int num_planchets;
+};
+
+
+/**
+ * We got a 200 response back from the exchange (or the merchant).
+ * Now we need to parse the response and if it is well-formed,
+ * call the callback (and set it to NULL afterwards).
+ *
+ * @param tqo handle of the original operation
+ * @param json cryptographic proof returned by the exchange/merchant
+ * @return #GNUNET_OK if response is valid
+ */
+static int
+check_ok (struct TALER_MERCHANT_TipQueryOperation *tqo,
+          const json_t *json)
+{
+  struct GNUNET_TIME_Absolute reserve_expiration;
+  struct TALER_Amount amount_authorized;
+  struct TALER_Amount amount_available;
+  struct TALER_Amount amount_picked_up;
+  struct TALER_ReservePublicKeyP reserve_pub;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("reserve_pub", &reserve_pub),
+    GNUNET_JSON_spec_absolute_time ("reserve_expiration", &reserve_expiration),
+    TALER_JSON_spec_amount ("amount_authorized", &amount_authorized),
+    TALER_JSON_spec_amount ("amount_available", &amount_available),
+    TALER_JSON_spec_amount ("amount_picked_up", &amount_picked_up),
+    GNUNET_JSON_spec_end()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (json,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  tqo->cb (tqo->cb_cls,
+           MHD_HTTP_OK,
+           TALER_JSON_get_error_code (json),
+           json,
+           reserve_expiration,
+           &reserve_pub,
+           &amount_authorized,
+           &amount_available,
+           &amount_picked_up);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /track/transaction request.
+ *
+ * @param cls the `struct TALER_MERCHANT_TipQueryOperation`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_tip_query_finished (void *cls,
+                            long response_code,
+                            const json_t *json)
+{
+  struct TALER_MERCHANT_TipQueryOperation *tqo = cls;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Got /tip-query response with status code %u\n",
+              (unsigned int) response_code);
+
+  tqo->job = NULL;
+  switch (response_code)
+  {
+  case MHD_HTTP_OK:
+    if (GNUNET_OK != check_ok (tqo,
+                               json))
+    {
+      GNUNET_break_op (0);
+      response_code = 0;
+    }
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* legal, can happen if instance or tip reserve is unknown */
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u\n",
+                (unsigned int) response_code);
+    GNUNET_break (0);
+    response_code = 0;
+    break;
+  }
+  if (MHD_HTTP_OK != response_code)
+    tqo->cb (tqo->cb_cls,
+             response_code,
+             TALER_JSON_get_error_code (json),
+             json,
+             GNUNET_TIME_UNIT_ZERO_ABS,
+             NULL,
+             NULL,
+             NULL,
+             NULL);
+  TALER_MERCHANT_tip_query_cancel (tqo);
+}
+
+
+/**
+ * Issue a /tip-query request to the backend.  Informs the backend
+ * that a customer wants to pick up a tip.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param instance instance to query
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_TipQueryOperation *
+TALER_MERCHANT_tip_query (struct GNUNET_CURL_Context *ctx,
+                          const char *backend_url,
+                          const char *instance,
+                          TALER_MERCHANT_TipQueryCallback query_cb,
+                          void *query_cb_cls)
+{
+  struct TALER_MERCHANT_TipQueryOperation *tqo;
+  CURL *eh;
+
+  tqo = GNUNET_new (struct TALER_MERCHANT_TipQueryOperation);
+  tqo->ctx = ctx;
+  tqo->cb = query_cb;
+  tqo->cb_cls = query_cb_cls;
+  tqo->url = TALER_url_join (backend_url, "/tip-query",
+                             "instance", instance,
+                             NULL);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Requesting URL '%s'\n",
+              tqo->url);
+  eh = curl_easy_init ();
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   tqo->url));
+  tqo->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_YES,
+                                  &handle_tip_query_finished,
+                                  tqo);
+  return tqo;
+}
+
+
+/**
+ * Cancel a /tip-query request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param tqo handle to the operation being cancelled
+ */
+void
+TALER_MERCHANT_tip_query_cancel (struct TALER_MERCHANT_TipQueryOperation *tqo)
+{
+  if (NULL != tqo->job)
+  {
+    GNUNET_CURL_job_cancel (tqo->job);
+    tqo->job = NULL;
+  }
+  GNUNET_free (tqo->url);
+  GNUNET_free (tqo);
+}
+
+/* end of merchant_api_tip_query.c */
diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c
index dc81b9a..5a92e9c 100644
--- a/src/lib/test_merchant_api.c
+++ b/src/lib/test_merchant_api.c
@@ -248,6 +248,11 @@ enum OpCode
    */
   OC_CHECK_PAYMENT,
 
+  /**
+   * Query tip stats.
+   */
+  OC_TIP_QUERY,
+
 };
 
 
@@ -962,6 +967,37 @@ struct Command
     } tip_pickup;
 
     struct {
+      /**
+       * Expected available amount (in string format).
+       * NULL to skip check.
+       */
+      char *expected_amount_available;
+
+      /**
+       * Expected picked up amount (in string format).
+       * NULL to skip check.
+       */
+      char *expected_amount_picked_up;
+
+      /**
+       * Expected authorized amount (in string format).
+       * NULL to skip check.
+       */
+      char *expected_amount_authorized;
+
+      /**
+       * Handle for the ongoing operation.
+       */
+      struct TALER_MERCHANT_TipQueryOperation *tqo;
+
+      /**
+       * Merchant instance to use for tipping.
+       */
+      char *instance;
+
+    } tip_query;
+
+    struct {
 
       /**
        * Reference for the contract we want to check.
@@ -2114,6 +2150,77 @@ check_payment_cb (void *cls,
 
 
 /**
+ * Callback to process a GET /tip-query request
+ *
+ * @param cls closure
+ * @param http_status HTTP status code for this request
+ * @param ec Taler-specific error code
+ * @param raw raw response body
+ */
+static void
+tip_query_cb (void *cls,
+              unsigned int http_status,
+              enum TALER_ErrorCode ec,
+              const json_t *raw,
+              struct GNUNET_TIME_Absolute reserve_expiration,
+              struct TALER_ReservePublicKeyP *reserve_pub,
+              struct TALER_Amount *amount_authorized,
+              struct TALER_Amount *amount_available,
+              struct TALER_Amount *amount_picked_up)
+{
+  struct InterpreterState *is = cls;
+  struct Command *cmd = &is->commands[is->ip];
+  struct TALER_Amount a;
+
+  cmd->details.tip_query.tqo = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Tip query callback at command %u/%s(%u)\n",
+              is->ip,
+              cmd->label,
+              cmd->oc);
+
+  GNUNET_assert (NULL != reserve_pub);
+  GNUNET_assert (NULL != amount_authorized);
+  GNUNET_assert (NULL != amount_available);
+  GNUNET_assert (NULL != amount_picked_up);
+
+  if (cmd->details.tip_query.expected_amount_available)
+  {
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_string_to_amount 
(cmd->details.tip_query.expected_amount_available, &a));
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "expected available %s, actual %s\n",
+                TALER_amount_to_string (&a),
+                TALER_amount_to_string (amount_available));
+    GNUNET_assert (0 == TALER_amount_cmp (amount_available, &a));
+  }
+
+  if (cmd->details.tip_query.expected_amount_authorized)
+  {
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_string_to_amount 
(cmd->details.tip_query.expected_amount_authorized, &a));
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "expected authorized %s, actual %s\n",
+                TALER_amount_to_string (&a),
+                TALER_amount_to_string (amount_authorized));
+    GNUNET_assert (0 == TALER_amount_cmp (amount_authorized, &a));
+  }
+
+  if (cmd->details.tip_query.expected_amount_picked_up)
+  {
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_string_to_amount 
(cmd->details.tip_query.expected_amount_picked_up, &a));
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "expected picked_up %s, actual %s\n",
+                TALER_amount_to_string (&a),
+                TALER_amount_to_string (amount_picked_up));
+    GNUNET_assert (0 == TALER_amount_cmp (amount_picked_up, &a));
+  }
+
+  if (cmd->expected_response_code != http_status)
+    fail (is);
+  next_command (is);
+}
+
+
+/**
  * Function called with detailed wire transfer data.
  *
  * @param cls closure
@@ -2684,6 +2791,17 @@ cleanup_state (struct InterpreterState *is)
         cmd->details.check_payment.cpo = NULL;
       }
       break;
+    case OC_TIP_QUERY:
+      if (NULL != cmd->details.tip_query.tqo)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                    "Command %u (%s) did not complete\n",
+                    i,
+                    cmd->label);
+        TALER_MERCHANT_tip_query_cancel (cmd->details.tip_query.tqo);
+        cmd->details.tip_query.tqo = NULL;
+      }
+      break;
     default:
       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                   "Shutdown: unknown instruction %d at %u (%s)\n",
@@ -2902,7 +3020,7 @@ interpreter_run (void *cls)
     fail (is);
     return;
   }
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Interpreter runs command %u/%s(%u)\n",
               is->ip,
               cmd->label,
@@ -2985,6 +3103,29 @@ interpreter_run (void *cls)
       }
     }
     break;
+  case OC_TIP_QUERY:
+    {
+      if (instance_idx !=  0)
+      {
+        // We check /tip-query only for the first instance,
+        // since for tipping we use a dedicated instance.
+        // On repeated runs, the expected authorized amounts wouldn't
+        // match up (they would all accumulate!)
+        next_command (is);
+        break;
+      }
+      if (NULL == (cmd->details.tip_query.tqo
+                   = TALER_MERCHANT_tip_query (ctx,
+                                               MERCHANT_URL,
+                                               cmd->details.tip_query.instance,
+                                               tip_query_cb,
+                                               is)))
+      {
+        GNUNET_break (0);
+        fail (is);
+      }
+    }
+    break;
   case OC_ADMIN_ADD_INCOMING:
     {
       char *subject;
@@ -4414,17 +4555,43 @@ run (void *cls)
       .details.tip_authorize.instance = "tip",
       .details.tip_authorize.justification = "tip 2",
       .details.tip_authorize.amount = "EUR:5.01" },
+    /* Check tip status */
+    { .oc = OC_TIP_QUERY,
+      .label = "query-tip-1",
+      .expected_response_code = MHD_HTTP_OK,
+      .details.tip_query.instance = "tip" },
+    { .oc = OC_TIP_QUERY,
+      .label = "query-tip-2",
+      .expected_response_code = MHD_HTTP_OK,
+      .details.tip_query.instance = "tip",
+      .details.tip_query.expected_amount_authorized = "EUR:10.02",
+      .details.tip_query.expected_amount_picked_up = "EUR:0.0",
+      .details.tip_query.expected_amount_available = "EUR:20.04" },
     /* Withdraw tip */
     { .oc = OC_TIP_PICKUP,
       .label = "pickup-tip-1",
       .expected_response_code = MHD_HTTP_OK,
       .details.tip_pickup.authorize_ref = "authorize-tip-1",
       .details.tip_pickup.amounts = pickup_amounts_1 },
+    /* Check tip status again */
+    { .oc = OC_TIP_QUERY,
+      .label = "query-tip-3",
+      .expected_response_code = MHD_HTTP_OK,
+      .details.tip_query.instance = "tip",
+      .details.tip_query.expected_amount_picked_up = "EUR:5.01",
+      .details.tip_query.expected_amount_available = "EUR:15.03" },
     { .oc = OC_TIP_PICKUP,
       .label = "pickup-tip-2",
       .expected_response_code = MHD_HTTP_OK,
       .details.tip_pickup.authorize_ref = "authorize-tip-2",
       .details.tip_pickup.amounts = pickup_amounts_1 },
+    { .oc = OC_TIP_QUERY,
+      .label = "query-tip-4",
+      .expected_response_code = MHD_HTTP_OK,
+      .details.tip_query.instance = "tip",
+      .details.tip_query.expected_amount_picked_up = "EUR:10.02",
+      .details.tip_query.expected_amount_available = "EUR:10.02",
+      .details.tip_query.expected_amount_authorized = "EUR:10.02" },
     { .oc = OC_TIP_PICKUP,
       .label = "pickup-tip-2b",
       .expected_response_code = MHD_HTTP_OK,

-- 
To stop receiving notification emails like this one, please contact
address@hidden



reply via email to

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