implementing parsing of /refund requests

This commit is contained in:
Christian Grothoff 2016-04-20 02:50:52 +02:00
parent f693e25793
commit edd31c7415
9 changed files with 441 additions and 85 deletions

View File

@ -35,6 +35,7 @@ taler_exchange_httpd_SOURCES = \
taler-exchange-httpd_mhd.c taler-exchange-httpd_mhd.h \ taler-exchange-httpd_mhd.c taler-exchange-httpd_mhd.h \
taler-exchange-httpd_parsing.c taler-exchange-httpd_parsing.h \ taler-exchange-httpd_parsing.c taler-exchange-httpd_parsing.h \
taler-exchange-httpd_refresh.c taler-exchange-httpd_refresh.h \ taler-exchange-httpd_refresh.c taler-exchange-httpd_refresh.h \
taler-exchange-httpd_refund.c taler-exchange-httpd_refund.h \
taler-exchange-httpd_reserve.c taler-exchange-httpd_reserve.h \ taler-exchange-httpd_reserve.c taler-exchange-httpd_reserve.h \
taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \ taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \
taler-exchange-httpd_tracking.c taler-exchange-httpd_tracking.h \ taler-exchange-httpd_tracking.c taler-exchange-httpd_tracking.h \

View File

@ -30,6 +30,7 @@
#include "taler-exchange-httpd_mhd.h" #include "taler-exchange-httpd_mhd.h"
#include "taler-exchange-httpd_admin.h" #include "taler-exchange-httpd_admin.h"
#include "taler-exchange-httpd_deposit.h" #include "taler-exchange-httpd_deposit.h"
#include "taler-exchange-httpd_refund.h"
#include "taler-exchange-httpd_reserve.h" #include "taler-exchange-httpd_reserve.h"
#include "taler-exchange-httpd_wire.h" #include "taler-exchange-httpd_wire.h"
#include "taler-exchange-httpd_refresh.h" #include "taler-exchange-httpd_refresh.h"
@ -200,6 +201,14 @@ handle_mhd_request (void *cls,
"Only POST is allowed", 0, "Only POST is allowed", 0,
&TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
/* Refunding coins */
{ "/refund", MHD_HTTP_METHOD_POST, "application/json",
NULL, 0,
&TMH_REFUND_handler_refund, MHD_HTTP_OK },
{ "/refund", NULL, "text/plain",
"Only POST is allowed", 0,
&TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
/* Dealing with change */ /* Dealing with change */
{ "/refresh/melt", MHD_HTTP_METHOD_POST, "application/json", { "/refresh/melt", MHD_HTTP_METHOD_POST, "application/json",
NULL, 0, NULL, 0,

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2014, 2015 GNUnet e.V. Copyright (C) 2014, 2015, 2016 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
@ -21,9 +21,6 @@
* @author Florian Dold * @author Florian Dold
* @author Benedikt Mueller * @author Benedikt Mueller
* @author Christian Grothoff * @author Christian Grothoff
*
* TODO:
* - ugly if-construction for deposit type
*/ */
#include "platform.h" #include "platform.h"
#include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_util_lib.h>
@ -119,28 +116,37 @@ verify_and_execute_deposit (struct MHD_Connection *connection,
/** /**
* Handle a "/deposit" request. This function parses the * Handle a "/deposit" request. Parses the JSON, and, if successful,
* JSON information and then calls #verify_and_execute_deposit() * passes the JSON data to #verify_and_execute_deposit() to further
* to verify the signatures and execute the deposit. * check the details of the operation specified. If everything checks
* out, this will ultimately lead to the "/deposit" being executed, or
* rejected.
* *
* @param rh context of the handler
* @param connection the MHD connection to handle * @param connection the MHD connection to handle
* @param root root of the posted JSON * @param[in,out] connection_cls the connection's closure (can be updated)
* @param amount how much should be deposited * @param upload_data upload data
* @param wire json describing the wire details (?) * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code * @return MHD result code
*/ */
static int int
parse_and_handle_deposit_request (struct MHD_Connection *connection, TMH_DEPOSIT_handler_deposit (struct TMH_RequestHandler *rh,
const json_t *root, struct MHD_Connection *connection,
const struct TALER_Amount *amount, void **connection_cls,
json_t *wire) const char *upload_data,
size_t *upload_data_size)
{ {
json_t *json;
int res; int res;
json_t *wire;
struct TALER_EXCHANGEDB_Deposit deposit; struct TALER_EXCHANGEDB_Deposit deposit;
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
struct TMH_KS_StateHandle *ks; struct TMH_KS_StateHandle *ks;
struct GNUNET_HashCode my_h_wire; struct GNUNET_HashCode my_h_wire;
struct TALER_Amount amount;
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_json ("wire", &wire),
TALER_JSON_spec_amount ("f", &amount),
TALER_JSON_spec_denomination_public_key ("denom_pub", &deposit.coin.denom_pub), TALER_JSON_spec_denomination_public_key ("denom_pub", &deposit.coin.denom_pub),
TALER_JSON_spec_denomination_signature ("ub_sig", &deposit.coin.denom_sig), TALER_JSON_spec_denomination_signature ("ub_sig", &deposit.coin.denom_sig),
GNUNET_JSON_spec_fixed_auto ("coin_pub", &deposit.coin.coin_pub), GNUNET_JSON_spec_fixed_auto ("coin_pub", &deposit.coin.coin_pub),
@ -155,10 +161,20 @@ parse_and_handle_deposit_request (struct MHD_Connection *connection,
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
}; };
res = TMH_PARSE_post_json (connection,
connection_cls,
upload_data,
upload_data_size,
&json);
if (GNUNET_SYSERR == res)
return MHD_NO;
if ( (GNUNET_NO == res) || (NULL == json) )
return MHD_YES;
memset (&deposit, 0, sizeof (deposit)); memset (&deposit, 0, sizeof (deposit));
res = TMH_PARSE_json_data (connection, res = TMH_PARSE_json_data (connection,
root, json,
spec); spec);
json_decref (json);
if (GNUNET_SYSERR == res) if (GNUNET_SYSERR == res)
return MHD_NO; /* hard failure */ return MHD_NO; /* hard failure */
if (GNUNET_NO == res) if (GNUNET_NO == res)
@ -205,12 +221,12 @@ parse_and_handle_deposit_request (struct MHD_Connection *connection,
&dki->issue.properties.fee_deposit); &dki->issue.properties.fee_deposit);
TMH_KS_release (ks); TMH_KS_release (ks);
deposit.wire = wire; deposit.wire = wire;
deposit.amount_with_fee = *amount; deposit.amount_with_fee = amount;
if (-1 == TALER_amount_cmp (&deposit.amount_with_fee, if (-1 == TALER_amount_cmp (&deposit.amount_with_fee,
&deposit.deposit_fee)) &deposit.deposit_fee))
{ {
/* Total amount smaller than fee, invalid */ /* Total amount smaller than fee, invalid */
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
return TMH_RESPONSE_reply_arg_invalid (connection, return TMH_RESPONSE_reply_arg_invalid (connection,
"f"); "f");
} }
@ -221,64 +237,4 @@ parse_and_handle_deposit_request (struct MHD_Connection *connection,
} }
/**
* Handle a "/deposit" request. Parses the JSON in the post to find
* the "type" (either DIRECT_DEPOSIT or INCREMENTAL_DEPOSIT), and, if
* successful, passes the JSON data to
* #parse_and_handle_deposit_request() to further check the details
* of the operation specified in the "wire" field of the JSON data.
* If everything checks out, this will ultimately lead to the
* "/deposit" being executed, or rejected.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] connection_cls the connection's closure (can be updated)
* @param upload_data upload data
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
int
TMH_DEPOSIT_handler_deposit (struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size)
{
json_t *json;
json_t *wire;
int res;
struct TALER_Amount amount;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_json ("wire", &wire),
TALER_JSON_spec_amount ("f", &amount),
GNUNET_JSON_spec_end ()
};
res = TMH_PARSE_post_json (connection,
connection_cls,
upload_data,
upload_data_size,
&json);
if (GNUNET_SYSERR == res)
return MHD_NO;
if ( (GNUNET_NO == res) || (NULL == json) )
return MHD_YES;
res = TMH_PARSE_json_data (connection,
json,
spec);
if (GNUNET_OK != res)
{
json_decref (json);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
res = parse_and_handle_deposit_request (connection,
json,
&amount,
wire);
GNUNET_JSON_parse_free (spec);
json_decref (json);
return res;
}
/* end of taler-exchange-httpd_deposit.c */ /* end of taler-exchange-httpd_deposit.c */

View File

@ -29,13 +29,9 @@
/** /**
* Handle a "/deposit" request. Parses the JSON in the post to find * Handle a "/deposit" request. Parses the JSON, and, if successful,
* the "type" (either DIRECT_DEPOSIT or INCREMENTAL_DEPOSIT), and, if * checks the signatures. If everything checks out, this will
* successful, passes the JSON data to * ultimately lead to the "/deposit" being executed, or rejected.
* #parse_and_handle_deposit_request() to further check the details
* of the operation specified in the "wire" field of the JSON data.
* If everything checks out, this will ultimately lead to the
* "/deposit" being executed, or rejected.
* *
* @param rh context of the handler * @param rh context of the handler
* @param connection the MHD connection to handle * @param connection the MHD connection to handle

View File

@ -0,0 +1,146 @@
/*
This file is part of TALER
Copyright (C) 2014, 2015, 2016 Inria and GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_refund.c
* @brief Handle /refund requests; parses the POST and JSON and
* verifies the coin signature before handing things off
* to the database.
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
#include "taler_json_lib.h"
#include "taler-exchange-httpd_parsing.h"
#include "taler-exchange-httpd_refund.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_keystate.h"
#include "taler-exchange-httpd_validation.h"
/**
* We have parsed the JSON information about the refund, do some basic
* sanity checks (especially that the signature on the coin is valid)
* and then execute the refund. Note that we need the DB to check
* the fee structure, so this is not done here.
*
* @param connection the MHD connection to handle
* @param refund information about the refund
* @return MHD result code
*/
static int
verify_and_execute_refund (struct MHD_Connection *connection,
const struct TALER_EXCHANGEDB_Refund *refund)
{
struct TALER_RefundRequestPS dr;
dr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND);
dr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS));
dr.h_contract = refund->h_contract;
dr.transaction_id = GNUNET_htonll (refund->transaction_id);
dr.rtransaction_id = GNUNET_htonll (refund->rtransaction_id);
TALER_amount_hton (&dr.refund_amount,
&refund->refund_amount);
TALER_amount_hton (&dr.refund_fee,
&refund->refund_fee);
dr.merchant = refund->merchant_pub;
dr.coin_pub = refund->coin.coin_pub;
if (GNUNET_OK !=
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
&dr.purpose,
&refund->merchant_sig.eddsa_sig,
&refund->merchant_pub.eddsa_pub))
{
TALER_LOG_WARNING ("Invalid signature on /refund request\n");
return TMH_RESPONSE_reply_signature_invalid (connection,
"merchant_sig");
}
#if 1
GNUNET_break (0); // FIXME: not implemented
return MHD_NO;
#else
return TMH_DB_execute_refund (connection,
refund);
#endif
}
/**
* Handle a "/refund" request. Parses the JSON, and, if successful,
* passes the JSON data to #parse_and_handle_refund_request() to
* further check the details of the operation specified. If
* everything checks out, this will ultimately lead to the "/refund"
* being executed, or rejected.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] connection_cls the connection's closure (can be updated)
* @param upload_data upload data
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
int
TMH_REFUND_handler_refund (struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size)
{
json_t *json;
int res;
struct TALER_EXCHANGEDB_Refund refund;
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount ("refund_amount", &refund.refund_amount),
TALER_JSON_spec_amount ("refund_fee", &refund.refund_fee),
GNUNET_JSON_spec_fixed_auto ("H_contract", &refund.h_contract),
GNUNET_JSON_spec_uint64 ("transaction_id", &refund.transaction_id),
GNUNET_JSON_spec_fixed_auto ("coin_pub", &refund.coin.coin_pub),
GNUNET_JSON_spec_fixed_auto ("merchant_pub", &refund.merchant_pub),
GNUNET_JSON_spec_uint64 ("rtransaction_id", &refund.rtransaction_id),
GNUNET_JSON_spec_fixed_auto ("merchant_sig", &refund.merchant_sig),
GNUNET_JSON_spec_end ()
};
res = TMH_PARSE_post_json (connection,
connection_cls,
upload_data,
upload_data_size,
&json);
if (GNUNET_SYSERR == res)
return MHD_NO;
if ( (GNUNET_NO == res) || (NULL == json) )
return MHD_YES;
res = TMH_PARSE_json_data (connection,
json,
spec);
json_decref (json);
if (GNUNET_SYSERR == res)
return MHD_NO; /* hard failure */
if (GNUNET_NO == res)
return MHD_YES; /* failure */
res = verify_and_execute_refund (connection,
&refund);
GNUNET_JSON_parse_free (spec);
return res;
}
/* end of taler-exchange-httpd_refund.c */

View File

@ -0,0 +1,52 @@
/*
This file is part of TALER
Copyright (C) 2014, 2015, 2016 GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_refund.h
* @brief Handle /refund requests
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
*/
#ifndef TALER_EXCHANGE_HTTPD_REFUND_H
#define TALER_EXCHANGE_HTTPD_REFUND_H
#include <gnunet/gnunet_util_lib.h>
#include <microhttpd.h>
#include "taler-exchange-httpd.h"
/**
* Handle a "/refund" request. Parses the JSON, and, if successful,
* passes the JSON data to #parse_and_handle_refund_request() to
* further check the details of the operation specified. If
* everything checks out, this will ultimately lead to the "/refund"
* being executed, or rejected.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] connection_cls the connection's closure (can be updated)
* @param upload_data upload data
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
int
TMH_REFUND_handler_refund (struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size);
#endif

View File

@ -465,6 +465,83 @@ void
TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit); TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit);
/* ********************* /refund *********************** */
/**
* @brief A Refund Handle
*/
struct TALER_EXCHANGE_RefundHandle;
/**
* Callbacks of this type are used to serve the result of submitting a
* deposit permission request to a exchange.
*
* @param cls closure
* @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit;
* 0 if the exchange's reply is bogus (fails to follow the protocol)
* @param obj the received JSON reply, should be kept as proof (and, in particular,
* be forwarded to the customer)
*/
typedef void
(*TALER_EXCHANGE_RefundResultCallback) (void *cls,
unsigned int http_status,
const json_t *obj);
/**
* Submit a refund request to the exchange and get the exchange's
* response. This API is used by a merchant. Note that
* while we return the response verbatim to the caller for further
* processing, we do already verify that the response is well-formed
* (i.e. that signatures included in the response are all valid). If
* the exchange's reply is not well-formed, we return an HTTP status code
* of zero to @a cb.
*
* The @a exchange must be ready to operate (i.e. have
* finished processing the /keys reply). If this check fails, we do
* NOT initiate the transaction with the exchange and instead return NULL.
*
* @param exchange the exchange handle; the exchange must be ready to operate
* @param amount the amount to be refunded; must be larger than the refund fee
* (as that fee is still being subtracted), and smaller than the amount
* (with deposit fee) of the original deposit contribution of this coin
* @param h_contract hash of the contact of the merchant with the customer that is being refunded
* @param transaction_id transaction id for the transaction being refunded, must match @a h_contract
* @param coin_pub coins public key of the coin from the original deposit operation
* @param rtransaction_id transaction id for the transaction between merchant and customer (of refunding operation);
* this is needed as we may first do a partial refund and later a full refund. If both
* refunds are also over the same amount, we need the @a rtransaction_id to make the disjoint
* refund requests different (as requests are idempotent and otherwise the 2nd refund might not work).
* @param merchant_priv the private key of the merchant, used to generate signature for refund request
* @param cb the callback to call when a reply for this request is available
* @param cb_cls closure for the above callback
* @return a handle for this request; NULL if the inputs are invalid (i.e.
* signatures fail to verify). In this case, the callback is not called.
*/
struct TALER_EXCHANGE_RefundHandle *
TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange,
const struct TALER_Amount *amount,
const struct GNUNET_HashCode *h_contract,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
uint64_t rtransaction_id,
const struct TALER_MerchantPrivateKeyP *merchant_priv,
TALER_EXCHANGE_RefundResultCallback cb,
void *cb_cls);
/**
* Cancel a refund permission request. This function cannot be used
* on a request handle if a response is already served for it. If
* this function is called, the refund may or may not have happened.
* However, it is fine to try to refund the coin a second time.
*
* @param refund the refund request handle
*/
void
TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund);
/* ********************* /reserve/status *********************** */ /* ********************* /reserve/status *********************** */

View File

@ -298,6 +298,67 @@ struct TALER_EXCHANGEDB_Deposit
}; };
/**
* @brief Specification for a /refund operation. The combination of
* the coin's public key, the merchant's public key and the
* transaction ID must be unique. While a coin can (theoretically) be
* deposited at the same merchant twice (with partial spending), the
* merchant must either use a different public key or a different
* transaction ID for the two transactions. The same goes for
* refunds, hence we also have a "rtransaction" ID which is disjoint
* from the transaction ID. The same coin must not be used twice at
* the same merchant for the same transaction or rtransaction ID.
*/
struct TALER_EXCHANGEDB_Refund
{
/**
* Information about the coin that is being refunded.
*/
struct TALER_CoinPublicInfo coin;
/**
* Public key of the merchant.
*/
struct TALER_MerchantPublicKeyP merchant_pub;
/**
* Signature from the merchant affirming the refund.
*/
struct TALER_MerchantSignatureP merchant_sig;
/**
* Hash over the contract between merchant and customer
* (remains unknown to the Exchange).
*/
struct GNUNET_HashCode h_contract;
/**
* Merchant-generated transaction ID to detect duplicate
* transactions, of the original transaction that is being
* refunded.
*/
uint64_t transaction_id;
/**
* Merchant-generated REFUND transaction ID to detect duplicate
* refunds.
*/
uint64_t rtransaction_id;
/**
* Fraction of the original deposit's value to be refunded, including
* refund fee (if any). The coin is identified by @e coin_pub.
*/
struct TALER_Amount refund_amount;
/**
* Refund fee to be covered by the customer.
*/
struct TALER_Amount refund_fee;
};
/** /**
* @brief Global information for a refreshing session. Includes * @brief Global information for a refreshing session. Includes
* dimensions of the operation, security parameters and * dimensions of the operation, security parameters and

View File

@ -395,6 +395,64 @@ struct TALER_DepositConfirmationPS
}; };
/**
* @brief Format used to generate the signature on a request to refund
* a coin into the account of the customer.
*/
struct TALER_RefundRequestPS
{
/**
* Purpose must be #TALER_SIGNATURE_MERCHANT_REFUND.
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
/**
* Hash over the contract which is being refunded.
*/
struct GNUNET_HashCode h_contract GNUNET_PACKED;
/**
* Merchant-generated transaction ID of the orginal transaction.
*/
uint64_t transaction_id GNUNET_PACKED;
/**
* The coin's public key. This is the value that must have been
* signed (blindly) by the Exchange.
*/
struct TALER_CoinSpendPublicKeyP coin_pub;
/**
* The Merchant's public key. Allows the merchant to later refund
* the transaction or to inquire about the wire transfer identifier.
*/
struct TALER_MerchantPublicKeyP merchant;
/**
* Merchant-generated transaction ID for the refund.
*/
uint64_t rtransaction_id GNUNET_PACKED;
/**
* Amount to be refunded, including refund fee charged by the
* exchange to the customer.
*/
struct TALER_AmountNBO refund_amount;
/**
* Refund fee charged by the exchange. This must match the
* Exchange's denomination key's refund fee. If the client puts in
* an invalid refund fee (too high or too low) that does not match
* the Exchange's denomination key, the refund operation is invalid
* and will be rejected by the exchange. The @e amount_with_fee
* minus the @e refund_fee is the amount that will be credited to
* the original coin.
*/
struct TALER_AmountNBO refund_fee;
};
/** /**
* @brief Message signed by a coin to indicate that the coin should be * @brief Message signed by a coin to indicate that the coin should be
* melted. * melted.