summaryrefslogtreecommitdiff
path: root/src/testing/testing_api_cmd_post_transfers.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/testing/testing_api_cmd_post_transfers.c')
-rw-r--r--src/testing/testing_api_cmd_post_transfers.c473
1 files changed, 473 insertions, 0 deletions
diff --git a/src/testing/testing_api_cmd_post_transfers.c b/src/testing/testing_api_cmd_post_transfers.c
new file mode 100644
index 00000000..f5087c2d
--- /dev/null
+++ b/src/testing/testing_api_cmd_post_transfers.c
@@ -0,0 +1,473 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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 lib/testing_api_cmd_post_transfers.c
+ * @brief command to test POST /transfers
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "POST /transfers" CMD.
+ */
+struct PostTransfersState
+{
+
+ /**
+ * Handle for a "POST /transfers" request.
+ */
+ struct TALER_MERCHANT_PostTransfersHandle *pth;
+
+ /**
+ * Handle for a "GET" bank account history request.
+ */
+ struct TALER_BANK_DebitHistoryHandle *dhh;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * URL of the bank to run history on.
+ */
+ const char *bank_url;
+
+ /**
+ * URL of the bank to run history on (set once @e found is set).
+ */
+ char *exchange_url;
+
+ /**
+ * Payto URI to filter on.
+ */
+ const char *payto_uri;
+
+ /**
+ * Authentication details to authenticate to the bank.
+ */
+ struct TALER_BANK_AuthenticationData auth;
+
+ /**
+ * Set once we discovered the WTID and thus @e found is true.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ /**
+ * the credit amount to look for at @e bank_url.
+ */
+ struct TALER_Amount credit_amount;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Array of deposit command labels we expect to see aggregated.
+ */
+ const char **deposits;
+
+ /**
+ * Length of @e deposits.
+ */
+ unsigned int deposits_length;
+
+ /**
+ * Set to true once @e wtid and @e exchange_url are initialized.
+ */
+ bool found;
+};
+
+
+/**
+ * Callback for a POST /transfers operation.
+ *
+ * @param cls closure for this function
+ * @param hr HTTP response details
+ * @param execution_time when did the transfer happen (according to the exchange),
+ * #GNUNET_TIME_UNIT_FOREVER_ABS if the transfer did not yet happen or if
+ * we have no data from the exchange about it
+ * @param total_amount total amount of the wire transfer, or NULL if the exchange did
+ * not provide any details
+ * @param wire_fee how much did the exchange charge in terms of wire fees, or NULL
+ * if the exchange did not provide any details
+ * @param details_length length of the @a details array
+ * @param details array with details about the combined transactions
+ */
+static void
+transfers_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ struct GNUNET_TIME_Absolute execution_time,
+ const struct TALER_Amount *total_amount,
+ const struct TALER_Amount *wire_fee,
+ unsigned int details_length,
+ const struct TALER_MERCHANT_TrackTransferDetail details[])
+{
+ struct PostTransfersState *pts = cls;
+
+ pts->pth = NULL;
+ if (pts->http_status != hr->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ hr->http_status,
+ (int) hr->ec,
+ TALER_TESTING_interpreter_get_current_label (pts->is));
+ TALER_TESTING_interpreter_fail (pts->is);
+ return;
+ }
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ struct TALER_Amount total;
+
+ if (0 >
+ TALER_amount_subtract (&total,
+ total_amount,
+ wire_fee))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pts->is);
+ return;
+ }
+ if (0 !=
+ TALER_amount_cmp (&total,
+ &pts->credit_amount))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pts->is);
+ return;
+ }
+ TALER_amount_get_zero (total.currency,
+ &total);
+ for (unsigned int i = 0; i<details_length; i++)
+ {
+ const struct TALER_MERCHANT_TrackTransferDetail *tdd = &details[i];
+ struct TALER_Amount sum;
+ struct TALER_Amount fees;
+
+ TALER_amount_get_zero (tdd->deposit_value.currency,
+ &sum);
+ TALER_amount_get_zero (tdd->deposit_fee.currency,
+ &fees);
+ for (unsigned int j = 0; j<pts->deposits_length; j++)
+ {
+ const char *label = pts->deposits[j];
+ const struct TALER_TESTING_Command *cmd;
+ const json_t *contract_terms;
+ const struct TALER_Amount *deposit_value;
+ const char *order_id;
+
+ cmd = TALER_TESTING_interpreter_lookup_command (pts->is,
+ label);
+ if (NULL == cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pts->is);
+ return;
+ }
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_contract_terms (cmd,
+ 0,
+ &contract_terms)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_amount_obj (cmd,
+ 0,
+ &deposit_value)) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pts->is);
+ return;
+ }
+ order_id = json_string_value (json_object_get (contract_terms,
+ "order_id"));
+ if (NULL == order_id)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pts->is);
+ return;
+ }
+ if (0 != strcmp (tdd->order_id,
+ order_id))
+ continue;
+ if (0 >
+ TALER_amount_add (&sum,
+ &sum,
+ deposit_value))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pts->is);
+ return;
+ }
+ // FIXME #6236: also would want to add deposit_fees, but unavailable as traits right now!
+ }
+ if (0 !=
+ TALER_amount_cmp (&sum,
+ &tdd->deposit_value))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pts->is);
+ return;
+ }
+#if FIXME6236
+ if (0 !=
+ TALER_amount_cmp (&fees,
+ tdd->deposit_fee))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pts->is);
+ return;
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&total,
+ &total,
+ &tdd->deposit_value));
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&total,
+ &total,
+ &tdd->deposit_fee));
+#endif
+ }
+#if FIXME6236
+ if (0 !=
+ TALER_amount_cmp (&total,
+ pts->credit_amount))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pts->is);
+ return;
+ }
+#endif
+ break;
+ }
+ default:
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status.\n");
+ }
+ TALER_TESTING_interpreter_next (pts->is);
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of asking
+ * the bank for the debit transaction history.
+ *
+ * @param cls closure with a `struct PostTransfersState *`
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
+ * 0 if the bank's reply is bogus (fails to follow the protocol),
+ * #MHD_HTTP_NO_CONTENT if there are no more results; on success the
+ * last callback is always of this status (even if `abs(num_results)` were
+ * already returned).
+ * @param ec detailed error code
+ * @param serial_id monotonically increasing counter corresponding to the transaction
+ * @param details details about the wire transfer
+ * @param json detailed response from the HTTPD, or NULL if reply was not in JSON
+ * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
+ */
+static int
+debit_cb (
+ void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ uint64_t serial_id,
+ const struct TALER_BANK_DebitDetails *details,
+ const json_t *json)
+{
+ struct PostTransfersState *pts = cls;
+
+ if (MHD_HTTP_NO_CONTENT == http_status)
+ {
+ pts->dhh = NULL;
+ if (! pts->found)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pts->is);
+ return GNUNET_OK;
+ }
+ GNUNET_assert (NULL != pts->exchange_url);
+ pts->pth = TALER_MERCHANT_transfers_post (pts->is->ctx,
+ pts->merchant_url,
+ &pts->credit_amount,
+ &pts->wtid,
+ pts->payto_uri,
+ pts->exchange_url,
+ &transfers_cb,
+ pts);
+ return GNUNET_OK;
+ }
+ if (MHD_HTTP_OK != http_status)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pts->is);
+ pts->dhh = NULL;
+ return GNUNET_SYSERR;
+ }
+ if (pts->found)
+ return GNUNET_OK;
+ if (0 != TALER_amount_cmp (&pts->credit_amount,
+ &details->amount))
+ return GNUNET_OK;
+ if ( (NULL != pts->payto_uri) &&
+ (0 != strcasecmp (pts->payto_uri,
+ details->credit_account_url)) )
+ return GNUNET_OK;
+ pts->found = true;
+ pts->wtid = details->wtid;
+ pts->exchange_url = GNUNET_strdup (details->exchange_base_url);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Run the "POST /transfers" CMD. First, get the bank history to find
+ * the wtid.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_transfers_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PostTransfersState *pts = cls;
+
+ pts->is = is;
+ pts->dhh = TALER_BANK_debit_history (is->ctx,
+ NULL,
+ UINT64_MAX,
+ -INT64_MAX,
+ &debit_cb,
+ pts);
+ GNUNET_assert (NULL != pts->dhh);
+}
+
+
+/**
+ * Free the state of a "POST product" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+post_transfers_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PostTransfersState *pts = cls;
+
+ if (NULL != pts->pth)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "POST /transfers operation did not complete\n");
+ TALER_MERCHANT_transfers_post_cancel (pts->pth);
+ }
+ if (NULL != pts->dhh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "GET debit history operation did not complete\n");
+ TALER_BANK_debit_history_cancel (pts->dhh);
+ }
+ GNUNET_array_grow (pts->deposits,
+ pts->deposits_length,
+ 0);
+ GNUNET_free_non_null (pts->exchange_url);
+ GNUNET_free (pts);
+}
+
+
+/**
+ * Define a POST /transfers CMD. Details like the WTID and
+ * other required parameters will be extracted from the bank
+ * history, using the latest transfer of the specified
+ * @a credit_amount to the @a merchant_url.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the backend serving the
+ * "refund increase" request.
+ * @param auth credentials to access the exchange's bank account
+ * @param bank_url URL of the exchange's bank account
+ * @param credit_amount amount credited
+ * @param http_code expected HTTP response code
+ * @param ... NULL-terminated list of labels (const char *) of
+ * deposit (commands) we expect to be aggregated in the transfer
+ * (assuming @a http_code is #MHD_HTTP_OK)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_transfer (
+ const char *label,
+ const struct TALER_BANK_AuthenticationData *auth,
+ const char *bank_url,
+ const char *merchant_url,
+ const char *credit_amount,
+ unsigned int http_code,
+ ...)
+{
+ struct PostTransfersState *pts;
+
+ pts = GNUNET_new (struct PostTransfersState);
+ pts->bank_url = bank_url;
+ pts->merchant_url = merchant_url;
+ pts->auth = *auth;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (credit_amount,
+ &pts->credit_amount));
+ pts->http_status = http_code;
+ {
+ const char *clabel;
+ va_list ap;
+
+ va_start (ap, http_code);
+ while (NULL != (clabel = va_arg (ap, const char *)))
+ {
+ GNUNET_array_append (pts->deposits,
+ pts->deposits_length,
+ clabel);
+ }
+ va_end (ap);
+ }
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pts,
+ .label = label,
+ .run = &post_transfers_run,
+ .cleanup = &post_transfers_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_post_transfers.c */