summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-04-27 04:53:17 +0200
committerChristian Grothoff <christian@grothoff.org>2020-04-27 04:53:17 +0200
commit20853863335ee4cd6a43044bfa1c8b38361cbc1f (patch)
treebb9edca91007d98f93d6ef1439e6d038fa7533cd /src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
parent68c440b0eb385ad80dec73843e021039b91b913c (diff)
downloadmerchant-20853863335ee4cd6a43044bfa1c8b38361cbc1f.tar.gz
merchant-20853863335ee4cd6a43044bfa1c8b38361cbc1f.tar.bz2
merchant-20853863335ee4cd6a43044bfa1c8b38361cbc1f.zip
rename fest to match new structure
Diffstat (limited to 'src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c')
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c379
1 files changed, 379 insertions, 0 deletions
diff --git a/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c b/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
new file mode 100644
index 00000000..5324c619
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
@@ -0,0 +1,379 @@
+/*
+ This file is part of TALER
+ (C) 2014-2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ 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
+ 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_refund_increase.c
+ * @brief Handle request to increase the refund for an order
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd.h"
+#include "taler-merchant-httpd_refund.h"
+
+/**
+ * How often do we retry the non-trivial refund INSERT database
+ * transaction?
+ */
+#define MAX_RETRIES 5
+
+
+/**
+ * Information we keep for individual calls
+ * to requests that parse JSON, but keep no other state.
+ */
+struct TMH_JsonParseContext
+{
+
+ /**
+ * This field MUST be first for handle_mhd_completion_callback() to work
+ * when it treats this struct as a `struct TM_HandlerContext`.
+ */
+ struct TM_HandlerContext hc;
+
+ /**
+ * Placeholder for #TALER_MHD_parse_post_json() to keep its internal state.
+ */
+ void *json_parse_context;
+};
+
+
+/**
+ * Make a taler://refund URI
+ *
+ * @param connection MHD connection to take host and path from
+ * @param instance_id merchant's instance ID, must not be NULL
+ * @param order_id order ID to show a refund for, must not be NULL
+ * @returns the URI, must be freed with #GNUNET_free
+ */
+static char *
+make_taler_refund_uri (struct MHD_Connection *connection,
+ const char *instance_id,
+ const char *order_id)
+{
+ const char *host;
+ const char *forwarded_host;
+ const char *uri_path;
+ const char *uri_instance_id;
+ const char *query;
+ char *result;
+
+ GNUNET_assert (NULL != instance_id);
+ GNUNET_assert (NULL != order_id);
+ host = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_HOST);
+ forwarded_host = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Host");
+ if (NULL != forwarded_host)
+ host = forwarded_host;
+ if (NULL == host)
+ {
+ /* Should never happen, at least the host header should be defined */
+ GNUNET_break (0);
+ return NULL;
+ }
+ uri_path = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Prefix");
+ if (NULL == uri_path)
+ uri_path = "-";
+ if (0 == strcmp (instance_id,
+ "default"))
+ uri_instance_id = "-";
+ else
+ uri_instance_id = instance_id;
+ if (GNUNET_YES == TALER_mhd_is_https (connection))
+ query = "";
+ else
+ query = "?insecure=1";
+ GNUNET_assert (0 < GNUNET_asprintf (&result,
+ "taler://refund/%s/%s/%s/%s%s",
+ host,
+ uri_path,
+ uri_instance_id,
+ order_id,
+ query));
+ return result;
+}
+
+
+/**
+ * Custom cleanup routine for a `struct TMH_JsonParseContext`.
+ *
+ * @param hc the `struct TMH_JsonParseContext` to clean up.
+ */
+static void
+json_parse_cleanup (struct TM_HandlerContext *hc)
+{
+ struct TMH_JsonParseContext *jpc = (struct TMH_JsonParseContext *) hc;
+
+ TALER_MHD_parse_post_cleanup_callback (jpc->json_parse_context);
+ GNUNET_free (jpc);
+}
+
+
+/**
+ * Process a refund request.
+ *
+ * @param connection HTTP client connection
+ * @param mi merchant instance doing the processing
+ * @param refund amount to be refunded
+ * @param order_id for which order is the refund
+ * @param reason reason for the refund
+ * @return MHD result code
+ */
+static MHD_RESULT
+process_refund (struct MHD_Connection *connection,
+ struct MerchantInstance *mi,
+ const struct TALER_Amount *refund,
+ const char *order_id,
+ const char *reason)
+{
+ json_t *contract_terms;
+ enum GNUNET_DB_QueryStatus qs;
+ enum GNUNET_DB_QueryStatus qsx;
+ struct GNUNET_HashCode h_contract_terms;
+
+ db->preflight (db->cls);
+ /* Convert order id to h_contract_terms */
+ qs = db->find_contract_terms (db->cls,
+ &contract_terms,
+ order_id,
+ &mi->pubkey);
+ if (0 > qs)
+ {
+ /* 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 TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_REFUND_LOOKUP_DB_ERROR,
+ "An error occurred while retrieving payment data from db");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Unknown order id given: `%s'\n",
+ order_id);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_REFUND_ORDER_ID_UNKNOWN,
+ "order_id not found in database");
+ }
+
+ if (GNUNET_OK !=
+ TALER_JSON_hash (contract_terms,
+ &h_contract_terms))
+ {
+ GNUNET_break (0);
+ json_decref (contract_terms);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_INTERNAL_LOGIC_ERROR,
+ "Could not hash contract terms");
+ }
+ json_decref (contract_terms);
+ for (unsigned int i = 0; i<MAX_RETRIES; i++)
+ {
+ if (GNUNET_OK !=
+ db->start (db->cls,
+ "increase refund"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ qs = db->increase_refund_for_contract_NT (db->cls,
+ &h_contract_terms,
+ &mi->pubkey,
+ refund,
+ reason);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "increase refund returned %d\n",
+ qs);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ GNUNET_break (0);
+ db->rollback (db->cls);
+ break;
+ }
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ db->rollback (db->cls);
+ continue;
+ }
+ /* Got one or more deposits */
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ db->rollback (db->cls);
+ break;
+ }
+ qsx = db->commit (db->cls);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qsx)
+ {
+ GNUNET_break (0);
+ qs = qsx;
+ break;
+ }
+ if (GNUNET_DB_STATUS_SOFT_ERROR != qsx)
+ break;
+ }
+ if (0 > qs)
+ {
+ /* Special report if retries insufficient */
+ 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 TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_REFUND_MERCHANT_DB_COMMIT_ERROR,
+ "Internal database error or refund amount too big");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Refusing refund amount %s that is larger than original payment\n",
+ TALER_amount2s (refund));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_REFUND_INCONSISTENT_AMOUNT,
+ "Amount above payment");
+ }
+
+ /* Resume /public/poll-payments clients that may wait for this refund */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Awakeing clients on %s waiting for refund of less than %s\n",
+ order_id,
+ TALER_amount2s (refund));
+
+ TMH_long_poll_resume (order_id,
+ &mi->pubkey,
+ refund);
+
+ {
+ MHD_RESULT ret;
+ char *taler_refund_uri;
+
+ taler_refund_uri = make_taler_refund_uri (connection,
+ mi->id,
+ order_id);
+ ret = TALER_MHD_reply_json_pack (
+ connection,
+ MHD_HTTP_OK,
+ "{s:o, s:s}",
+ "h_contract_terms",
+ GNUNET_JSON_from_data_auto (&h_contract_terms),
+ "taler_refund_url",
+ taler_refund_uri);
+ GNUNET_free (taler_refund_uri);
+ return ret;
+ }
+}
+
+
+/**
+ * Handle request for increasing the refund associated with
+ * a contract.
+ *
+ * @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
+ */
+MHD_RESULT
+MH_handler_refund_increase (struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size,
+ struct MerchantInstance *mi)
+{
+ enum GNUNET_GenericReturnValue res;
+ struct TMH_JsonParseContext *ctx;
+ struct TALER_Amount refund;
+ const char *order_id;
+ const char *reason;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("refund", &refund),
+ GNUNET_JSON_spec_string ("order_id", &order_id),
+ GNUNET_JSON_spec_string ("reason", &reason),
+ GNUNET_JSON_spec_end ()
+ };
+ json_t *root;
+
+ if (NULL == *connection_cls)
+ {
+ ctx = GNUNET_new (struct TMH_JsonParseContext);
+ ctx->hc.cc = &json_parse_cleanup;
+ *connection_cls = ctx;
+ }
+ else
+ {
+ ctx = *connection_cls;
+ }
+
+ res = TALER_MHD_parse_post_json (connection,
+ &ctx->json_parse_context,
+ upload_data,
+ upload_data_size,
+ &root);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO;
+ /* the POST's body has to be further fetched */
+ if ( (GNUNET_NO == res) ||
+ (NULL == root) )
+ return MHD_YES;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ json_decref (root);
+ return MHD_YES;
+ }
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break_op (0);
+ json_decref (root);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_JSON_INVALID,
+ "Request body does not match specification");
+ }
+ {
+ MHD_RESULT ret;
+
+ ret = process_refund (connection,
+ mi,
+ &refund,
+ order_id,
+ reason);
+ GNUNET_JSON_parse_free (spec);
+ json_decref (root);
+ return ret;
+ }
+}
+
+
+/* end of taler-merchant-httpd_refund_increase.c */