summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2018-02-01 03:38:07 +0100
committerFlorian Dold <florian.dold@gmail.com>2018-02-01 03:38:07 +0100
commit77f74fbef5bd6a50f68e61b1642f1701a926abf3 (patch)
treed2dff0e3f47628d4d2b28d47e39249e51984dac4 /src
parentd0078be494fed82a092952bfc36055d01bfdd60d (diff)
downloadmerchant-77f74fbef5bd6a50f68e61b1642f1701a926abf3.tar.gz
merchant-77f74fbef5bd6a50f68e61b1642f1701a926abf3.tar.bz2
merchant-77f74fbef5bd6a50f68e61b1642f1701a926abf3.zip
implement /tip-query and fix tip db issues (uniqueness)
Diffstat (limited to 'src')
-rw-r--r--src/backend/taler-merchant-httpd_tip-authorize.c1
-rw-r--r--src/backend/taler-merchant-httpd_tip-query.c499
-rw-r--r--src/backend/taler-merchant-httpd_tip-query.h4
-rw-r--r--src/backenddb/plugin_merchantdb_postgres.c175
-rw-r--r--src/include/taler_merchant_service.h68
-rw-r--r--src/include/taler_merchantdb_plugin.h16
-rw-r--r--src/lib/Makefile.am3
-rw-r--r--src/lib/merchant_api_tip_query.c244
-rw-r--r--src/lib/test_merchant_api.c169
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 ea2c7064..71938619 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 6381c8aa..7139b3f1 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 ec8358d3..f3a9ebff 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 0a3ce5f5..0ee4aa05 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 3462288e..2dd0b202 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 0bb1e335..1005e6c5 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 79f26749..d953081f 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 00000000..b0b2c194
--- /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 dc81b9a7..5a92e9cc 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,