/*
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 */