/* This file is part of TALER Copyright (C) 2020, 2023, 2024 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 */ /** * @file testing_api_cmd_post_transfers.c * @brief command to test POST /transfers * @author Christian Grothoff */ #include "platform.h" #include #include #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. */ char *exchange_url; /** * Credit account of the merchant. */ struct TALER_FullPayto credit_account; /** * Payto URI to filter on. */ struct TALER_FullPayto payto_uri; /** * Set to the hash of the @e payto_uri. */ struct TALER_FullPaytoHashP h_payto; /** * Set to the hash of the normalized @e payto_uri. */ struct TALER_NormalizedPaytoHashP h_normalized_payto; /** * Authentication details to authenticate to the bank. */ struct TALER_BANK_AuthenticationData auth; /** * Set once we discovered the WTID. */ 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; /** * Serial number of the wire transfer in the merchant backend, * set by #TALER_TESTING_cmd_merchant_get_transfers(). 0 if unknown. */ uint64_t serial; /** * Length of @e deposits. */ unsigned int deposits_length; }; /** * Callback for a POST /transfers operation. * * @param cls closure for this function * @param ptr response details */ static void transfers_cb (void *cls, const struct TALER_MERCHANT_PostTransfersResponse *ptr) { struct PostTransfersState *pts = cls; pts->pth = NULL; if (pts->http_status != ptr->hr.http_status) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u (%d) to command %s\n", ptr->hr.http_status, (int) ptr->hr.ec, TALER_TESTING_interpreter_get_current_label (pts->is)); GNUNET_break (0); TALER_TESTING_interpreter_fail (pts->is); return; } switch (ptr->hr.http_status) { case MHD_HTTP_NO_CONTENT: break; case MHD_HTTP_UNAUTHORIZED: break; case MHD_HTTP_NOT_FOUND: break; case MHD_HTTP_GATEWAY_TIMEOUT: break; default: GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unhandled HTTP status %u for POST /transfers.\n", ptr->hr.http_status); } TALER_TESTING_interpreter_next (pts->is); } /** * Offers information from the POST /transfers CMD state to other * commands. * * @param cls closure * @param[out] ret result (could be anything) * @param trait name of the trait * @param index index number of the object to extract. * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue post_transfers_traits (void *cls, const void **ret, const char *trait, unsigned int index) { struct PostTransfersState *pts = cls; struct TALER_TESTING_Trait traits[] = { TALER_TESTING_make_trait_wtid (&pts->wtid), TALER_TESTING_make_trait_credit_payto_uri (&pts->credit_account), TALER_TESTING_make_trait_h_full_payto (&pts->h_payto), TALER_TESTING_make_trait_h_normalized_payto (&pts->h_normalized_payto), TALER_TESTING_make_trait_amount (&pts->credit_amount), TALER_TESTING_make_trait_exchange_url (pts->exchange_url), TALER_TESTING_make_trait_bank_row (&pts->serial), TALER_TESTING_trait_end (), }; return TALER_TESTING_get_trait (traits, ret, trait, index); } /** * 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_run2 (void *cls, const struct TALER_TESTING_Command *cmd, struct TALER_TESTING_Interpreter *is) { struct PostTransfersState *pts = cls; pts->is = is; pts->pth = TALER_MERCHANT_transfers_post ( TALER_TESTING_interpreter_get_context (pts->is), pts->merchant_url, &pts->credit_amount, &pts->wtid, pts->credit_account, pts->exchange_url, &transfers_cb, pts); GNUNET_assert (NULL != pts->pth); } /** * 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 reply details from the HTTP response code */ static void debit_cb ( void *cls, const struct TALER_BANK_DebitHistoryResponse *reply) { struct PostTransfersState *pts = cls; pts->dhh = NULL; switch (reply->http_status) { case MHD_HTTP_OK: /* handled below */ break; case MHD_HTTP_NO_CONTENT: GNUNET_break (0); TALER_TESTING_interpreter_fail (pts->is); return; default: GNUNET_break (0); TALER_TESTING_interpreter_fail (pts->is); return; } for (unsigned int i = 0; idetails.ok.details_length; i++) { const struct TALER_BANK_DebitDetails *details = &reply->details.ok.details[i]; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Bank reports transfer of %s to %s\n", TALER_amount2s (&details->amount), details->credit_account_uri.full_payto); if (0 != TALER_amount_cmp (&pts->credit_amount, &details->amount)) continue; pts->wtid = details->wtid; pts->credit_account.full_payto = GNUNET_strdup (details->credit_account_uri.full_payto); pts->exchange_url = GNUNET_strdup (details->exchange_base_url); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Bank transfer found, checking with merchant backend at %s about %s from %s to %s with %s\n", pts->merchant_url, TALER_amount2s (&pts->credit_amount), pts->payto_uri.full_payto, pts->exchange_url, TALER_B2S (&pts->wtid)); pts->pth = TALER_MERCHANT_transfers_post ( TALER_TESTING_interpreter_get_context (pts->is), pts->merchant_url, &pts->credit_amount, &pts->wtid, pts->credit_account, pts->exchange_url, &transfers_cb, pts); GNUNET_assert (NULL != pts->pth); break; } if (NULL == pts->pth) { GNUNET_break (0); TALER_TESTING_interpreter_fail (pts->is); return; } } /** * 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; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Looking for transfer of %s from %s at bank\n", TALER_amount2s (&pts->credit_amount), pts->payto_uri.full_payto); pts->dhh = TALER_BANK_debit_history (TALER_TESTING_interpreter_get_context ( is), &pts->auth, UINT64_MAX, -INT64_MAX, GNUNET_TIME_UNIT_ZERO, &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 (pts->exchange_url); GNUNET_free (pts->credit_account.full_payto); GNUNET_free (pts); } struct TALER_TESTING_Command TALER_TESTING_cmd_merchant_post_transfer ( const char *label, const struct TALER_BANK_AuthenticationData *auth, struct TALER_FullPayto payto_uri, const char *merchant_url, const char *credit_amount, unsigned int http_code, ...) { struct PostTransfersState *pts; pts = GNUNET_new (struct PostTransfersState); pts->merchant_url = merchant_url; pts->auth = *auth; pts->payto_uri = payto_uri; TALER_full_payto_hash (payto_uri, &pts->h_payto); TALER_full_payto_normalize_and_hash (payto_uri, &pts->h_normalized_payto); 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, .traits = &post_transfers_traits }; return cmd; } } struct TALER_TESTING_Command TALER_TESTING_cmd_merchant_post_transfer2 ( const char *label, const char *merchant_url, struct TALER_FullPayto payto_uri, const char *credit_amount, const char *wtid, const char *exchange_url, unsigned int http_code) { struct PostTransfersState *pts; pts = GNUNET_new (struct PostTransfersState); pts->merchant_url = merchant_url; pts->credit_account.full_payto = GNUNET_strdup (payto_uri.full_payto); pts->exchange_url = GNUNET_strdup (exchange_url); GNUNET_assert (GNUNET_OK == TALER_string_to_amount (credit_amount, &pts->credit_amount)); if (NULL == wtid) { GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, &pts->wtid, sizeof (pts->wtid)); } else { GNUNET_assert (GNUNET_OK == GNUNET_STRINGS_string_to_data (wtid, strlen (wtid), &pts->wtid, sizeof (pts->wtid))); } pts->http_status = http_code; { struct TALER_TESTING_Command cmd = { .cls = pts, .label = label, .run = &post_transfers_run2, .cleanup = &post_transfers_cleanup, .traits = &post_transfers_traits }; return cmd; } } void TALER_TESTING_cmd_merchant_post_transfer_set_serial ( struct TALER_TESTING_Command *cmd, uint64_t serial) { struct PostTransfersState *pts = cmd->cls; GNUNET_assert (cmd->run = &post_transfers_run); pts->serial = serial; } /* end of testing_api_cmd_post_transfers.c */