summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_private-get-rewards-ID.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/taler-merchant-httpd_private-get-rewards-ID.c')
-rw-r--r--src/backend/taler-merchant-httpd_private-get-rewards-ID.c395
1 files changed, 395 insertions, 0 deletions
diff --git a/src/backend/taler-merchant-httpd_private-get-rewards-ID.c b/src/backend/taler-merchant-httpd_private-get-rewards-ID.c
new file mode 100644
index 00000000..78b6c2d0
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-rewards-ID.c
@@ -0,0 +1,395 @@
+/*
+ This file is part of TALER
+ (C) 2017-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ 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 taler-merchant-httpd_get-rewards-ID.c
+ * @brief implementation of a GET /rewards/ID handler
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h>
+#include <jansson.h>
+#include <taler/taler_dbevents.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_exchanges.h"
+
+
+/**
+ * Information we keep per /kyc request.
+ */
+struct RewardContext
+{
+ /**
+ * Stored in a DLL.
+ */
+ struct RewardContext *next;
+
+ /**
+ * Stored in a DLL.
+ */
+ struct RewardContext *prev;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Our handler context.
+ */
+ struct TMH_HandlerContext *hc;
+
+ /**
+ * Database event we are waiting on to be resuming.
+ */
+ struct GNUNET_DB_EventHandler *eh;
+
+ /**
+ * Response to return, NULL if we don't have one yet.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * When does this request time out?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * ID of the reward being queried.
+ */
+ struct TALER_RewardIdentifierP reward_id;
+
+ /**
+ * Minimum reward amount picked up we should return to the
+ * client.
+ */
+ struct TALER_Amount min_amount;
+
+ /**
+ * #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.
+ */
+ enum GNUNET_GenericReturnValue suspended;
+
+ /**
+ * Is the "pickups" argument set to "yes"?
+ */
+ bool fpu;
+
+};
+
+
+/**
+ * Head of DLL.
+ */
+static struct RewardContext *tc_head;
+
+/**
+ * Tail of DLL.
+ */
+static struct RewardContext *tc_tail;
+
+
+void
+TMH_force_reward_resume ()
+{
+ for (struct RewardContext *tc = tc_head;
+ NULL != tc;
+ tc = tc->next)
+ {
+ if (GNUNET_YES == tc->suspended)
+ {
+ tc->suspended = GNUNET_SYSERR;
+ MHD_resume_connection (tc->connection);
+ }
+ }
+}
+
+
+/**
+ * Custom cleanup routine for a `struct RewardContext`.
+ *
+ * @param cls the `struct RewardContext` to clean up.
+ */
+static void
+reward_context_cleanup (void *cls)
+{
+ struct RewardContext *tc = cls;
+
+ if (NULL != tc->response)
+ {
+ MHD_destroy_response (tc->response);
+ tc->response = NULL;
+ }
+ if (NULL != tc->eh)
+ {
+ TMH_db->event_listen_cancel (tc->eh);
+ tc->eh = NULL;
+ }
+ GNUNET_CONTAINER_DLL_remove (tc_head,
+ tc_tail,
+ tc);
+ GNUNET_free (tc);
+}
+
+
+/**
+ * We have received a trigger from the database
+ * that we should (possibly) resume the request.
+ *
+ * @param cls a `struct RewardContext` to resume
+ * @param extra usually NULL
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+resume_by_event (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ struct RewardContext *tc = cls;
+
+ (void) extra;
+ (void) extra_size;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming request %p by trigger\n",
+ tc);
+ if (GNUNET_NO == tc->suspended)
+ return; /* duplicate event is possible */
+ tc->suspended = GNUNET_NO;
+ GNUNET_CONTAINER_DLL_remove (tc_head,
+ tc_tail,
+ tc);
+ MHD_resume_connection (tc->connection);
+ TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
+}
+
+
+MHD_RESULT
+TMH_private_get_rewards_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct RewardContext *tc = hc->ctx;
+ struct TALER_Amount total_authorized;
+ struct TALER_Amount total_picked_up;
+ char *reason;
+ struct GNUNET_TIME_Timestamp expiration;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ unsigned int pickups_length = 0;
+ struct TALER_MERCHANTDB_PickupDetails *pickups = NULL;
+ enum GNUNET_DB_QueryStatus qs;
+ json_t *pickups_json = NULL;
+
+ (void) rh;
+ if (NULL == tc)
+ {
+ tc = GNUNET_new (struct RewardContext);
+ hc->ctx = tc;
+ hc->cc = &reward_context_cleanup;
+ GNUNET_CONTAINER_DLL_insert (tc_head,
+ tc_tail,
+ tc);
+ tc->connection = connection;
+ tc->hc = hc;
+ GNUNET_assert (NULL != hc->infix);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_hash_from_string (hc->infix,
+ &tc->reward_id.hash))
+ {
+ /* reward_id has wrong encoding */
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ hc->infix);
+ }
+ {
+ const char *pstr;
+
+ pstr = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "pickups");
+ tc->fpu = (NULL != pstr)
+ ? 0 == strcasecmp (pstr, "yes")
+ : false;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TMH_currency,
+ &tc->min_amount));
+ {
+ const char *min_amount;
+
+ min_amount = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "min_amount");
+ if (NULL != min_amount)
+ {
+ if (GNUNET_OK !=
+ TALER_string_to_amount (min_amount,
+ &tc->min_amount))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "min_amount");
+ }
+ if (0 !=
+ strcasecmp (tc->min_amount.currency,
+ TMH_currency))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ TMH_currency);
+ }
+ }
+ }
+ TALER_MHD_parse_request_timeout (connection,
+ &tc->timeout);
+ if (! GNUNET_TIME_absolute_is_future (tc->timeout))
+ {
+ struct TMH_RewardPickupEventP reward_eh = {
+ .header.size = htons (sizeof (reward_eh)),
+ .header.type = htons (TALER_DBEVENT_MERCHANT_REWARD_PICKUP),
+ .reward_id = tc->reward_id
+ };
+
+ GNUNET_CRYPTO_hash (hc->instance->settings.id,
+ strlen (hc->instance->settings.id),
+ &reward_eh.h_instance);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Subscribing to payment triggers for %p\n",
+ tc);
+ tc->eh = TMH_db->event_listen (
+ TMH_db->cls,
+ &reward_eh.header,
+ GNUNET_TIME_absolute_get_remaining (tc->timeout),
+ &resume_by_event,
+ tc);
+ }
+ }
+
+ GNUNET_assert (GNUNET_YES != tc->suspended);
+ TMH_db->preflight (TMH_db->cls);
+ qs = TMH_db->lookup_reward_details (TMH_db->cls,
+ hc->instance->settings.id,
+ &tc->reward_id,
+ tc->fpu,
+ &total_authorized,
+ &total_picked_up,
+ &reason,
+ &expiration,
+ &reserve_pub,
+ &pickups_length,
+ &pickups);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ unsigned int response_code;
+ enum TALER_ErrorCode ec;
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ ec = TALER_EC_MERCHANT_GENERIC_REWARD_ID_UNKNOWN;
+ response_code = MHD_HTTP_NOT_FOUND;
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
+ response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ ec = TALER_EC_GENERIC_DB_COMMIT_FAILED;
+ response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ default:
+ GNUNET_break (0);
+ ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+ return TALER_MHD_reply_with_error (connection,
+ response_code,
+ ec,
+ NULL);
+ }
+ /* do not allow timeout above reward expiration */
+ tc->timeout = GNUNET_TIME_absolute_min (tc->timeout,
+ expiration.abs_time);
+ if ( (NULL != tc->eh) &&
+ (GNUNET_TIME_absolute_is_future (tc->timeout)) &&
+ (1 == TALER_amount_cmp (&tc->min_amount,
+ &total_picked_up)) )
+ {
+ MHD_suspend_connection (connection);
+ tc->suspended = GNUNET_YES;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Suspending REWARD request handling as pickup is below threshold requested by client\n");
+ GNUNET_array_grow (pickups,
+ pickups_length,
+ 0);
+ GNUNET_free (reason);
+ return MHD_YES;
+ }
+ if (tc->fpu)
+ {
+ pickups_json = json_array ();
+ GNUNET_assert (NULL != pickups_json);
+ for (unsigned int i = 0; i<pickups_length; i++)
+ {
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ pickups_json,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("pickup_id",
+ &pickups[i].pickup_id),
+ GNUNET_JSON_pack_uint64 ("num_planchets",
+ pickups[i].num_planchets),
+ TALER_JSON_pack_amount ("requested_amount",
+ &pickups[i].requested_amount))));
+ }
+ }
+ GNUNET_array_grow (pickups,
+ pickups_length,
+ 0);
+ {
+ MHD_RESULT ret;
+
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_amount ("total_authorized",
+ &total_authorized),
+ TALER_JSON_pack_amount ("total_picked_up",
+ &total_picked_up),
+ GNUNET_JSON_pack_string ("reason",
+ reason),
+ GNUNET_JSON_pack_timestamp ("expiration",
+ expiration),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &reserve_pub),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("pickups",
+ pickups_json)));
+ GNUNET_free (reason);
+ return ret;
+ }
+}