address #5010 for /refund
This commit is contained in:
parent
3d701e8d2a
commit
92e6744ac0
@ -1864,12 +1864,15 @@ wire_transfer_information_cb (void *cls,
|
|||||||
struct TALER_Amount coin_value_without_fee;
|
struct TALER_Amount coin_value_without_fee;
|
||||||
struct TALER_EXCHANGEDB_TransactionList *tl;
|
struct TALER_EXCHANGEDB_TransactionList *tl;
|
||||||
const struct TALER_CoinPublicInfo *coin;
|
const struct TALER_CoinPublicInfo *coin;
|
||||||
|
enum GNUNET_DB_QueryStatus qs;
|
||||||
|
|
||||||
/* Obtain coin's transaction history */
|
/* Obtain coin's transaction history */
|
||||||
tl = edb->get_coin_transactions (edb->cls,
|
qs = edb->get_coin_transactions (edb->cls,
|
||||||
esession,
|
esession,
|
||||||
coin_pub);
|
coin_pub,
|
||||||
if (NULL == tl)
|
&tl);
|
||||||
|
if ( (qs < 0) ||
|
||||||
|
(NULL == tl) )
|
||||||
{
|
{
|
||||||
wcc->ok = GNUNET_SYSERR;
|
wcc->ok = GNUNET_SYSERR;
|
||||||
report_row_inconsistency ("aggregation",
|
report_row_inconsistency ("aggregation",
|
||||||
|
@ -279,279 +279,6 @@ TEH_DB_calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a "/refund". Returns a confirmation that the refund
|
|
||||||
* was successful, or a failure if we are not aware of a matching
|
|
||||||
* /deposit or if it is too late to do the refund.
|
|
||||||
*
|
|
||||||
* @param connection the MHD connection to handle
|
|
||||||
* @param refund refund details
|
|
||||||
* @return MHD result code
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
TEH_DB_execute_refund (struct MHD_Connection *connection,
|
|
||||||
const struct TALER_EXCHANGEDB_Refund *refund)
|
|
||||||
{
|
|
||||||
struct TALER_EXCHANGEDB_Session *session;
|
|
||||||
struct TALER_EXCHANGEDB_TransactionList *tl;
|
|
||||||
struct TALER_EXCHANGEDB_TransactionList *tlp;
|
|
||||||
const struct TALER_EXCHANGEDB_Deposit *dep;
|
|
||||||
const struct TALER_EXCHANGEDB_Refund *ref;
|
|
||||||
struct TEH_KS_StateHandle *mks;
|
|
||||||
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
|
|
||||||
struct TALER_Amount expect_fee;
|
|
||||||
enum GNUNET_DB_QueryStatus qs;
|
|
||||||
int ret;
|
|
||||||
int deposit_found;
|
|
||||||
int refund_found;
|
|
||||||
int done;
|
|
||||||
int fee_cmp;
|
|
||||||
|
|
||||||
if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls)))
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
return TEH_RESPONSE_reply_internal_db_error (connection,
|
|
||||||
TALER_EC_DB_SETUP_FAILED);
|
|
||||||
}
|
|
||||||
dep = NULL;
|
|
||||||
ref = NULL;
|
|
||||||
START_TRANSACTION (session, connection);
|
|
||||||
tl = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
|
||||||
session,
|
|
||||||
&refund->coin.coin_pub);
|
|
||||||
if (NULL == tl)
|
|
||||||
{
|
|
||||||
TEH_plugin->rollback (TEH_plugin->cls,
|
|
||||||
session);
|
|
||||||
return TEH_RESPONSE_reply_refund_failure (connection,
|
|
||||||
MHD_HTTP_NOT_FOUND,
|
|
||||||
TALER_EC_REFUND_COIN_NOT_FOUND);
|
|
||||||
}
|
|
||||||
deposit_found = GNUNET_NO;
|
|
||||||
refund_found = GNUNET_NO;
|
|
||||||
for (tlp = tl; NULL != tlp; tlp = tlp->next)
|
|
||||||
{
|
|
||||||
switch (tlp->type)
|
|
||||||
{
|
|
||||||
case TALER_EXCHANGEDB_TT_DEPOSIT:
|
|
||||||
if (GNUNET_NO == deposit_found)
|
|
||||||
{
|
|
||||||
if ( (0 == memcmp (&tlp->details.deposit->merchant_pub,
|
|
||||||
&refund->merchant_pub,
|
|
||||||
sizeof (struct TALER_MerchantPublicKeyP))) &&
|
|
||||||
(0 == memcmp (&tlp->details.deposit->h_contract_terms,
|
|
||||||
&refund->h_contract_terms,
|
|
||||||
sizeof (struct GNUNET_HashCode))) )
|
|
||||||
{
|
|
||||||
dep = tlp->details.deposit;
|
|
||||||
deposit_found = GNUNET_YES;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case TALER_EXCHANGEDB_TT_REFRESH_MELT:
|
|
||||||
/* Melts cannot be refunded, ignore here */
|
|
||||||
break;
|
|
||||||
case TALER_EXCHANGEDB_TT_REFUND:
|
|
||||||
if (GNUNET_NO == refund_found)
|
|
||||||
{
|
|
||||||
/* First, check if existing refund request is identical */
|
|
||||||
if ( (0 == memcmp (&tlp->details.refund->merchant_pub,
|
|
||||||
&refund->merchant_pub,
|
|
||||||
sizeof (struct TALER_MerchantPublicKeyP))) &&
|
|
||||||
(0 == memcmp (&tlp->details.refund->h_contract_terms,
|
|
||||||
&refund->h_contract_terms,
|
|
||||||
sizeof (struct GNUNET_HashCode))) &&
|
|
||||||
(tlp->details.refund->rtransaction_id == refund->rtransaction_id) )
|
|
||||||
{
|
|
||||||
ref = tlp->details.refund;
|
|
||||||
refund_found = GNUNET_YES;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
/* Second, check if existing refund request conflicts */
|
|
||||||
if ( (0 == memcmp (&tlp->details.refund->merchant_pub,
|
|
||||||
&refund->merchant_pub,
|
|
||||||
sizeof (struct TALER_MerchantPublicKeyP))) &&
|
|
||||||
(0 == memcmp (&tlp->details.refund->h_contract_terms,
|
|
||||||
&refund->h_contract_terms,
|
|
||||||
sizeof (struct GNUNET_HashCode))) &&
|
|
||||||
(tlp->details.refund->rtransaction_id != refund->rtransaction_id) )
|
|
||||||
{
|
|
||||||
GNUNET_break_op (0); /* conflicting refund found */
|
|
||||||
refund_found = GNUNET_SYSERR;
|
|
||||||
/* NOTE: Alternatively we could total up all existing
|
|
||||||
refunds and check if the sum still permits the
|
|
||||||
refund requested (thus allowing multiple, partial
|
|
||||||
refunds). Fow now, we keep it simple. */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case TALER_EXCHANGEDB_TT_PAYBACK:
|
|
||||||
/* Paybacks cannot be refunded, ignore here */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* handle if deposit was NOT found */
|
|
||||||
if (GNUNET_NO == deposit_found)
|
|
||||||
{
|
|
||||||
TEH_plugin->rollback (TEH_plugin->cls,
|
|
||||||
session);
|
|
||||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
|
||||||
tl);
|
|
||||||
return TEH_RESPONSE_reply_transaction_unknown (connection,
|
|
||||||
TALER_EC_REFUND_DEPOSIT_NOT_FOUND);
|
|
||||||
}
|
|
||||||
/* handle if conflicting refund found */
|
|
||||||
if (GNUNET_SYSERR == refund_found)
|
|
||||||
{
|
|
||||||
TEH_plugin->rollback (TEH_plugin->cls,
|
|
||||||
session);
|
|
||||||
ret = TEH_RESPONSE_reply_refund_conflict (connection,
|
|
||||||
tl);
|
|
||||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
|
||||||
tl);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
/* handle if identical refund found */
|
|
||||||
if (GNUNET_YES == refund_found)
|
|
||||||
{
|
|
||||||
/* /refund already done, simply re-transmit confirmation */
|
|
||||||
TEH_plugin->rollback (TEH_plugin->cls,
|
|
||||||
session);
|
|
||||||
ret = TEH_RESPONSE_reply_refund_success (connection,
|
|
||||||
ref);
|
|
||||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
|
||||||
tl);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check currency is compatible */
|
|
||||||
if ( (GNUNET_YES !=
|
|
||||||
TALER_amount_cmp_currency (&refund->refund_amount,
|
|
||||||
&dep->amount_with_fee)) ||
|
|
||||||
(GNUNET_YES !=
|
|
||||||
TALER_amount_cmp_currency (&refund->refund_fee,
|
|
||||||
&dep->deposit_fee)) )
|
|
||||||
{
|
|
||||||
GNUNET_break_op (0); /* currency missmatch */
|
|
||||||
TEH_plugin->rollback (TEH_plugin->cls,
|
|
||||||
session);
|
|
||||||
return TEH_RESPONSE_reply_refund_failure (connection,
|
|
||||||
MHD_HTTP_PRECONDITION_FAILED,
|
|
||||||
TALER_EC_REFUND_CURRENCY_MISSMATCH);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check if we already send the money for the /deposit */
|
|
||||||
done = TEH_plugin->test_deposit_done (TEH_plugin->cls,
|
|
||||||
session,
|
|
||||||
dep);
|
|
||||||
if (GNUNET_SYSERR == done)
|
|
||||||
{
|
|
||||||
/* Internal error, we first had the deposit in the history,
|
|
||||||
but now it is gone? */
|
|
||||||
GNUNET_break (0);
|
|
||||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
|
||||||
tl);
|
|
||||||
TEH_plugin->rollback (TEH_plugin->cls,
|
|
||||||
session);
|
|
||||||
return TEH_RESPONSE_reply_internal_error (connection,
|
|
||||||
TALER_EC_REFUND_DB_INCONSISTENT,
|
|
||||||
"database inconsistent");
|
|
||||||
}
|
|
||||||
if (GNUNET_YES == done)
|
|
||||||
{
|
|
||||||
/* money was already transferred to merchant, can no longer refund */
|
|
||||||
TEH_plugin->rollback (TEH_plugin->cls,
|
|
||||||
session);
|
|
||||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
|
||||||
tl);
|
|
||||||
return TEH_RESPONSE_reply_refund_failure (connection,
|
|
||||||
MHD_HTTP_GONE,
|
|
||||||
TALER_EC_REFUND_MERCHANT_ALREADY_PAID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check refund amount is sufficiently low */
|
|
||||||
if (1 == TALER_amount_cmp (&refund->refund_amount,
|
|
||||||
&dep->amount_with_fee) )
|
|
||||||
{
|
|
||||||
GNUNET_break_op (0); /* cannot refund more than original value */
|
|
||||||
TEH_plugin->rollback (TEH_plugin->cls,
|
|
||||||
session);
|
|
||||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
|
||||||
tl);
|
|
||||||
return TEH_RESPONSE_reply_refund_failure (connection,
|
|
||||||
MHD_HTTP_PRECONDITION_FAILED,
|
|
||||||
TALER_EC_REFUND_INSUFFICIENT_FUNDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check refund fee matches fee of denomination key! */
|
|
||||||
mks = TEH_KS_acquire ();
|
|
||||||
dki = TEH_KS_denomination_key_lookup (mks,
|
|
||||||
&dep->coin.denom_pub,
|
|
||||||
TEH_KS_DKU_DEPOSIT);
|
|
||||||
if (NULL == dki)
|
|
||||||
{
|
|
||||||
/* DKI not found, but we do have a coin with this DK in our database;
|
|
||||||
not good... */
|
|
||||||
GNUNET_break (0);
|
|
||||||
TEH_plugin->rollback (TEH_plugin->cls,
|
|
||||||
session);
|
|
||||||
TEH_KS_release (mks);
|
|
||||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
|
||||||
tl);
|
|
||||||
return TEH_RESPONSE_reply_internal_error (connection,
|
|
||||||
TALER_EC_REFUND_DENOMINATION_KEY_NOT_FOUND,
|
|
||||||
"denomination key not found");
|
|
||||||
}
|
|
||||||
TALER_amount_ntoh (&expect_fee,
|
|
||||||
&dki->issue.properties.fee_refund);
|
|
||||||
fee_cmp = TALER_amount_cmp (&refund->refund_fee,
|
|
||||||
&expect_fee);
|
|
||||||
TEH_KS_release (mks);
|
|
||||||
|
|
||||||
if (-1 == fee_cmp)
|
|
||||||
{
|
|
||||||
TEH_plugin->rollback (TEH_plugin->cls,
|
|
||||||
session);
|
|
||||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
|
||||||
tl);
|
|
||||||
return TEH_RESPONSE_reply_arg_invalid (connection,
|
|
||||||
TALER_EC_REFUND_FEE_TOO_LOW,
|
|
||||||
"refund_fee");
|
|
||||||
}
|
|
||||||
if (1 == fee_cmp)
|
|
||||||
{
|
|
||||||
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
|
|
||||||
"Refund fee proposed by merchant is higher than necessary.\n");
|
|
||||||
}
|
|
||||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
|
||||||
tl);
|
|
||||||
|
|
||||||
/* Finally, store new refund data */
|
|
||||||
qs = TEH_plugin->insert_refund (TEH_plugin->cls,
|
|
||||||
session,
|
|
||||||
refund);
|
|
||||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
|
||||||
{
|
|
||||||
TALER_LOG_WARNING ("Failed to store /refund information in database\n");
|
|
||||||
TEH_plugin->rollback (TEH_plugin->cls,
|
|
||||||
session);
|
|
||||||
return TEH_RESPONSE_reply_internal_db_error (connection,
|
|
||||||
TALER_EC_REFUND_STORE_DB_ERROR);
|
|
||||||
}
|
|
||||||
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
|
|
||||||
{
|
|
||||||
/* FIXME: #5010: retry! */
|
|
||||||
}
|
|
||||||
|
|
||||||
COMMIT_TRANSACTION (session, connection);
|
|
||||||
|
|
||||||
return TEH_RESPONSE_reply_refund_success (connection,
|
|
||||||
refund);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse coin melt requests from a JSON object and write them to
|
* Parse coin melt requests from a JSON object and write them to
|
||||||
* the database.
|
* the database.
|
||||||
@ -581,6 +308,7 @@ refresh_check_melt (struct MHD_Connection *connection,
|
|||||||
struct TALER_Amount coin_residual;
|
struct TALER_Amount coin_residual;
|
||||||
struct TALER_Amount spent;
|
struct TALER_Amount spent;
|
||||||
int res;
|
int res;
|
||||||
|
enum GNUNET_DB_QueryStatus qs;
|
||||||
|
|
||||||
dk = TEH_KS_denomination_key_lookup (key_state,
|
dk = TEH_KS_denomination_key_lookup (key_state,
|
||||||
&coin_details->coin_info.denom_pub,
|
&coin_details->coin_info.denom_pub,
|
||||||
@ -597,9 +325,11 @@ refresh_check_melt (struct MHD_Connection *connection,
|
|||||||
/* fee for THIS transaction; the melt amount includes the fee! */
|
/* fee for THIS transaction; the melt amount includes the fee! */
|
||||||
spent = coin_details->melt_amount_with_fee;
|
spent = coin_details->melt_amount_with_fee;
|
||||||
/* add historic transaction costs of this coin */
|
/* add historic transaction costs of this coin */
|
||||||
tl = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
||||||
session,
|
session,
|
||||||
&coin_details->coin_info.coin_pub);
|
&coin_details->coin_info.coin_pub,
|
||||||
|
&tl);
|
||||||
|
(void) qs; /* FIXME #5010 */
|
||||||
if (GNUNET_OK !=
|
if (GNUNET_OK !=
|
||||||
TEH_DB_calculate_transaction_list_totals (tl,
|
TEH_DB_calculate_transaction_list_totals (tl,
|
||||||
&spent,
|
&spent,
|
||||||
@ -1921,7 +1651,8 @@ TEH_DB_execute_payback (struct MHD_Connection *connection,
|
|||||||
struct TALER_Amount amount;
|
struct TALER_Amount amount;
|
||||||
struct TALER_Amount spent;
|
struct TALER_Amount spent;
|
||||||
struct GNUNET_TIME_Absolute now;
|
struct GNUNET_TIME_Absolute now;
|
||||||
|
enum GNUNET_DB_QueryStatus qs;
|
||||||
|
|
||||||
if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls)))
|
if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls)))
|
||||||
{
|
{
|
||||||
GNUNET_break (0);
|
GNUNET_break (0);
|
||||||
@ -1955,9 +1686,11 @@ TEH_DB_execute_payback (struct MHD_Connection *connection,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Calculate remaining balance. */
|
/* Calculate remaining balance. */
|
||||||
tl = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
||||||
session,
|
session,
|
||||||
&coin->coin_pub);
|
&coin->coin_pub,
|
||||||
|
&tl);
|
||||||
|
(void) qs; /* FIXME #5010 */
|
||||||
TALER_amount_get_zero (value->currency,
|
TALER_amount_get_zero (value->currency,
|
||||||
&spent);
|
&spent);
|
||||||
if (GNUNET_OK !=
|
if (GNUNET_OK !=
|
||||||
|
@ -83,20 +83,6 @@ TEH_DB_calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionLis
|
|||||||
struct TALER_Amount *ret);
|
struct TALER_Amount *ret);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a "/refund". Returns a confirmation that the refund
|
|
||||||
* was successful, or a failure if we are not aware of a matching
|
|
||||||
* /deposit or if it is too late to do the refund.
|
|
||||||
*
|
|
||||||
* @param connection the MHD connection to handle
|
|
||||||
* @param refund refund details
|
|
||||||
* @return MHD result code
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
TEH_DB_execute_refund (struct MHD_Connection *connection,
|
|
||||||
const struct TALER_EXCHANGEDB_Refund *refund);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Details about a melt operation of an individual coin.
|
* @brief Details about a melt operation of an individual coin.
|
||||||
*/
|
*/
|
||||||
|
@ -163,9 +163,12 @@ deposit_transaction (void *cls,
|
|||||||
/* Start with fee for THIS transaction */
|
/* Start with fee for THIS transaction */
|
||||||
spent = deposit->amount_with_fee;
|
spent = deposit->amount_with_fee;
|
||||||
/* add cost of all previous transactions */
|
/* add cost of all previous transactions */
|
||||||
tl = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
||||||
session,
|
session,
|
||||||
&deposit->coin.coin_pub);
|
&deposit->coin.coin_pub,
|
||||||
|
&tl);
|
||||||
|
if (0 > qs)
|
||||||
|
return qs;
|
||||||
if (GNUNET_OK !=
|
if (GNUNET_OK !=
|
||||||
TEH_DB_calculate_transaction_list_totals (tl,
|
TEH_DB_calculate_transaction_list_totals (tl,
|
||||||
&spent,
|
&spent,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
This file is part of TALER
|
This file is part of TALER
|
||||||
Copyright (C) 2014, 2015, 2016 Inria and GNUnet e.V.
|
Copyright (C) 2014-2017 Inria and GNUnet e.V.
|
||||||
|
|
||||||
TALER is free software; you can redistribute it and/or modify it under the
|
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
|
terms of the GNU Affero General Public License as published by the Free Software
|
||||||
@ -36,6 +36,350 @@
|
|||||||
#include "taler-exchange-httpd_validation.h"
|
#include "taler-exchange-httpd_validation.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate successful refund confirmation message.
|
||||||
|
*
|
||||||
|
* @param connection connection to the client
|
||||||
|
* @param refund details about the successful refund
|
||||||
|
* @return MHD result code
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
reply_refund_success (struct MHD_Connection *connection,
|
||||||
|
const struct TALER_EXCHANGEDB_Refund *refund)
|
||||||
|
{
|
||||||
|
struct TALER_RefundConfirmationPS rc;
|
||||||
|
struct TALER_ExchangePublicKeyP pub;
|
||||||
|
struct TALER_ExchangeSignatureP sig;
|
||||||
|
|
||||||
|
rc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND);
|
||||||
|
rc.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS));
|
||||||
|
rc.h_contract_terms = refund->h_contract_terms;
|
||||||
|
rc.coin_pub = refund->coin.coin_pub;
|
||||||
|
rc.merchant = refund->merchant_pub;
|
||||||
|
rc.rtransaction_id = GNUNET_htonll (refund->rtransaction_id);
|
||||||
|
TALER_amount_hton (&rc.refund_amount,
|
||||||
|
&refund->refund_amount);
|
||||||
|
TALER_amount_hton (&rc.refund_fee,
|
||||||
|
&refund->refund_fee);
|
||||||
|
TEH_KS_sign (&rc.purpose,
|
||||||
|
&pub,
|
||||||
|
&sig);
|
||||||
|
return TEH_RESPONSE_reply_json_pack (connection,
|
||||||
|
MHD_HTTP_OK,
|
||||||
|
"{s:s, s:o, s:o}",
|
||||||
|
"status", "REFUND_OK",
|
||||||
|
"sig", GNUNET_JSON_from_data_auto (&sig),
|
||||||
|
"pub", GNUNET_JSON_from_data_auto (&pub));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate generic refund failure message. All the details
|
||||||
|
* are in the @a response_code. The body can be empty.
|
||||||
|
*
|
||||||
|
* @param connection connection to the client
|
||||||
|
* @param response_code response code to generate
|
||||||
|
* @param ec taler error code to include
|
||||||
|
* @return MHD result code
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
reply_refund_failure (struct MHD_Connection *connection,
|
||||||
|
unsigned int response_code,
|
||||||
|
enum TALER_ErrorCode ec)
|
||||||
|
{
|
||||||
|
return TEH_RESPONSE_reply_json_pack (connection,
|
||||||
|
response_code,
|
||||||
|
"{s:s, s:I}",
|
||||||
|
"status", "refund failure",
|
||||||
|
"code", (json_int_t) ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate refund conflict failure message. Returns the
|
||||||
|
* transaction list @a tl with the details about the conflict.
|
||||||
|
*
|
||||||
|
* @param connection connection to the client
|
||||||
|
* @param tl transaction list showing the conflict
|
||||||
|
* @return MHD result code
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
reply_refund_conflict (struct MHD_Connection *connection,
|
||||||
|
const struct TALER_EXCHANGEDB_TransactionList *tl)
|
||||||
|
{
|
||||||
|
return TEH_RESPONSE_reply_json_pack (connection,
|
||||||
|
MHD_HTTP_CONFLICT,
|
||||||
|
"{s:s, s:I, s:o}",
|
||||||
|
"status", "conflicting refund",
|
||||||
|
"code", (json_int_t) TALER_EC_REFUND_CONFLICT,
|
||||||
|
"history", TEH_RESPONSE_compile_transaction_history (tl));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a "/refund" transaction. Returns a confirmation that the
|
||||||
|
* refund was successful, or a failure if we are not aware of a
|
||||||
|
* matching /deposit or if it is too late to do the refund.
|
||||||
|
*
|
||||||
|
* IF it returns a non-error code, the transaction logic MUST
|
||||||
|
* NOT queue a MHD response. IF it returns an hard error, the
|
||||||
|
* transaction logic MUST queue a MHD response and set @a mhd_ret. IF
|
||||||
|
* it returns the soft error code, the function MAY be called again to
|
||||||
|
* retry and MUST not queue a MHD response.
|
||||||
|
*
|
||||||
|
* @param cls closure with a `const struct TALER_EXCHANGEDB_Refund *`
|
||||||
|
* @param connection MHD request which triggered the transaction
|
||||||
|
* @param session database session to use
|
||||||
|
* @param[out] mhd_ret set to MHD response status for @a connection,
|
||||||
|
* if transaction failed (!)
|
||||||
|
* @return transaction status
|
||||||
|
*/
|
||||||
|
static enum GNUNET_DB_QueryStatus
|
||||||
|
refund_transaction (void *cls,
|
||||||
|
struct MHD_Connection *connection,
|
||||||
|
struct TALER_EXCHANGEDB_Session *session,
|
||||||
|
int *mhd_ret)
|
||||||
|
{
|
||||||
|
const struct TALER_EXCHANGEDB_Refund *refund = cls;
|
||||||
|
struct TALER_EXCHANGEDB_TransactionList *tl;
|
||||||
|
const struct TALER_EXCHANGEDB_Deposit *dep;
|
||||||
|
const struct TALER_EXCHANGEDB_Refund *ref;
|
||||||
|
struct TEH_KS_StateHandle *mks;
|
||||||
|
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
|
||||||
|
struct TALER_Amount expect_fee;
|
||||||
|
enum GNUNET_DB_QueryStatus qs;
|
||||||
|
int deposit_found;
|
||||||
|
int refund_found;
|
||||||
|
int fee_cmp;
|
||||||
|
|
||||||
|
dep = NULL;
|
||||||
|
ref = NULL;
|
||||||
|
tl = NULL;
|
||||||
|
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
||||||
|
session,
|
||||||
|
&refund->coin.coin_pub,
|
||||||
|
&tl);
|
||||||
|
if (0 > qs)
|
||||||
|
{
|
||||||
|
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||||
|
*mhd_ret = reply_refund_failure (connection,
|
||||||
|
MHD_HTTP_NOT_FOUND,
|
||||||
|
TALER_EC_REFUND_COIN_NOT_FOUND);
|
||||||
|
return qs;
|
||||||
|
}
|
||||||
|
deposit_found = GNUNET_NO;
|
||||||
|
refund_found = GNUNET_NO;
|
||||||
|
for (struct TALER_EXCHANGEDB_TransactionList *tlp = tl;
|
||||||
|
NULL != tlp;
|
||||||
|
tlp = tlp->next)
|
||||||
|
{
|
||||||
|
switch (tlp->type)
|
||||||
|
{
|
||||||
|
case TALER_EXCHANGEDB_TT_DEPOSIT:
|
||||||
|
if (GNUNET_NO == deposit_found)
|
||||||
|
{
|
||||||
|
if ( (0 == memcmp (&tlp->details.deposit->merchant_pub,
|
||||||
|
&refund->merchant_pub,
|
||||||
|
sizeof (struct TALER_MerchantPublicKeyP))) &&
|
||||||
|
(0 == memcmp (&tlp->details.deposit->h_contract_terms,
|
||||||
|
&refund->h_contract_terms,
|
||||||
|
sizeof (struct GNUNET_HashCode))) )
|
||||||
|
{
|
||||||
|
dep = tlp->details.deposit;
|
||||||
|
deposit_found = GNUNET_YES;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TALER_EXCHANGEDB_TT_REFRESH_MELT:
|
||||||
|
/* Melts cannot be refunded, ignore here */
|
||||||
|
break;
|
||||||
|
case TALER_EXCHANGEDB_TT_REFUND:
|
||||||
|
if (GNUNET_NO == refund_found)
|
||||||
|
{
|
||||||
|
/* First, check if existing refund request is identical */
|
||||||
|
if ( (0 == memcmp (&tlp->details.refund->merchant_pub,
|
||||||
|
&refund->merchant_pub,
|
||||||
|
sizeof (struct TALER_MerchantPublicKeyP))) &&
|
||||||
|
(0 == memcmp (&tlp->details.refund->h_contract_terms,
|
||||||
|
&refund->h_contract_terms,
|
||||||
|
sizeof (struct GNUNET_HashCode))) &&
|
||||||
|
(tlp->details.refund->rtransaction_id == refund->rtransaction_id) )
|
||||||
|
{
|
||||||
|
ref = tlp->details.refund;
|
||||||
|
refund_found = GNUNET_YES;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* Second, check if existing refund request conflicts */
|
||||||
|
if ( (0 == memcmp (&tlp->details.refund->merchant_pub,
|
||||||
|
&refund->merchant_pub,
|
||||||
|
sizeof (struct TALER_MerchantPublicKeyP))) &&
|
||||||
|
(0 == memcmp (&tlp->details.refund->h_contract_terms,
|
||||||
|
&refund->h_contract_terms,
|
||||||
|
sizeof (struct GNUNET_HashCode))) &&
|
||||||
|
(tlp->details.refund->rtransaction_id != refund->rtransaction_id) )
|
||||||
|
{
|
||||||
|
GNUNET_break_op (0); /* conflicting refund found */
|
||||||
|
refund_found = GNUNET_SYSERR;
|
||||||
|
/* NOTE: Alternatively we could total up all existing
|
||||||
|
refunds and check if the sum still permits the
|
||||||
|
refund requested (thus allowing multiple, partial
|
||||||
|
refunds). Fow now, we keep it simple. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TALER_EXCHANGEDB_TT_PAYBACK:
|
||||||
|
/* Paybacks cannot be refunded, ignore here */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* handle if deposit was NOT found */
|
||||||
|
if (GNUNET_NO == deposit_found)
|
||||||
|
{
|
||||||
|
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||||
|
tl);
|
||||||
|
*mhd_ret = TEH_RESPONSE_reply_transaction_unknown (connection,
|
||||||
|
TALER_EC_REFUND_DEPOSIT_NOT_FOUND);
|
||||||
|
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||||
|
}
|
||||||
|
/* handle if conflicting refund found */
|
||||||
|
if (GNUNET_SYSERR == refund_found)
|
||||||
|
{
|
||||||
|
*mhd_ret = reply_refund_conflict (connection,
|
||||||
|
tl);
|
||||||
|
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||||
|
tl);
|
||||||
|
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||||
|
}
|
||||||
|
/* handle if identical refund found */
|
||||||
|
if (GNUNET_YES == refund_found)
|
||||||
|
{
|
||||||
|
/* /refund already done, simply re-transmit confirmation */
|
||||||
|
*mhd_ret = reply_refund_success (connection,
|
||||||
|
ref);
|
||||||
|
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||||
|
tl);
|
||||||
|
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check currency is compatible */
|
||||||
|
if ( (GNUNET_YES !=
|
||||||
|
TALER_amount_cmp_currency (&refund->refund_amount,
|
||||||
|
&dep->amount_with_fee)) ||
|
||||||
|
(GNUNET_YES !=
|
||||||
|
TALER_amount_cmp_currency (&refund->refund_fee,
|
||||||
|
&dep->deposit_fee)) )
|
||||||
|
{
|
||||||
|
GNUNET_break_op (0); /* currency missmatch */
|
||||||
|
*mhd_ret = reply_refund_failure (connection,
|
||||||
|
MHD_HTTP_PRECONDITION_FAILED,
|
||||||
|
TALER_EC_REFUND_CURRENCY_MISSMATCH);
|
||||||
|
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check if we already send the money for the /deposit */
|
||||||
|
// FIXME: DB API...
|
||||||
|
qs = TEH_plugin->test_deposit_done (TEH_plugin->cls,
|
||||||
|
session,
|
||||||
|
dep);
|
||||||
|
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||||
|
{
|
||||||
|
/* Internal error, we first had the deposit in the history,
|
||||||
|
but now it is gone? */
|
||||||
|
GNUNET_break (0);
|
||||||
|
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||||
|
tl);
|
||||||
|
*mhd_ret = TEH_RESPONSE_reply_internal_error (connection,
|
||||||
|
TALER_EC_REFUND_DB_INCONSISTENT,
|
||||||
|
"database inconsistent");
|
||||||
|
return qs;
|
||||||
|
}
|
||||||
|
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
|
||||||
|
return qs; /* go and retry */
|
||||||
|
|
||||||
|
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
|
||||||
|
{
|
||||||
|
/* money was already transferred to merchant, can no longer refund */
|
||||||
|
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||||
|
tl);
|
||||||
|
*mhd_ret = reply_refund_failure (connection,
|
||||||
|
MHD_HTTP_GONE,
|
||||||
|
TALER_EC_REFUND_MERCHANT_ALREADY_PAID);
|
||||||
|
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check refund amount is sufficiently low */
|
||||||
|
if (1 == TALER_amount_cmp (&refund->refund_amount,
|
||||||
|
&dep->amount_with_fee) )
|
||||||
|
{
|
||||||
|
GNUNET_break_op (0); /* cannot refund more than original value */
|
||||||
|
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||||
|
tl);
|
||||||
|
*mhd_ret = reply_refund_failure (connection,
|
||||||
|
MHD_HTTP_PRECONDITION_FAILED,
|
||||||
|
TALER_EC_REFUND_INSUFFICIENT_FUNDS);
|
||||||
|
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: do this outside of transaction function?
|
||||||
|
/* Check refund fee matches fee of denomination key! */
|
||||||
|
mks = TEH_KS_acquire ();
|
||||||
|
dki = TEH_KS_denomination_key_lookup (mks,
|
||||||
|
&dep->coin.denom_pub,
|
||||||
|
TEH_KS_DKU_DEPOSIT);
|
||||||
|
if (NULL == dki)
|
||||||
|
{
|
||||||
|
/* DKI not found, but we do have a coin with this DK in our database;
|
||||||
|
not good... */
|
||||||
|
GNUNET_break (0);
|
||||||
|
TEH_KS_release (mks);
|
||||||
|
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||||
|
tl);
|
||||||
|
*mhd_ret = TEH_RESPONSE_reply_internal_error (connection,
|
||||||
|
TALER_EC_REFUND_DENOMINATION_KEY_NOT_FOUND,
|
||||||
|
"denomination key not found");
|
||||||
|
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||||
|
}
|
||||||
|
TALER_amount_ntoh (&expect_fee,
|
||||||
|
&dki->issue.properties.fee_refund);
|
||||||
|
fee_cmp = TALER_amount_cmp (&refund->refund_fee,
|
||||||
|
&expect_fee);
|
||||||
|
TEH_KS_release (mks);
|
||||||
|
|
||||||
|
if (-1 == fee_cmp)
|
||||||
|
{
|
||||||
|
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||||
|
tl);
|
||||||
|
*mhd_ret = TEH_RESPONSE_reply_arg_invalid (connection,
|
||||||
|
TALER_EC_REFUND_FEE_TOO_LOW,
|
||||||
|
"refund_fee");
|
||||||
|
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||||
|
}
|
||||||
|
if (1 == fee_cmp)
|
||||||
|
{
|
||||||
|
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
|
||||||
|
"Refund fee proposed by merchant is higher than necessary.\n");
|
||||||
|
}
|
||||||
|
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||||
|
tl);
|
||||||
|
|
||||||
|
/* Finally, store new refund data */
|
||||||
|
qs = TEH_plugin->insert_refund (TEH_plugin->cls,
|
||||||
|
session,
|
||||||
|
refund);
|
||||||
|
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||||
|
{
|
||||||
|
TALER_LOG_WARNING ("Failed to store /refund information in database\n");
|
||||||
|
*mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
|
||||||
|
TALER_EC_REFUND_STORE_DB_ERROR);
|
||||||
|
return qs;
|
||||||
|
}
|
||||||
|
/* Success or soft failure */
|
||||||
|
return qs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We have parsed the JSON information about the refund, do some basic
|
* We have parsed the JSON information about the refund, do some basic
|
||||||
* sanity checks (especially that the signature on the coin is valid)
|
* sanity checks (especially that the signature on the coin is valid)
|
||||||
@ -51,6 +395,7 @@ verify_and_execute_refund (struct MHD_Connection *connection,
|
|||||||
const struct TALER_EXCHANGEDB_Refund *refund)
|
const struct TALER_EXCHANGEDB_Refund *refund)
|
||||||
{
|
{
|
||||||
struct TALER_RefundRequestPS rr;
|
struct TALER_RefundRequestPS rr;
|
||||||
|
int mhd_ret;
|
||||||
|
|
||||||
rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND);
|
rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND);
|
||||||
rr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS));
|
rr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS));
|
||||||
@ -90,8 +435,14 @@ verify_and_execute_refund (struct MHD_Connection *connection,
|
|||||||
TALER_EC_REFUND_MERCHANT_SIGNATURE_INVALID,
|
TALER_EC_REFUND_MERCHANT_SIGNATURE_INVALID,
|
||||||
"merchant_sig");
|
"merchant_sig");
|
||||||
}
|
}
|
||||||
return TEH_DB_execute_refund (connection,
|
if (GNUNET_OK !=
|
||||||
refund);
|
TEH_DB_run_transaction (connection,
|
||||||
|
&mhd_ret,
|
||||||
|
&refund_transaction,
|
||||||
|
(void *) refund))
|
||||||
|
return mhd_ret;
|
||||||
|
return reply_refund_success (connection,
|
||||||
|
refund);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -467,8 +467,8 @@ TEH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection)
|
|||||||
* @param tl transaction history to JSON-ify
|
* @param tl transaction history to JSON-ify
|
||||||
* @return json representation of the @a rh
|
* @return json representation of the @a rh
|
||||||
*/
|
*/
|
||||||
static json_t *
|
json_t *
|
||||||
compile_transaction_history (const struct TALER_EXCHANGEDB_TransactionList *tl)
|
TEH_RESPONSE_compile_transaction_history (const struct TALER_EXCHANGEDB_TransactionList *tl)
|
||||||
{
|
{
|
||||||
json_t *history;
|
json_t *history;
|
||||||
|
|
||||||
@ -663,7 +663,7 @@ TEH_RESPONSE_reply_coin_insufficient_funds (struct MHD_Connection *connection,
|
|||||||
{
|
{
|
||||||
json_t *history;
|
json_t *history;
|
||||||
|
|
||||||
history = compile_transaction_history (tl);
|
history = TEH_RESPONSE_compile_transaction_history (tl);
|
||||||
if (NULL == history)
|
if (NULL == history)
|
||||||
return TEH_RESPONSE_reply_internal_error (connection,
|
return TEH_RESPONSE_reply_internal_error (connection,
|
||||||
TALER_EC_COIN_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS,
|
TALER_EC_COIN_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS,
|
||||||
@ -878,86 +878,6 @@ TEH_RESPONSE_compile_reserve_history (const struct TALER_EXCHANGEDB_ReserveHisto
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate refund conflict failure message. Returns the
|
|
||||||
* transaction list @a tl with the details about the conflict.
|
|
||||||
*
|
|
||||||
* @param connection connection to the client
|
|
||||||
* @param tl transaction list showing the conflict
|
|
||||||
* @return MHD result code
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
TEH_RESPONSE_reply_refund_conflict (struct MHD_Connection *connection,
|
|
||||||
const struct TALER_EXCHANGEDB_TransactionList *tl)
|
|
||||||
{
|
|
||||||
return TEH_RESPONSE_reply_json_pack (connection,
|
|
||||||
MHD_HTTP_CONFLICT,
|
|
||||||
"{s:s, s:I, s:o}",
|
|
||||||
"status", "conflicting refund",
|
|
||||||
"code", (json_int_t) TALER_EC_REFUND_CONFLICT,
|
|
||||||
"history", compile_transaction_history (tl));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate generic refund failure message. All the details
|
|
||||||
* are in the @a response_code. The body can be empty.
|
|
||||||
*
|
|
||||||
* @param connection connection to the client
|
|
||||||
* @param response_code response code to generate
|
|
||||||
* @param ec taler error code to include
|
|
||||||
* @return MHD result code
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
TEH_RESPONSE_reply_refund_failure (struct MHD_Connection *connection,
|
|
||||||
unsigned int response_code,
|
|
||||||
enum TALER_ErrorCode ec)
|
|
||||||
{
|
|
||||||
return TEH_RESPONSE_reply_json_pack (connection,
|
|
||||||
response_code,
|
|
||||||
"{s:s, s:I}",
|
|
||||||
"status", "refund failure",
|
|
||||||
"code", (json_int_t) ec);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate successful refund confirmation message.
|
|
||||||
*
|
|
||||||
* @param connection connection to the client
|
|
||||||
* @param refund details about the successful refund
|
|
||||||
* @return MHD result code
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
TEH_RESPONSE_reply_refund_success (struct MHD_Connection *connection,
|
|
||||||
const struct TALER_EXCHANGEDB_Refund *refund)
|
|
||||||
{
|
|
||||||
struct TALER_RefundConfirmationPS rc;
|
|
||||||
struct TALER_ExchangePublicKeyP pub;
|
|
||||||
struct TALER_ExchangeSignatureP sig;
|
|
||||||
|
|
||||||
rc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND);
|
|
||||||
rc.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS));
|
|
||||||
rc.h_contract_terms = refund->h_contract_terms;
|
|
||||||
rc.coin_pub = refund->coin.coin_pub;
|
|
||||||
rc.merchant = refund->merchant_pub;
|
|
||||||
rc.rtransaction_id = GNUNET_htonll (refund->rtransaction_id);
|
|
||||||
TALER_amount_hton (&rc.refund_amount,
|
|
||||||
&refund->refund_amount);
|
|
||||||
TALER_amount_hton (&rc.refund_fee,
|
|
||||||
&refund->refund_fee);
|
|
||||||
TEH_KS_sign (&rc.purpose,
|
|
||||||
&pub,
|
|
||||||
&sig);
|
|
||||||
return TEH_RESPONSE_reply_json_pack (connection,
|
|
||||||
MHD_HTTP_OK,
|
|
||||||
"{s:s, s:o, s:o}",
|
|
||||||
"status", "REFUND_OK",
|
|
||||||
"sig", GNUNET_JSON_from_data_auto (&sig),
|
|
||||||
"pub", GNUNET_JSON_from_data_auto (&pub));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a response for a failed "/refresh/melt" request. The
|
* Send a response for a failed "/refresh/melt" request. The
|
||||||
* transaction history of the given coin demonstrates that the
|
* transaction history of the given coin demonstrates that the
|
||||||
@ -983,7 +903,7 @@ TEH_RESPONSE_reply_refresh_melt_insufficient_funds (struct MHD_Connection *conne
|
|||||||
{
|
{
|
||||||
json_t *history;
|
json_t *history;
|
||||||
|
|
||||||
history = compile_transaction_history (tl);
|
history = TEH_RESPONSE_compile_transaction_history (tl);
|
||||||
if (NULL == history)
|
if (NULL == history)
|
||||||
return TEH_RESPONSE_reply_internal_db_error (connection,
|
return TEH_RESPONSE_reply_internal_db_error (connection,
|
||||||
TALER_EC_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS);
|
TALER_EC_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS);
|
||||||
|
@ -273,46 +273,6 @@ TEH_RESPONSE_reply_coin_insufficient_funds (struct MHD_Connection *connection,
|
|||||||
const struct TALER_EXCHANGEDB_TransactionList *tl);
|
const struct TALER_EXCHANGEDB_TransactionList *tl);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate refund conflict failure message. Returns the
|
|
||||||
* transaction list @a tl with the details about the conflict.
|
|
||||||
*
|
|
||||||
* @param connection connection to the client
|
|
||||||
* @param tl transaction list showing the conflict
|
|
||||||
* @return MHD result code
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
TEH_RESPONSE_reply_refund_conflict (struct MHD_Connection *connection,
|
|
||||||
const struct TALER_EXCHANGEDB_TransactionList *tl);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate generic refund failure message. All the details
|
|
||||||
* are in the @a response_code. The body can be empty.
|
|
||||||
*
|
|
||||||
* @param connection connection to the client
|
|
||||||
* @param response_code response code to generate
|
|
||||||
* @param ec error code uniquely identifying the error
|
|
||||||
* @return MHD result code
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
TEH_RESPONSE_reply_refund_failure (struct MHD_Connection *connection,
|
|
||||||
unsigned int response_code,
|
|
||||||
enum TALER_ErrorCode ec);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate successful refund confirmation message.
|
|
||||||
*
|
|
||||||
* @param connection connection to the client
|
|
||||||
* @param refund details about the successful refund
|
|
||||||
* @return MHD result code
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
TEH_RESPONSE_reply_refund_success (struct MHD_Connection *connection,
|
|
||||||
const struct TALER_EXCHANGEDB_Refund *refund);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A merchant asked for details about a deposit, but
|
* A merchant asked for details about a deposit, but
|
||||||
* we do not know anything about the deposit. Generate the
|
* we do not know anything about the deposit. Generate the
|
||||||
@ -555,8 +515,19 @@ int
|
|||||||
TEH_RESPONSE_reply_payback_success (struct MHD_Connection *connection,
|
TEH_RESPONSE_reply_payback_success (struct MHD_Connection *connection,
|
||||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||||
const struct TALER_ReservePublicKeyP *reserve_pub,
|
const struct TALER_ReservePublicKeyP *reserve_pub,
|
||||||
|
|
||||||
const struct TALER_Amount *amount,
|
const struct TALER_Amount *amount,
|
||||||
struct GNUNET_TIME_Absolute timestamp);
|
struct GNUNET_TIME_Absolute timestamp);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile the transaction history of a coin into a JSON object.
|
||||||
|
*
|
||||||
|
* @param tl transaction history to JSON-ify
|
||||||
|
* @return json representation of the @a rh
|
||||||
|
*/
|
||||||
|
json_t *
|
||||||
|
TEH_RESPONSE_compile_transaction_history (const struct TALER_EXCHANGEDB_TransactionList *tl);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1529,13 +1529,16 @@ interpret (struct PERF_TALER_EXCHANGEDB_interpreter_state *state)
|
|||||||
unsigned int coin_index;
|
unsigned int coin_index;
|
||||||
struct PERF_TALER_EXCHANGEDB_Coin *coin;
|
struct PERF_TALER_EXCHANGEDB_Coin *coin;
|
||||||
struct TALER_EXCHANGEDB_TransactionList *transactions;
|
struct TALER_EXCHANGEDB_TransactionList *transactions;
|
||||||
|
enum GNUNET_DB_QueryStatus qs;
|
||||||
|
|
||||||
coin_index = state->cmd[state->i].details.get_coin_transaction.index_coin;
|
coin_index = state->cmd[state->i].details.get_coin_transaction.index_coin;
|
||||||
coin = state->cmd[coin_index].exposed.data.coin;
|
coin = state->cmd[coin_index].exposed.data.coin;
|
||||||
transactions = state->plugin->get_coin_transactions (state->plugin->cls,
|
qs = state->plugin->get_coin_transactions (state->plugin->cls,
|
||||||
state->session,
|
state->session,
|
||||||
&coin->public_info.coin_pub);
|
&coin->public_info.coin_pub,
|
||||||
GNUNET_assert (transactions != NULL);
|
&transactions);
|
||||||
|
GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
|
||||||
|
GNUNET_assert (transactions != NULL);
|
||||||
state->plugin->free_coin_transaction_list (state->plugin->cls,
|
state->plugin->free_coin_transaction_list (state->plugin->cls,
|
||||||
transactions);
|
transactions);
|
||||||
}
|
}
|
||||||
|
@ -2827,10 +2827,11 @@ postgres_mark_deposit_tiny (void *cls,
|
|||||||
* @param cls the @e cls of this struct with the plugin-specific state
|
* @param cls the @e cls of this struct with the plugin-specific state
|
||||||
* @param session connection to the database
|
* @param session connection to the database
|
||||||
* @param deposit the deposit to check
|
* @param deposit the deposit to check
|
||||||
* @return #GNUNET_YES if is is marked done done, #GNUNET_NO if not,
|
* @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if is is marked done,
|
||||||
* #GNUNET_SYSERR on error (deposit unknown)
|
* #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if not,
|
||||||
|
* otherwise transaction error status (incl. deposit unknown)
|
||||||
*/
|
*/
|
||||||
static int
|
static enum GNUNET_DB_QueryStatus
|
||||||
postgres_test_deposit_done (void *cls,
|
postgres_test_deposit_done (void *cls,
|
||||||
struct TALER_EXCHANGEDB_Session *session,
|
struct TALER_EXCHANGEDB_Session *session,
|
||||||
const struct TALER_EXCHANGEDB_Deposit *deposit)
|
const struct TALER_EXCHANGEDB_Deposit *deposit)
|
||||||
@ -2842,50 +2843,25 @@ postgres_test_deposit_done (void *cls,
|
|||||||
GNUNET_PQ_query_param_auto_from_type (&deposit->h_wire),
|
GNUNET_PQ_query_param_auto_from_type (&deposit->h_wire),
|
||||||
GNUNET_PQ_query_param_end
|
GNUNET_PQ_query_param_end
|
||||||
};
|
};
|
||||||
PGresult *result;
|
uint8_t done = 0;
|
||||||
|
struct GNUNET_PQ_ResultSpec rs[] = {
|
||||||
|
GNUNET_PQ_result_spec_auto_from_type ("done",
|
||||||
|
&done),
|
||||||
|
GNUNET_PQ_result_spec_end
|
||||||
|
};
|
||||||
|
enum GNUNET_DB_QueryStatus qs;
|
||||||
|
|
||||||
result = GNUNET_PQ_exec_prepared (session->conn,
|
qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
|
||||||
"test_deposit_done",
|
"test_deposit_done",
|
||||||
params);
|
params,
|
||||||
if (PGRES_TUPLES_OK !=
|
rs);
|
||||||
PQresultStatus (result))
|
if (qs < 0)
|
||||||
{
|
return qs;
|
||||||
BREAK_DB_ERR (result, session->conn);
|
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
|
||||||
PQclear (result);
|
return GNUNET_DB_STATUS_HARD_ERROR; /* deposit MUST exist */
|
||||||
return GNUNET_SYSERR;
|
return (done
|
||||||
}
|
? GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
|
||||||
if (0 == PQntuples (result))
|
: GNUNET_DB_STATUS_SUCCESS_NO_RESULTS);
|
||||||
{
|
|
||||||
PQclear (result);
|
|
||||||
return GNUNET_SYSERR;
|
|
||||||
}
|
|
||||||
if (1 != PQntuples (result))
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
PQclear (result);
|
|
||||||
return GNUNET_SYSERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
uint8_t done = 0;
|
|
||||||
struct GNUNET_PQ_ResultSpec rs[] = {
|
|
||||||
GNUNET_PQ_result_spec_auto_from_type ("done",
|
|
||||||
&done),
|
|
||||||
GNUNET_PQ_result_spec_end
|
|
||||||
};
|
|
||||||
|
|
||||||
if (GNUNET_OK !=
|
|
||||||
GNUNET_PQ_extract_result (result,
|
|
||||||
rs,
|
|
||||||
0))
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
PQclear (result);
|
|
||||||
return GNUNET_SYSERR;
|
|
||||||
}
|
|
||||||
PQclear (result);
|
|
||||||
return (done ? GNUNET_YES : GNUNET_NO);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -4070,6 +4046,310 @@ postgres_get_transfer (void *cls,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closure for callbacks called from #postgres_get_coin_transactions()
|
||||||
|
*/
|
||||||
|
struct CoinHistoryContext
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Head of the coin's history list.
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGEDB_TransactionList *head;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public key of the coin we are building the history for.
|
||||||
|
*/
|
||||||
|
const struct TALER_CoinSpendPublicKeyP *coin_pub;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closure for all callbacks of this database plugin.
|
||||||
|
*/
|
||||||
|
void *db_cls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database session we are using.
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGEDB_Session *session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to #GNUNET_SYSERR on errors
|
||||||
|
*/
|
||||||
|
int status;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to be called with the results of a SELECT statement
|
||||||
|
* that has returned @a num_results results.
|
||||||
|
*
|
||||||
|
* @param cls closure of type `struct CoinHistoryContext`
|
||||||
|
* @param result the postgres result
|
||||||
|
* @param num_result the number of results in @a result
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
add_coin_deposit (void *cls,
|
||||||
|
PGresult *result,
|
||||||
|
unsigned int num_results)
|
||||||
|
{
|
||||||
|
struct CoinHistoryContext *chc = cls;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < num_results; i++)
|
||||||
|
{
|
||||||
|
struct TALER_EXCHANGEDB_Deposit *deposit;
|
||||||
|
struct TALER_EXCHANGEDB_TransactionList *tl;
|
||||||
|
|
||||||
|
deposit = GNUNET_new (struct TALER_EXCHANGEDB_Deposit);
|
||||||
|
{
|
||||||
|
struct GNUNET_PQ_ResultSpec rs[] = {
|
||||||
|
TALER_PQ_result_spec_amount ("amount_with_fee",
|
||||||
|
&deposit->amount_with_fee),
|
||||||
|
TALER_PQ_result_spec_amount ("fee_deposit",
|
||||||
|
&deposit->deposit_fee),
|
||||||
|
GNUNET_PQ_result_spec_absolute_time ("timestamp",
|
||||||
|
&deposit->timestamp),
|
||||||
|
GNUNET_PQ_result_spec_absolute_time ("refund_deadline",
|
||||||
|
&deposit->refund_deadline),
|
||||||
|
GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
|
||||||
|
&deposit->merchant_pub),
|
||||||
|
GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
|
||||||
|
&deposit->h_contract_terms),
|
||||||
|
GNUNET_PQ_result_spec_auto_from_type ("h_wire",
|
||||||
|
&deposit->h_wire),
|
||||||
|
TALER_PQ_result_spec_json ("wire",
|
||||||
|
&deposit->receiver_wire_account),
|
||||||
|
GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
|
||||||
|
&deposit->csig),
|
||||||
|
GNUNET_PQ_result_spec_end
|
||||||
|
};
|
||||||
|
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
GNUNET_PQ_extract_result (result,
|
||||||
|
rs,
|
||||||
|
i))
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
GNUNET_free (deposit);
|
||||||
|
chc->status = GNUNET_SYSERR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deposit->coin.coin_pub = *chc->coin_pub;
|
||||||
|
}
|
||||||
|
tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
|
||||||
|
tl->next = chc->head;
|
||||||
|
tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
|
||||||
|
tl->details.deposit = deposit;
|
||||||
|
if (GNUNET_SYSERR == get_known_coin (chc->db_cls,
|
||||||
|
chc->session,
|
||||||
|
chc->coin_pub,
|
||||||
|
&deposit->coin))
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
GNUNET_free (deposit);
|
||||||
|
chc->status = GNUNET_SYSERR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chc->head = tl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to be called with the results of a SELECT statement
|
||||||
|
* that has returned @a num_results results.
|
||||||
|
*
|
||||||
|
* @param cls closure of type `struct CoinHistoryContext`
|
||||||
|
* @param result the postgres result
|
||||||
|
* @param num_result the number of results in @a result
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
add_coin_melt (void *cls,
|
||||||
|
PGresult *result,
|
||||||
|
unsigned int num_results)
|
||||||
|
{
|
||||||
|
struct CoinHistoryContext *chc = cls;
|
||||||
|
|
||||||
|
for (unsigned int i=0;i<num_results;i++)
|
||||||
|
{
|
||||||
|
struct TALER_EXCHANGEDB_RefreshMelt *melt;
|
||||||
|
struct TALER_EXCHANGEDB_TransactionList *tl;
|
||||||
|
|
||||||
|
melt = GNUNET_new (struct TALER_EXCHANGEDB_RefreshMelt);
|
||||||
|
{
|
||||||
|
struct GNUNET_PQ_ResultSpec rs[] = {
|
||||||
|
GNUNET_PQ_result_spec_auto_from_type ("session_hash",
|
||||||
|
&melt->session_hash),
|
||||||
|
/* oldcoin_index not needed */
|
||||||
|
GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
|
||||||
|
&melt->coin_sig),
|
||||||
|
TALER_PQ_result_spec_amount ("amount_with_fee",
|
||||||
|
&melt->amount_with_fee),
|
||||||
|
TALER_PQ_result_spec_amount ("fee_refresh",
|
||||||
|
&melt->melt_fee),
|
||||||
|
GNUNET_PQ_result_spec_end
|
||||||
|
};
|
||||||
|
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
GNUNET_PQ_extract_result (result,
|
||||||
|
rs,
|
||||||
|
i))
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
GNUNET_free (melt);
|
||||||
|
chc->status = GNUNET_SYSERR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
melt->coin.coin_pub = *chc->coin_pub;
|
||||||
|
}
|
||||||
|
tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
|
||||||
|
tl->next = chc->head;
|
||||||
|
tl->type = TALER_EXCHANGEDB_TT_REFRESH_MELT;
|
||||||
|
tl->details.melt = melt;
|
||||||
|
if (GNUNET_SYSERR == get_known_coin (chc->db_cls,
|
||||||
|
chc->session,
|
||||||
|
chc->coin_pub,
|
||||||
|
&melt->coin))
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
GNUNET_free (melt);
|
||||||
|
chc->status = GNUNET_SYSERR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chc->head = tl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to be called with the results of a SELECT statement
|
||||||
|
* that has returned @a num_results results.
|
||||||
|
*
|
||||||
|
* @param cls closure of type `struct CoinHistoryContext`
|
||||||
|
* @param result the postgres result
|
||||||
|
* @param num_result the number of results in @a result
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
add_coin_refund (void *cls,
|
||||||
|
PGresult *result,
|
||||||
|
unsigned int num_results)
|
||||||
|
{
|
||||||
|
struct CoinHistoryContext *chc = cls;
|
||||||
|
|
||||||
|
for (unsigned int i=0;i<num_results;i++)
|
||||||
|
{
|
||||||
|
struct TALER_EXCHANGEDB_Refund *refund;
|
||||||
|
struct TALER_EXCHANGEDB_TransactionList *tl;
|
||||||
|
|
||||||
|
refund = GNUNET_new (struct TALER_EXCHANGEDB_Refund);
|
||||||
|
{
|
||||||
|
struct GNUNET_PQ_ResultSpec rs[] = {
|
||||||
|
GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
|
||||||
|
&refund->merchant_pub),
|
||||||
|
GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
|
||||||
|
&refund->merchant_sig),
|
||||||
|
GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
|
||||||
|
&refund->h_contract_terms),
|
||||||
|
GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
|
||||||
|
&refund->rtransaction_id),
|
||||||
|
TALER_PQ_result_spec_amount ("amount_with_fee",
|
||||||
|
&refund->refund_amount),
|
||||||
|
TALER_PQ_result_spec_amount ("fee_refund",
|
||||||
|
&refund->refund_fee),
|
||||||
|
GNUNET_PQ_result_spec_end
|
||||||
|
};
|
||||||
|
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
GNUNET_PQ_extract_result (result,
|
||||||
|
rs,
|
||||||
|
i))
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
GNUNET_free (refund);
|
||||||
|
chc->status = GNUNET_SYSERR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
refund->coin.coin_pub = *chc->coin_pub;
|
||||||
|
}
|
||||||
|
tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
|
||||||
|
tl->next = chc->head;
|
||||||
|
tl->type = TALER_EXCHANGEDB_TT_REFUND;
|
||||||
|
tl->details.refund = refund;
|
||||||
|
if (GNUNET_SYSERR ==
|
||||||
|
get_known_coin (chc->db_cls,
|
||||||
|
chc->session,
|
||||||
|
chc->coin_pub,
|
||||||
|
&refund->coin))
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
GNUNET_free (refund);
|
||||||
|
chc->status = GNUNET_SYSERR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chc->head = tl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to be called with the results of a SELECT statement
|
||||||
|
* that has returned @a num_results results.
|
||||||
|
*
|
||||||
|
* @param cls closure of type `struct CoinHistoryContext`
|
||||||
|
* @param result the postgres result
|
||||||
|
* @param num_result the number of results in @a result
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
add_coin_payback (void *cls,
|
||||||
|
PGresult *result,
|
||||||
|
unsigned int num_results)
|
||||||
|
{
|
||||||
|
struct CoinHistoryContext *chc = cls;
|
||||||
|
|
||||||
|
for (unsigned int i=0;i<num_results;i++)
|
||||||
|
{
|
||||||
|
struct TALER_EXCHANGEDB_Payback *payback;
|
||||||
|
struct TALER_EXCHANGEDB_TransactionList *tl;
|
||||||
|
|
||||||
|
payback = GNUNET_new (struct TALER_EXCHANGEDB_Payback);
|
||||||
|
{
|
||||||
|
struct GNUNET_PQ_ResultSpec rs[] = {
|
||||||
|
TALER_PQ_result_spec_amount ("amount",
|
||||||
|
&payback->value),
|
||||||
|
GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
|
||||||
|
&payback->reserve_pub),
|
||||||
|
GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
|
||||||
|
&payback->coin_blind),
|
||||||
|
GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
|
||||||
|
&payback->coin_sig),
|
||||||
|
GNUNET_PQ_result_spec_absolute_time ("timestamp",
|
||||||
|
&payback->timestamp),
|
||||||
|
GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
|
||||||
|
&payback->coin.denom_pub.rsa_public_key),
|
||||||
|
GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
|
||||||
|
&payback->coin.denom_sig.rsa_signature),
|
||||||
|
GNUNET_PQ_result_spec_end
|
||||||
|
};
|
||||||
|
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
GNUNET_PQ_extract_result (result,
|
||||||
|
rs,
|
||||||
|
i))
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
GNUNET_free (payback);
|
||||||
|
chc->status = GNUNET_SYSERR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
payback->coin.coin_pub = *chc->coin_pub;
|
||||||
|
}
|
||||||
|
tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
|
||||||
|
tl->next = chc->head;
|
||||||
|
tl->type = TALER_EXCHANGEDB_TT_PAYBACK;
|
||||||
|
tl->details.payback = payback;
|
||||||
|
chc->head = tl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile a list of all (historic) transactions performed
|
* Compile a list of all (historic) transactions performed
|
||||||
* with the given coin (/refresh/melt, /deposit and /refund operations).
|
* with the given coin (/refresh/melt, /deposit and /refund operations).
|
||||||
@ -4077,311 +4357,75 @@ postgres_get_transfer (void *cls,
|
|||||||
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
||||||
* @param session database connection
|
* @param session database connection
|
||||||
* @param coin_pub coin to investigate
|
* @param coin_pub coin to investigate
|
||||||
* @return list of transactions, NULL if coin is fresh
|
* @param[out] tlp set to list of transactions, NULL if coin is fresh
|
||||||
|
* @return database transaction status
|
||||||
*/
|
*/
|
||||||
static struct TALER_EXCHANGEDB_TransactionList *
|
static enum GNUNET_DB_QueryStatus
|
||||||
postgres_get_coin_transactions (void *cls,
|
postgres_get_coin_transactions (void *cls,
|
||||||
struct TALER_EXCHANGEDB_Session *session,
|
struct TALER_EXCHANGEDB_Session *session,
|
||||||
const struct TALER_CoinSpendPublicKeyP *coin_pub)
|
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||||
|
struct TALER_EXCHANGEDB_TransactionList **tlp)
|
||||||
{
|
{
|
||||||
struct TALER_EXCHANGEDB_TransactionList *head;
|
struct CoinHistoryContext chc;
|
||||||
|
struct GNUNET_PQ_QueryParam params[] = {
|
||||||
|
GNUNET_PQ_query_param_auto_from_type (coin_pub),
|
||||||
|
GNUNET_PQ_query_param_end
|
||||||
|
};
|
||||||
|
enum GNUNET_DB_QueryStatus qs;
|
||||||
|
struct {
|
||||||
|
/**
|
||||||
|
* SQL prepared statement name.
|
||||||
|
*/
|
||||||
|
const char *statement;
|
||||||
|
|
||||||
head = NULL;
|
/**
|
||||||
/** #TALER_EXCHANGEDB_TT_DEPOSIT */
|
* Function to call to handle the result(s).
|
||||||
|
*/
|
||||||
|
GNUNET_PQ_PostgresResultHandler cb;
|
||||||
|
} work[] = {
|
||||||
|
/** #TALER_EXCHANGEDB_TT_DEPOSIT */
|
||||||
|
{ "get_deposit_with_coin_pub",
|
||||||
|
&add_coin_deposit },
|
||||||
|
/** #TALER_EXCHANGEDB_TT_REFRESH_MELT */
|
||||||
|
{ "get_refresh_session_by_coin",
|
||||||
|
&add_coin_melt },
|
||||||
|
/** #TALER_EXCHANGEDB_TT_REFUND */
|
||||||
|
{ "get_refunds_by_coin",
|
||||||
|
&add_coin_refund },
|
||||||
|
/** #TALER_EXCHANGEDB_TT_PAYBACK */
|
||||||
|
{ "payback_by_coin",
|
||||||
|
&add_coin_payback },
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
chc.head = NULL;
|
||||||
|
chc.status = GNUNET_OK;
|
||||||
|
chc.coin_pub = coin_pub;
|
||||||
|
chc.session = session;
|
||||||
|
chc.db_cls = cls;
|
||||||
|
for (unsigned int i=0;NULL != work[i].statement; i++)
|
||||||
{
|
{
|
||||||
struct GNUNET_PQ_QueryParam params[] = {
|
qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
|
||||||
GNUNET_PQ_query_param_auto_from_type (coin_pub),
|
work[i].statement,
|
||||||
GNUNET_PQ_query_param_end
|
params,
|
||||||
};
|
work[i].cb,
|
||||||
int nrows;
|
&chc);
|
||||||
PGresult *result;
|
if ( (0 > qs) ||
|
||||||
struct TALER_EXCHANGEDB_TransactionList *tl;
|
(GNUNET_OK != chc.status) )
|
||||||
|
|
||||||
result = GNUNET_PQ_exec_prepared (session->conn,
|
|
||||||
"get_deposit_with_coin_pub",
|
|
||||||
params);
|
|
||||||
if (PGRES_TUPLES_OK != PQresultStatus (result))
|
|
||||||
{
|
{
|
||||||
QUERY_ERR (result, session->conn);
|
if (NULL != chc.head)
|
||||||
PQclear (result);
|
common_free_coin_transaction_list (cls,
|
||||||
goto cleanup;
|
chc.head);
|
||||||
|
*tlp = NULL;
|
||||||
|
if (GNUNET_OK != chc.status)
|
||||||
|
qs = GNUNET_DB_STATUS_HARD_ERROR;
|
||||||
|
return qs;
|
||||||
}
|
}
|
||||||
nrows = PQntuples (result);
|
|
||||||
for (int i = 0; i < nrows; i++)
|
|
||||||
{
|
|
||||||
struct TALER_EXCHANGEDB_Deposit *deposit;
|
|
||||||
|
|
||||||
deposit = GNUNET_new (struct TALER_EXCHANGEDB_Deposit);
|
|
||||||
{
|
|
||||||
struct GNUNET_PQ_ResultSpec rs[] = {
|
|
||||||
TALER_PQ_result_spec_amount ("amount_with_fee",
|
|
||||||
&deposit->amount_with_fee),
|
|
||||||
TALER_PQ_result_spec_amount ("fee_deposit",
|
|
||||||
&deposit->deposit_fee),
|
|
||||||
GNUNET_PQ_result_spec_absolute_time ("timestamp",
|
|
||||||
&deposit->timestamp),
|
|
||||||
GNUNET_PQ_result_spec_absolute_time ("refund_deadline",
|
|
||||||
&deposit->refund_deadline),
|
|
||||||
GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
|
|
||||||
&deposit->merchant_pub),
|
|
||||||
GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
|
|
||||||
&deposit->h_contract_terms),
|
|
||||||
GNUNET_PQ_result_spec_auto_from_type ("h_wire",
|
|
||||||
&deposit->h_wire),
|
|
||||||
TALER_PQ_result_spec_json ("wire",
|
|
||||||
&deposit->receiver_wire_account),
|
|
||||||
GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
|
|
||||||
&deposit->csig),
|
|
||||||
GNUNET_PQ_result_spec_end
|
|
||||||
};
|
|
||||||
|
|
||||||
if (GNUNET_OK !=
|
|
||||||
GNUNET_PQ_extract_result (result,
|
|
||||||
rs,
|
|
||||||
i))
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
GNUNET_free (deposit);
|
|
||||||
PQclear (result);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
deposit->coin.coin_pub = *coin_pub;
|
|
||||||
}
|
|
||||||
tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
|
|
||||||
tl->next = head;
|
|
||||||
tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
|
|
||||||
tl->details.deposit = deposit;
|
|
||||||
if (GNUNET_SYSERR == get_known_coin (cls,
|
|
||||||
session,
|
|
||||||
&deposit->coin.coin_pub,
|
|
||||||
&deposit->coin))
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
GNUNET_free (deposit);
|
|
||||||
PQclear (result);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
head = tl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
PQclear (result);
|
|
||||||
}
|
}
|
||||||
/** #TALER_EXCHANGEDB_TT_REFRESH_MELT */
|
*tlp = chc.head;
|
||||||
{
|
if (NULL == chc.head)
|
||||||
struct GNUNET_PQ_QueryParam params[] = {
|
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
|
||||||
GNUNET_PQ_query_param_auto_from_type (&coin_pub->eddsa_pub),
|
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
|
||||||
GNUNET_PQ_query_param_end
|
|
||||||
};
|
|
||||||
int nrows;
|
|
||||||
PGresult *result;
|
|
||||||
struct TALER_EXCHANGEDB_TransactionList *tl;
|
|
||||||
|
|
||||||
/* check if the melt records exist and get them */
|
|
||||||
result = GNUNET_PQ_exec_prepared (session->conn,
|
|
||||||
"get_refresh_session_by_coin",
|
|
||||||
params);
|
|
||||||
if (PGRES_TUPLES_OK != PQresultStatus (result))
|
|
||||||
{
|
|
||||||
BREAK_DB_ERR (result, session->conn);
|
|
||||||
PQclear (result);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
nrows = PQntuples (result);
|
|
||||||
for (int i=0;i<nrows;i++)
|
|
||||||
{
|
|
||||||
struct TALER_EXCHANGEDB_RefreshMelt *melt;
|
|
||||||
|
|
||||||
melt = GNUNET_new (struct TALER_EXCHANGEDB_RefreshMelt);
|
|
||||||
{
|
|
||||||
struct GNUNET_PQ_ResultSpec rs[] = {
|
|
||||||
GNUNET_PQ_result_spec_auto_from_type ("session_hash",
|
|
||||||
&melt->session_hash),
|
|
||||||
/* oldcoin_index not needed */
|
|
||||||
GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
|
|
||||||
&melt->coin_sig),
|
|
||||||
TALER_PQ_result_spec_amount ("amount_with_fee",
|
|
||||||
&melt->amount_with_fee),
|
|
||||||
TALER_PQ_result_spec_amount ("fee_refresh",
|
|
||||||
&melt->melt_fee),
|
|
||||||
GNUNET_PQ_result_spec_end
|
|
||||||
};
|
|
||||||
if (GNUNET_OK !=
|
|
||||||
GNUNET_PQ_extract_result (result,
|
|
||||||
rs,
|
|
||||||
i))
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
GNUNET_free (melt);
|
|
||||||
PQclear (result);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
melt->coin.coin_pub = *coin_pub;
|
|
||||||
}
|
|
||||||
tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
|
|
||||||
tl->next = head;
|
|
||||||
tl->type = TALER_EXCHANGEDB_TT_REFRESH_MELT;
|
|
||||||
tl->details.melt = melt;
|
|
||||||
if (GNUNET_SYSERR == get_known_coin (cls,
|
|
||||||
session,
|
|
||||||
coin_pub,
|
|
||||||
&melt->coin))
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
GNUNET_free (melt);
|
|
||||||
PQclear (result);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
head = tl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
PQclear (result);
|
|
||||||
}
|
|
||||||
/** #TALER_EXCHANGEDB_TT_REFUND */
|
|
||||||
{
|
|
||||||
struct GNUNET_PQ_QueryParam params[] = {
|
|
||||||
GNUNET_PQ_query_param_auto_from_type (coin_pub),
|
|
||||||
GNUNET_PQ_query_param_end
|
|
||||||
};
|
|
||||||
int nrows;
|
|
||||||
PGresult *result;
|
|
||||||
struct TALER_EXCHANGEDB_TransactionList *tl;
|
|
||||||
|
|
||||||
/* check if a refund records exist and get them */
|
|
||||||
result = GNUNET_PQ_exec_prepared (session->conn,
|
|
||||||
"get_refunds_by_coin",
|
|
||||||
params);
|
|
||||||
if (PGRES_TUPLES_OK != PQresultStatus (result))
|
|
||||||
{
|
|
||||||
BREAK_DB_ERR (result, session->conn);
|
|
||||||
PQclear (result);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
nrows = PQntuples (result);
|
|
||||||
for (int i=0;i<nrows;i++)
|
|
||||||
{
|
|
||||||
struct TALER_EXCHANGEDB_Refund *refund;
|
|
||||||
|
|
||||||
refund = GNUNET_new (struct TALER_EXCHANGEDB_Refund);
|
|
||||||
{
|
|
||||||
struct GNUNET_PQ_ResultSpec rs[] = {
|
|
||||||
GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
|
|
||||||
&refund->merchant_pub),
|
|
||||||
GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
|
|
||||||
&refund->merchant_sig),
|
|
||||||
GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
|
|
||||||
&refund->h_contract_terms),
|
|
||||||
GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
|
|
||||||
&refund->rtransaction_id),
|
|
||||||
TALER_PQ_result_spec_amount ("amount_with_fee",
|
|
||||||
&refund->refund_amount),
|
|
||||||
TALER_PQ_result_spec_amount ("fee_refund",
|
|
||||||
&refund->refund_fee),
|
|
||||||
GNUNET_PQ_result_spec_end
|
|
||||||
};
|
|
||||||
if (GNUNET_OK !=
|
|
||||||
GNUNET_PQ_extract_result (result,
|
|
||||||
rs,
|
|
||||||
i))
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
GNUNET_free (refund);
|
|
||||||
PQclear (result);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
refund->coin.coin_pub = *coin_pub;
|
|
||||||
}
|
|
||||||
tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
|
|
||||||
tl->next = head;
|
|
||||||
tl->type = TALER_EXCHANGEDB_TT_REFUND;
|
|
||||||
tl->details.refund = refund;
|
|
||||||
if (GNUNET_SYSERR ==
|
|
||||||
get_known_coin (cls,
|
|
||||||
session,
|
|
||||||
coin_pub,
|
|
||||||
&refund->coin))
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
GNUNET_free (refund);
|
|
||||||
PQclear (result);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
head = tl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
PQclear (result);
|
|
||||||
}
|
|
||||||
/** #TALER_EXCHANGEDB_TT_PAYBACK */
|
|
||||||
{
|
|
||||||
struct GNUNET_PQ_QueryParam params[] = {
|
|
||||||
GNUNET_PQ_query_param_auto_from_type (coin_pub),
|
|
||||||
GNUNET_PQ_query_param_end
|
|
||||||
};
|
|
||||||
int nrows;
|
|
||||||
PGresult *result;
|
|
||||||
struct TALER_EXCHANGEDB_TransactionList *tl;
|
|
||||||
|
|
||||||
/* check if a refund records exist and get them */
|
|
||||||
result = GNUNET_PQ_exec_prepared (session->conn,
|
|
||||||
"payback_by_coin",
|
|
||||||
params);
|
|
||||||
if (PGRES_TUPLES_OK != PQresultStatus (result))
|
|
||||||
{
|
|
||||||
BREAK_DB_ERR (result, session->conn);
|
|
||||||
PQclear (result);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
nrows = PQntuples (result);
|
|
||||||
for (int i=0;i<nrows;i++)
|
|
||||||
{
|
|
||||||
struct TALER_EXCHANGEDB_Payback *payback;
|
|
||||||
|
|
||||||
payback = GNUNET_new (struct TALER_EXCHANGEDB_Payback);
|
|
||||||
{
|
|
||||||
struct GNUNET_PQ_ResultSpec rs[] = {
|
|
||||||
TALER_PQ_result_spec_amount ("amount",
|
|
||||||
&payback->value),
|
|
||||||
GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
|
|
||||||
&payback->reserve_pub),
|
|
||||||
GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
|
|
||||||
&payback->coin_blind),
|
|
||||||
GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
|
|
||||||
&payback->coin_sig),
|
|
||||||
GNUNET_PQ_result_spec_absolute_time ("timestamp",
|
|
||||||
&payback->timestamp),
|
|
||||||
GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
|
|
||||||
&payback->coin.denom_pub.rsa_public_key),
|
|
||||||
GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
|
|
||||||
&payback->coin.denom_sig.rsa_signature),
|
|
||||||
GNUNET_PQ_result_spec_end
|
|
||||||
};
|
|
||||||
if (GNUNET_OK !=
|
|
||||||
GNUNET_PQ_extract_result (result,
|
|
||||||
rs,
|
|
||||||
i))
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
GNUNET_free (payback);
|
|
||||||
PQclear (result);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
payback->coin.coin_pub = *coin_pub;
|
|
||||||
}
|
|
||||||
tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
|
|
||||||
tl->next = head;
|
|
||||||
tl->type = TALER_EXCHANGEDB_TT_PAYBACK;
|
|
||||||
tl->details.payback = payback;
|
|
||||||
head = tl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
PQclear (result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return head;
|
|
||||||
cleanup:
|
|
||||||
if (NULL != head)
|
|
||||||
common_free_coin_transaction_list (cls,
|
|
||||||
head);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -726,10 +726,13 @@ test_melting (struct TALER_EXCHANGEDB_Session *session)
|
|||||||
{
|
{
|
||||||
/* Just to test fetching a coin with melt history */
|
/* Just to test fetching a coin with melt history */
|
||||||
struct TALER_EXCHANGEDB_TransactionList *tl;
|
struct TALER_EXCHANGEDB_TransactionList *tl;
|
||||||
|
enum GNUNET_DB_QueryStatus qs;
|
||||||
|
|
||||||
tl = plugin->get_coin_transactions (plugin->cls,
|
qs = plugin->get_coin_transactions (plugin->cls,
|
||||||
session,
|
session,
|
||||||
&meltp->coin.coin_pub);
|
&meltp->coin.coin_pub,
|
||||||
|
&tl);
|
||||||
|
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
|
||||||
plugin->free_coin_transaction_list (plugin->cls,
|
plugin->free_coin_transaction_list (plugin->cls,
|
||||||
tl);
|
tl);
|
||||||
}
|
}
|
||||||
@ -1930,9 +1933,11 @@ run (void *cls)
|
|||||||
NULL));
|
NULL));
|
||||||
|
|
||||||
FAILIF (1 != auditor_row_cnt);
|
FAILIF (1 != auditor_row_cnt);
|
||||||
tl = plugin->get_coin_transactions (plugin->cls,
|
qs = plugin->get_coin_transactions (plugin->cls,
|
||||||
session,
|
session,
|
||||||
&refund.coin.coin_pub);
|
&refund.coin.coin_pub,
|
||||||
|
&tl);
|
||||||
|
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
|
||||||
GNUNET_assert (NULL != tl);
|
GNUNET_assert (NULL != tl);
|
||||||
matched = 0;
|
matched = 0;
|
||||||
for (tlp = tl; NULL != tlp; tlp = tlp->next)
|
for (tlp = tl; NULL != tlp; tlp = tlp->next)
|
||||||
|
@ -1346,16 +1346,17 @@ struct TALER_EXCHANGEDB_Plugin
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if a deposit was marked as done, thereby declaring that it cannot be
|
* Test if a deposit was marked as done, thereby declaring that it
|
||||||
* refunded anymore.
|
* cannot be refunded anymore.
|
||||||
*
|
*
|
||||||
* @param cls the @e cls of this struct with the plugin-specific state
|
* @param cls the @e cls of this struct with the plugin-specific state
|
||||||
* @param session connection to the database
|
* @param session connection to the database
|
||||||
* @param deposit the deposit to check
|
* @param deposit the deposit to check
|
||||||
* @return #GNUNET_YES if is is marked done done, #GNUNET_NO if not,
|
* @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if is is marked done,
|
||||||
* #GNUNET_SYSERR on error (deposit unknown)
|
* #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if not,
|
||||||
|
* otherwise transaction error status (incl. deposit unknown)
|
||||||
*/
|
*/
|
||||||
int
|
enum GNUNET_DB_QueryStatus
|
||||||
(*test_deposit_done) (void *cls,
|
(*test_deposit_done) (void *cls,
|
||||||
struct TALER_EXCHANGEDB_Session *session,
|
struct TALER_EXCHANGEDB_Session *session,
|
||||||
const struct TALER_EXCHANGEDB_Deposit *deposit);
|
const struct TALER_EXCHANGEDB_Deposit *deposit);
|
||||||
@ -1700,12 +1701,14 @@ struct TALER_EXCHANGEDB_Plugin
|
|||||||
* @param cls the @e cls of this struct with the plugin-specific state
|
* @param cls the @e cls of this struct with the plugin-specific state
|
||||||
* @param session database connection
|
* @param session database connection
|
||||||
* @param coin_pub coin to investigate
|
* @param coin_pub coin to investigate
|
||||||
* @return list of transactions, NULL if coin is fresh
|
* @param[out] tlp set to list of transactions, NULL if coin is fresh
|
||||||
|
* @return database transaction status
|
||||||
*/
|
*/
|
||||||
struct TALER_EXCHANGEDB_TransactionList *
|
enum GNUNET_DB_QueryStatus
|
||||||
(*get_coin_transactions) (void *cls,
|
(*get_coin_transactions) (void *cls,
|
||||||
struct TALER_EXCHANGEDB_Session *session,
|
struct TALER_EXCHANGEDB_Session *session,
|
||||||
const struct TALER_CoinSpendPublicKeyP *coin_pub);
|
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||||
|
struct TALER_EXCHANGEDB_TransactionList **tlp);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user