diff options
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.c | 395 |
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; + } +} |