aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2019-11-02 13:40:41 +0100
committerChristian Grothoff <christian@grothoff.org>2019-11-02 13:40:41 +0100
commitea48d9fc008e34cf954f497db635fe343ba902f0 (patch)
tree550fa4fc50400b7a5bff3d952a88697b358251bd
parentae06bf1930ab7196938dd1eaa7db8c8d51d94cf4 (diff)
implement /public/poll-payment API, refactor to avoid code duplication
-rw-r--r--src/backend/Makefile.am21
-rw-r--r--src/backend/taler-merchant-httpd.c81
-rw-r--r--src/backend/taler-merchant-httpd.h31
-rw-r--r--src/backend/taler-merchant-httpd_check-payment.c102
-rw-r--r--src/backend/taler-merchant-httpd_poll-payment.c557
-rw-r--r--src/backend/taler-merchant-httpd_poll-payment.h47
-rw-r--r--src/backend/taler-merchant-httpd_tip-pickup.c4
7 files changed, 731 insertions, 112 deletions
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index ec4b2046..966d8f41 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -14,26 +14,25 @@ bin_PROGRAMS = \
taler_merchant_httpd_SOURCES = \
taler-merchant-httpd.c taler-merchant-httpd.h \
- taler-merchant-httpd_parsing.c taler-merchant-httpd_parsing.h \
- taler-merchant-httpd_responses.c taler-merchant-httpd_responses.h \
- taler-merchant-httpd_mhd.c taler-merchant-httpd_mhd.h \
taler-merchant-httpd_auditors.c taler-merchant-httpd_auditors.h \
+ taler-merchant-httpd_config.c taler-merchant-httpd_config.h \
+ taler-merchant-httpd_check-payment.c taler-merchant-httpd_check-payment.h \
taler-merchant-httpd_exchanges.c taler-merchant-httpd_exchanges.h \
+ taler-merchant-httpd_history.c taler-merchant-httpd_history.h \
+ taler-merchant-httpd_mhd.c taler-merchant-httpd_mhd.h \
taler-merchant-httpd_order.c taler-merchant-httpd_order.h \
- taler-merchant-httpd_proposal.c taler-merchant-httpd_proposal.h \
+ taler-merchant-httpd_parsing.c taler-merchant-httpd_parsing.h \
taler-merchant-httpd_pay.c taler-merchant-httpd_pay.h \
- taler-merchant-httpd_history.c taler-merchant-httpd_history.h \
+ taler-merchant-httpd_poll-payment.c taler-merchant-httpd_poll-payment.h \
+ taler-merchant-httpd_proposal.c taler-merchant-httpd_proposal.h \
+ taler-merchant-httpd_refund.c taler-merchant-httpd_refund.h \
+ taler-merchant-httpd_responses.c taler-merchant-httpd_responses.h \
taler-merchant-httpd_tip-authorize.c taler-merchant-httpd_tip-authorize.h \
taler-merchant-httpd_tip-pickup.c taler-merchant-httpd_tip-pickup.h \
taler-merchant-httpd_tip-query.c taler-merchant-httpd_tip-query.h \
taler-merchant-httpd_tip-reserve-helper.c taler-merchant-httpd_tip-reserve-helper.h \
taler-merchant-httpd_track-transaction.c taler-merchant-httpd_track-transaction.h \
- taler-merchant-httpd_track-transfer.c taler-merchant-httpd_track-transfer.h \
- taler-merchant-httpd_refund.c taler-merchant-httpd_refund.h \
- taler-merchant-httpd_check-payment.c taler-merchant-httpd_check-payment.h \
- taler-merchant-httpd_config.c taler-merchant-httpd_config.h
-
-
+ taler-merchant-httpd_track-transfer.c taler-merchant-httpd_track-transfer.h
taler_merchant_httpd_LDADD = \
$(top_builddir)/src/backenddb/libtalermerchantdb.la \
-ltalerexchange \
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
index e06e1441..8abb4449 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -48,6 +48,7 @@
#include "taler-merchant-httpd_history.h"
#include "taler-merchant-httpd_refund.h"
#include "taler-merchant-httpd_check-payment.h"
+#include "taler-merchant-httpd_poll-payment.h"
#include "taler-merchant-httpd_config.h"
/**
@@ -168,6 +169,11 @@ struct GNUNET_CONTAINER_Heap *resume_timeout_heap;
*/
struct GNUNET_CONTAINER_MultiHashMap *payment_trigger_map;
+/**
+ * Task responsible for timeouts in the #resume_timeout_heap.
+ */
+struct GNUNET_SCHEDULER_Task *resume_timeout_task;
+
/**
* Return #GNUNET_YES if given a valid correlation ID and
@@ -277,6 +283,73 @@ TMH_compute_pay_key (const char *order_id,
/**
+ * Create a taler://pay/ URI for the given @a con and @a order_id
+ * and @a session_id and @a instance_id.
+ *
+ * @param con HTTP connection
+ * @param order_id the order id
+ * @param session_id session, may be NULL
+ * @param instance_id instance, may be "default"
+ * @return corresponding taler://pay/ URI, or NULL on missing "host"
+ */
+char *
+TMH_make_taler_pay_uri (struct MHD_Connection *con,
+ const char *order_id,
+ const char *session_id,
+ const char *instance_id)
+{
+ const char *host;
+ const char *forwarded_host;
+ const char *uri_path;
+ const char *uri_instance_id;
+ const char *query;
+ char *result;
+
+ host = MHD_lookup_connection_value (con,
+ MHD_HEADER_KIND,
+ "Host");
+ forwarded_host = MHD_lookup_connection_value (con,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Host");
+
+ uri_path = MHD_lookup_connection_value (con,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Prefix");
+ if (NULL == uri_path)
+ uri_path = "-";
+ if (NULL != forwarded_host)
+ host = forwarded_host;
+ if (0 == strcmp (instance_id,
+ "default"))
+ uri_instance_id = "-";
+ else
+ uri_instance_id = instance_id;
+ if (NULL == host)
+ {
+ /* Should never happen, at least the host header should be defined */
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ if (GNUNET_YES == TALER_mhd_is_https (con))
+ query = "";
+ else
+ query = "?insecure=1";
+ GNUNET_assert (NULL != order_id);
+ GNUNET_assert (0 < GNUNET_asprintf (&result,
+ "taler://pay/%s/%s/%s/%s%s%s%s",
+ host,
+ uri_path,
+ uri_instance_id,
+ order_id,
+ (NULL == session_id) ? "" : "/",
+ (NULL == session_id) ? "" : session_id,
+ query));
+ return result;
+}
+
+
+/**
* Shutdown task (magically invoked when the application is being
* quit)
*
@@ -311,6 +384,11 @@ do_shutdown (void *cls)
GNUNET_CONTAINER_heap_destroy (resume_timeout_heap);
resume_timeout_heap = NULL;
}
+ if (NULL != resume_timeout_task)
+ {
+ GNUNET_SCHEDULER_cancel (resume_timeout_task);
+ resume_timeout_task = NULL;
+ }
if (NULL != mhd)
{
MHD_stop_daemon (mhd);
@@ -1196,6 +1274,9 @@ url_handler (void *cls,
{ "/check-payment", MHD_HTTP_METHOD_GET, "text/plain",
NULL, 0,
&MH_handler_check_payment, MHD_HTTP_OK},
+ { "/public/poll-payment", MHD_HTTP_METHOD_GET, "text/plain",
+ NULL, 0,
+ &MH_handler_poll_payment, MHD_HTTP_OK},
{ "/config", MHD_HTTP_METHOD_GET, "text/plain",
NULL, 0,
&MH_handler_config, MHD_HTTP_OK},
diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h
index bc53bbc6..53dedcb4 100644
--- a/src/backend/taler-merchant-httpd.h
+++ b/src/backend/taler-merchant-httpd.h
@@ -277,14 +277,20 @@ struct TMH_SuspendedConnection
struct MHD_Connection *con;
/**
+ * Associated heap node.
+ */
+ struct GNUNET_CONTAINER_HeapNode *hn;
+
+ /**
* Key of this entry in the #payment_trigger_map
*/
struct GNUNET_HashCode key;
/**
- * Associated heap node.
+ * At what time does this request expire? If set in the future, we
+ * may wait this long for a payment to arrive before responding.
*/
- struct GNUNET_CONTAINER_HeapNode *hn;
+ struct GNUNET_TIME_Absolute long_poll_timeout;
};
@@ -320,6 +326,11 @@ extern unsigned long long default_wire_fee_amortization;
extern struct GNUNET_CONTAINER_Heap *resume_timeout_heap;
/**
+ * Task responsible for timeouts in the #resume_timeout_heap.
+ */
+extern struct GNUNET_SCHEDULER_Task *resume_timeout_task;
+
+/**
* Hash map from H(order_id,merchant_pub) to `struct TMH_SuspendedConnection`
* entries to resume when a payment is made for the given order.
*/
@@ -397,4 +408,20 @@ TMH_compute_pay_key (const char *order_id,
struct GNUNET_HashCode *key);
+/**
+ * Create a taler://pay/ URI for the given @a con and @a order_id
+ * and @a session_id and @a instance_id.
+ *
+ * @param con HTTP connection
+ * @param order_id the order id
+ * @param session_id session, may be NULL
+ * @param instance_id instance, may be "default"
+ * @return corresponding taler://pay/ URI, or NULL on missing "host"
+ */
+char *
+TMH_make_taler_pay_uri (struct MHD_Connection *con,
+ const char *order_id,
+ const char *session_id,
+ const char *instance_id);
+
#endif
diff --git a/src/backend/taler-merchant-httpd_check-payment.c b/src/backend/taler-merchant-httpd_check-payment.c
index ac73a90b..fad3fdc6 100644
--- a/src/backend/taler-merchant-httpd_check-payment.c
+++ b/src/backend/taler-merchant-httpd_check-payment.c
@@ -48,9 +48,6 @@ struct CheckPaymentRequestContext
*/
struct TM_HandlerContext hc;
- /**
- * Connection we are processing a request for.
- */
struct MHD_Connection *connection;
/**
@@ -85,12 +82,6 @@ struct CheckPaymentRequestContext
const char *fulfillment_url;
/**
- * At what time does this request expire? If set in the future, we
- * may wait this long for a payment to arrive before responding.
- */
- struct GNUNET_TIME_Absolute long_poll_timeout;
-
- /**
* Contract terms of the payment we are checking. NULL when they
* are not (yet) known.
*/
@@ -142,67 +133,6 @@ cprc_cleanup (struct TM_HandlerContext *hc)
/**
- * Make a taler://pay URI
- *
- * @param cprc payment request context
- * @returns the URI, must be freed with #GNUNET_free
- */
-static char *
-make_taler_pay_uri (const struct CheckPaymentRequestContext *cprc)
-{
- const char *host;
- const char *forwarded_host;
- const char *uri_path;
- const char *uri_instance_id;
- const char *query;
- char *result;
-
- host = MHD_lookup_connection_value (cprc->connection,
- MHD_HEADER_KIND,
- "Host");
- forwarded_host = MHD_lookup_connection_value (cprc->connection,
- MHD_HEADER_KIND,
- "X-Forwarded-Host");
-
- uri_path = MHD_lookup_connection_value (cprc->connection,
- MHD_HEADER_KIND,
- "X-Forwarded-Prefix");
- if (NULL == uri_path)
- uri_path = "-";
- if (NULL != forwarded_host)
- host = forwarded_host;
- if (0 == strcmp (cprc->mi->id,
- "default"))
- uri_instance_id = "-";
- else
- uri_instance_id = cprc->mi->id;
- if (NULL == host)
- {
- /* Should never happen, at least the host header should be defined */
- GNUNET_break (0);
- return NULL;
- }
-
- if (GNUNET_YES == TALER_mhd_is_https (cprc->connection))
- query = "";
- else
- query = "?insecure=1";
- GNUNET_assert (NULL != cprc->order_id);
- GNUNET_assert (0 < GNUNET_asprintf (&result,
- "taler://pay/%s/%s/%s/%s%s%s%s",
- host,
- uri_path,
- uri_instance_id,
- cprc->order_id,
- (cprc->session_id == NULL) ? "" : "/",
- (cprc->session_id == NULL) ? "" :
- cprc->session_id,
- query));
- return result;
-}
-
-
-/**
* Function called with information about a refund.
* It is responsible for summing up the refund amount.
*
@@ -274,7 +204,10 @@ send_pay_request (const struct CheckPaymentRequestContext *cprc)
"db error fetching pay session info");
}
}
- taler_pay_uri = make_taler_pay_uri (cprc);
+ taler_pay_uri = TMH_make_taler_pay_uri (cprc->connection,
+ cprc->order_id,
+ cprc->session_id,
+ cprc->mi->id);
ret = TMH_RESPONSE_reply_json_pack (cprc->connection,
MHD_HTTP_OK,
"{s:s, s:s, s:b, s:s?}",
@@ -413,8 +346,6 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh,
if (NULL == cprc)
{
/* First time here, parse request and check order is known */
- const char *long_poll_timeout_s;
-
cprc = GNUNET_new (struct CheckPaymentRequestContext);
cprc->hc.cc = &cprc_cleanup;
cprc->ret = GNUNET_SYSERR;
@@ -433,31 +364,6 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh,
TALER_EC_PARAMETER_MISSING,
"order_id required");
}
- long_poll_timeout_s = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "timeout");
- if (NULL != long_poll_timeout_s)
- {
- unsigned int timeout;
-
- if (1 != sscanf (long_poll_timeout_s,
- "%u",
- &timeout))
- {
- GNUNET_break_op (0);
- return TMH_RESPONSE_reply_bad_request (connection,
- TALER_EC_PARAMETER_MALFORMED,
- "timeout must be non-negative number");
- }
- cprc->long_poll_timeout
- = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_SECONDS,
- timeout));
- }
- else
- {
- cprc->long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
- }
cprc->contract_url = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"contract_url");
diff --git a/src/backend/taler-merchant-httpd_poll-payment.c b/src/backend/taler-merchant-httpd_poll-payment.c
new file mode 100644
index 00000000..945a356f
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_poll-payment.c
@@ -0,0 +1,557 @@
+/*
+ This file is part of TALER
+ (C) 2017, 2019 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
+ 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
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/taler-merchant-httpd_poll-payment.c
+ * @brief implementation of /public/poll-payment handler
+ * @author Florian Dold
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <string.h>
+#include <microhttpd.h>
+#include <jansson.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"
+#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_responses.h"
+#include "taler-merchant-httpd_poll-payment.h"
+
+/**
+ * Maximum number of retries for database operations.
+ */
+#define MAX_RETRIES 5
+
+
+/**
+ * Data structure we keep for a check payment request.
+ */
+struct PollPaymentRequestContext
+{
+ /**
+ * Must be first for #handle_mhd_completion_callback.
+ */
+ struct TM_HandlerContext hc;
+
+ /**
+ * Entry in the #resume_timeout_heap for this check payment, if we are
+ * suspended.
+ */
+ struct TMH_SuspendedConnection sc;
+
+ /**
+ * Which merchant instance is this for?
+ */
+ struct MerchantInstance *mi;
+
+ /**
+ * URL where the final contract can be found for this payment.
+ */
+ char *final_contract_url;
+
+ /**
+ * order ID for the payment
+ */
+ const char *order_id;
+
+ /**
+ * Where to get the contract
+ */
+ const char *contract_url;
+
+ /**
+ * fulfillment URL of the contract (valid as long as
+ * @e contract_terms is valid).
+ */
+ const char *fulfillment_url;
+
+ /**
+ * session of the client
+ */
+ const char *session_id;
+
+ /**
+ * Contract terms of the payment we are checking. NULL when they
+ * are not (yet) known.
+ */
+ json_t *contract_terms;
+
+ /**
+ * Hash of @e contract_terms, set only once @e contract_terms
+ * is available.
+ */
+ struct GNUNET_HashCode h_contract_terms;
+
+ /**
+ * Total refunds granted for this payment. Only initialized
+ * if @e refunded is set to #GNUNET_YES.
+ */
+ struct TALER_Amount refund_amount;
+
+ /**
+ * Set to #GNUNET_YES if this payment has been refunded and
+ * @e refund_amount is initialized.
+ */
+ int refunded;
+
+ /**
+ * Initially #GNUNET_SYSERR. If we queued a response, set to the
+ * result code (i.e. #MHD_YES or #MHD_NO).
+ */
+ int ret;
+
+};
+
+
+/**
+ * Clean up the session state for a check payment request.
+ *
+ * @param hc must be a `struct PollPaymentRequestContext *`
+ */
+static void
+pprc_cleanup (struct TM_HandlerContext *hc)
+{
+ struct PollPaymentRequestContext *pprc = (struct
+ PollPaymentRequestContext *) hc;
+
+ if (NULL != pprc->contract_terms)
+ json_decref (pprc->contract_terms);
+ GNUNET_free_non_null (pprc->final_contract_url);
+ GNUNET_free (pprc);
+}
+
+
+/**
+ * Function called with information about a refund.
+ * It is responsible for summing up the refund amount.
+ *
+ * @param cls closure
+ * @param coin_pub public coin from which the refund comes from
+ * @param rtransaction_id identificator of the refund
+ * @param reason human-readable explaination of the refund
+ * @param refund_amount refund amount which is being taken from coin_pub
+ * @param refund_fee cost of this refund operation
+ */
+static void
+process_refunds_cb (void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t rtransaction_id,
+ const char *reason,
+ const struct TALER_Amount *refund_amount,
+ const struct TALER_Amount *refund_fee)
+{
+ struct PollPaymentRequestContext *pprc = cls;
+
+ if (pprc->refunded)
+ {
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_amount_add (&pprc->refund_amount,
+ &pprc->refund_amount,
+ refund_amount));
+ return;
+ }
+ pprc->refund_amount = *refund_amount;
+ pprc->refunded = GNUNET_YES;
+}
+
+
+/**
+ * Resume processing all suspended connections past timeout.
+ *
+ * @param cls unused
+ */
+static void
+do_resume (void *cls)
+{
+ struct TMH_SuspendedConnection *sc;
+
+ (void) cls;
+ resume_timeout_task = NULL;
+ while (1)
+ {
+ sc = GNUNET_CONTAINER_heap_peek (resume_timeout_heap);
+ if (NULL == sc)
+ return;
+ if (0 !=
+ GNUNET_TIME_absolute_get_remaining (
+ sc->long_poll_timeout).rel_value_us)
+ break;
+ GNUNET_assert (sc ==
+ GNUNET_CONTAINER_heap_remove_root (resume_timeout_heap));
+ sc->hn = NULL;
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CONTAINER_multihashmap_remove (payment_trigger_map,
+ &sc->key,
+ sc));
+ MHD_resume_connection (sc->con);
+ }
+ resume_timeout_task = GNUNET_SCHEDULER_add_at (sc->long_poll_timeout,
+ &do_resume,
+ NULL);
+}
+
+
+/**
+ * The client did not yet pay, send it the payment request.
+ *
+ * @param pprc check pay request context
+ * @return #MHD_YES on success
+ */
+static int
+send_pay_request (struct PollPaymentRequestContext *pprc)
+{
+ int ret;
+ char *already_paid_order_id = NULL;
+ char *taler_pay_uri;
+
+ if (0 !=
+ GNUNET_TIME_absolute_get_remaining (
+ pprc->sc.long_poll_timeout).rel_value_us)
+ {
+ struct TMH_SuspendedConnection *sc;
+
+ /* long polling: do not queue a response, suspend connection instead */
+ TMH_compute_pay_key (pprc->order_id,
+ &pprc->mi->pubkey,
+ &pprc->sc.key);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (payment_trigger_map,
+ &pprc->sc.key,
+ &pprc->sc,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
+ pprc->sc.hn = GNUNET_CONTAINER_heap_insert (resume_timeout_heap,
+ &pprc->sc,
+ pprc->sc.long_poll_timeout.
+ abs_value_us);
+ MHD_suspend_connection (pprc->sc.con);
+ if (NULL != resume_timeout_task)
+ {
+ GNUNET_SCHEDULER_cancel (resume_timeout_task);
+ resume_timeout_task = NULL;
+ }
+ sc = GNUNET_CONTAINER_heap_peek (resume_timeout_heap);
+ resume_timeout_task = GNUNET_SCHEDULER_add_at (sc->long_poll_timeout,
+ &do_resume,
+ NULL);
+ return MHD_YES;
+ }
+
+ /* Check if resource_id has been paid for in the same session
+ * with another order_id.
+ */
+ if ( (NULL != pprc->session_id) &&
+ (NULL != pprc->fulfillment_url) )
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db->find_session_info (db->cls,
+ &already_paid_order_id,
+ pprc->session_id,
+ pprc->fulfillment_url,
+ &pprc->mi->pubkey);
+ if (qs < 0)
+ {
+ /* single, read-only SQL statements should never cause
+ serialization problems */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ return TMH_RESPONSE_reply_internal_error (pprc->sc.con,
+ TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR,
+ "db error fetching pay session info");
+ }
+ }
+ taler_pay_uri = TMH_make_taler_pay_uri (pprc->sc.con,
+ pprc->order_id,
+ pprc->session_id,
+ pprc->mi->id);
+ ret = TMH_RESPONSE_reply_json_pack (pprc->sc.con,
+ MHD_HTTP_OK,
+ "{s:s, s:s, s:b, s:s?}",
+ "taler_pay_uri", taler_pay_uri,
+ "contract_url", pprc->final_contract_url,
+ "paid", 0,
+ "already_paid_order_id",
+ already_paid_order_id);
+ GNUNET_free (taler_pay_uri);
+ GNUNET_free_non_null (already_paid_order_id);
+ return ret;
+}
+
+
+/**
+ * Manages a /public/poll-payment call, checking the status
+ * of a payment and, if necessary, constructing the URL
+ * for a payment redirect URL.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param mi merchant backend instance, never NULL
+ * @return MHD result code
+ */
+int
+MH_handler_poll_payment (struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size,
+ struct MerchantInstance *mi)
+{
+ struct PollPaymentRequestContext *pprc = *connection_cls;
+ enum GNUNET_DB_QueryStatus qs;
+ int ret;
+
+ if (NULL == pprc)
+ {
+ /* First time here, parse request and check order is known */
+ const char *long_poll_timeout_s;
+ const char *cts;
+
+ pprc = GNUNET_new (struct PollPaymentRequestContext);
+ pprc->hc.cc = &pprc_cleanup;
+ pprc->ret = GNUNET_SYSERR;
+ pprc->sc.con = connection;
+ pprc->mi = mi;
+ *connection_cls = pprc;
+
+ pprc->order_id = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "order_id");
+ if (NULL == pprc->order_id)
+ {
+ /* order_id is required but missing */
+ GNUNET_break_op (0);
+ return TMH_RESPONSE_reply_bad_request (connection,
+ TALER_EC_PARAMETER_MISSING,
+ "order_id required");
+ }
+ cts = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "h_contract");
+ if (NULL == cts)
+ {
+ /* h_contract required but missing */
+ GNUNET_break_op (0);
+ return TMH_RESPONSE_reply_bad_request (connection,
+ TALER_EC_PARAMETER_MISSING,
+ "h_contract required");
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_hash_from_string (cts,
+ &pprc->h_contract_terms))
+ {
+ /* cts has wrong encoding */
+ GNUNET_break_op (0);
+ return TMH_RESPONSE_reply_bad_request (connection,
+ TALER_EC_PARAMETER_MALFORMED,
+ "h_contract malformed");
+ }
+ long_poll_timeout_s = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "timeout");
+ if (NULL != long_poll_timeout_s)
+ {
+ unsigned int timeout;
+
+ if (1 != sscanf (long_poll_timeout_s,
+ "%u",
+ &timeout))
+ {
+ GNUNET_break_op (0);
+ return TMH_RESPONSE_reply_bad_request (connection,
+ TALER_EC_PARAMETER_MALFORMED,
+ "timeout must be non-negative number");
+ }
+ pprc->sc.long_poll_timeout
+ = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_SECONDS,
+ timeout));
+ }
+ else
+ {
+ pprc->sc.long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
+ }
+ pprc->contract_url = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "contract_url");
+ if (NULL == pprc->contract_url)
+ {
+ pprc->final_contract_url = TALER_url_absolute_mhd (connection,
+ "/public/proposal",
+ "instance", mi->id,
+ "order_id",
+ pprc->order_id,
+ NULL);
+ GNUNET_assert (NULL != pprc->final_contract_url);
+ }
+ else
+ {
+ pprc->final_contract_url = GNUNET_strdup (pprc->contract_url);
+ }
+ pprc->session_id = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "session_id");
+
+ /* obtain contract terms, indirectly checking that the client's contract
+ terms hash is actually valid and known. */
+ db->preflight (db->cls);
+ qs = db->find_contract_terms_from_hash (db->cls,
+ &pprc->contract_terms,
+ &pprc->h_contract_terms,
+ &mi->pubkey);
+ if (0 > qs)
+ {
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ return TMH_RESPONSE_reply_internal_error (connection,
+ TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
+ "Merchant database error");
+ }
+ if (0 == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Contract unknown\n");
+ return TMH_RESPONSE_reply_not_found (connection,
+ TALER_EC_POLL_PAYMENT_CONTRACT_NOT_FOUND,
+ "Given order_id doesn't map to any proposal");
+ }
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("fulfillment_url",
+ &pprc->fulfillment_url),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (pprc->contract_terms,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return TMH_RESPONSE_reply_internal_error (connection,
+ TALER_EC_CHECK_PAYMENT_DB_FETCH_CONTRACT_TERMS_ERROR,
+ "Merchant database error (contract terms corrupted)");
+ }
+ }
+
+ } /* end of first-time initialization / sanity checks */
+
+
+ db->preflight (db->cls);
+
+ /* Check if the order has been paid for. */
+ if (NULL != pprc->session_id)
+ {
+ /* Check if paid within a session. */
+ char *already_paid_order_id = NULL;
+
+ qs = db->find_session_info (db->cls,
+ &already_paid_order_id,
+ pprc->session_id,
+ pprc->fulfillment_url,
+ &mi->pubkey);
+ if (qs < 0)
+ {
+ /* single, read-only SQL statements should never cause
+ serialization problems */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ return TMH_RESPONSE_reply_internal_error (connection,
+ TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR,
+ "db error fetching pay session info");
+ }
+ else if (0 == qs)
+ {
+ ret = send_pay_request (pprc);
+ GNUNET_free_non_null (already_paid_order_id);
+ return ret;
+ }
+ GNUNET_break (1 == qs);
+ GNUNET_break (0 == strcmp (pprc->order_id,
+ already_paid_order_id));
+ GNUNET_free_non_null (already_paid_order_id);
+ }
+ else
+ {
+ /* Check if paid regardless of session. */
+ json_t *xcontract_terms = NULL;
+
+ qs = db->find_paid_contract_terms_from_hash (db->cls,
+ &xcontract_terms,
+ &pprc->h_contract_terms,
+ &mi->pubkey);
+ if (0 > qs)
+ {
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ return TMH_RESPONSE_reply_internal_error (connection,
+ TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
+ "Merchant database error");
+ }
+ if (0 == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "not paid yet\n");
+ return send_pay_request (pprc);
+ }
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ GNUNET_assert (NULL != xcontract_terms);
+ json_decref (xcontract_terms);
+ }
+
+ /* Accumulate refunds, if any. */
+ for (unsigned int i = 0; i<MAX_RETRIES; i++)
+ {
+ qs = db->get_refunds_from_contract_terms_hash (db->cls,
+ &mi->pubkey,
+ &pprc->h_contract_terms,
+ &process_refunds_cb,
+ pprc);
+ if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+ break;
+ }
+ if (0 > qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database hard error on refunds_from_contract_terms_hash lookup: %s\n",
+ GNUNET_h2s (&pprc->h_contract_terms));
+ return TMH_RESPONSE_reply_internal_error (connection,
+ TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
+ "Merchant database error");
+ }
+ if (pprc->refunded)
+ return TMH_RESPONSE_reply_json_pack (connection,
+ MHD_HTTP_OK,
+ "{s:b, s:b, s:o}",
+ "paid", 1,
+ "refunded", pprc->refunded,
+ "refund_amount",
+ TALER_JSON_from_amount (
+ &pprc->refund_amount));
+ return TMH_RESPONSE_reply_json_pack (connection,
+ MHD_HTTP_OK,
+ "{s:b, s:b }",
+ "paid", 1,
+ "refunded", 0);
+}
diff --git a/src/backend/taler-merchant-httpd_poll-payment.h b/src/backend/taler-merchant-httpd_poll-payment.h
new file mode 100644
index 00000000..e9f54c26
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_poll-payment.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ (C) 2017 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
+ 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
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/taler-merchant-httpd_poll-payment.h
+ * @brief headers for /public/poll-payment handler
+ * @author Christian Grothoff
+ * @author Florian Dold
+ */
+#ifndef TALER_MERCHANT_HTTPD_POLL_PAYMENT_H
+#define TALER_MERCHANT_HTTPD_POLL_PAYMENT_H
+#include <microhttpd.h>
+#include "taler-merchant-httpd.h"
+
+/**
+ * Manages a /public/poll-payment call, checking the status
+ * of a payment.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param mi merchant backend instance, never NULL
+ * @return MHD result code
+ */
+int
+MH_handler_poll_payment (struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size,
+ struct MerchantInstance *mi);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_tip-pickup.c b/src/backend/taler-merchant-httpd_tip-pickup.c
index bead3419..d53596cc 100644
--- a/src/backend/taler-merchant-httpd_tip-pickup.c
+++ b/src/backend/taler-merchant-httpd_tip-pickup.c
@@ -649,7 +649,9 @@ MH_handler_tip_pickup_get (struct TMH_RequestHandler *rh,
"tip_id required");
}
- if (GNUNET_OK != GNUNET_CRYPTO_hash_from_string (tip_id_str, &tip_id))
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_hash_from_string (tip_id_str,
+ &tip_id))
{
/* tip_id has wrong encoding */
GNUNET_break_op (0);