aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog7
-rw-r--r--contrib/auditor-report.tex.j239
-rw-r--r--src/auditor/taler-auditor.c97
-rw-r--r--src/bank-lib/fakebank.c2
-rw-r--r--src/exchange-lib/exchange_api_deposit.c2
-rw-r--r--src/exchange-lib/exchange_api_refund.c73
-rw-r--r--src/exchange-lib/test_exchange_api.c59
-rw-r--r--src/exchange/taler-exchange-aggregator.c81
-rw-r--r--src/exchange/taler-exchange-httpd_deposit.c6
-rw-r--r--src/exchange/taler-exchange-httpd_refund.c1
-rw-r--r--src/exchange/taler-exchange-httpd_track_transfer.c2
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c150
-rw-r--r--src/exchangedb/test_exchangedb.c96
-rw-r--r--src/exchangedb/test_exchangedb_fees.c6
-rw-r--r--src/include/taler_error_codes.h84
-rw-r--r--src/include/taler_exchange_service.h44
-rw-r--r--src/include/taler_exchangedb_plugin.h44
-rw-r--r--src/include/taler_signatures.h31
-rw-r--r--src/util/amount.c4
19 files changed, 702 insertions, 126 deletions
diff --git a/ChangeLog b/ChangeLog
index 6edb0eaa4..e5d3eaf16 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+Thu Jan 4 11:55:41 CET 2018
+ Fix issue #5234 (aggregator ignoring refunds).
+ Misc. minor fixes to the auditor. -CG
+
+Mon Jan 1 23:15:37 CET 2018
+ Add TALER_EXCHANGE_refund2() API call to libtalerexchange. -CG
+
Thu Dec 14 15:32:50 CET 2017
Replaced taler-exchange-reservemod tool with new taler-bank-transfer
tool (#5195). Removed /admin/add/incoming API, replaced by new
diff --git a/contrib/auditor-report.tex.j2 b/contrib/auditor-report.tex.j2
index 0fbf74c19..380733037 100644
--- a/contrib/auditor-report.tex.j2
+++ b/contrib/auditor-report.tex.j2
@@ -102,8 +102,7 @@ making wire transfers that have been due.
The total amount the exchange currently lags behind is
{\bf {{ wire.total_amount_lag.value }}.{{ wire.total_amount_lag.fraction }}
- {{ wire.total_amount_lag.currency }}
-}.
+ {{ wire.total_amount_lag.currency }}}.
Note that some lag is perfectly normal, as tiny amounts that are too small to be wired
are deferred beyond the due date, hoping that additional transfers will push them above
@@ -210,7 +209,7 @@ the financial damage done to the customer).
{% if data.amount_arithmetic_inconsistencies|length() == 0 %}
{\bf No arithmetic problems detected.}
{% else %}
- \begin{longtable}{p{4.5cm}|l|rl|rl}
+ \begin{longtable}{p{3.5cm}|l|rl|rl}
{\bf Operation} & {\bf Table row} & \multicolumn{2}{|c|}{ {\bf Exchange}} & \multicolumn{2}{|c}{ {\bf Auditor}} \\
\hline \hline
\endfirsthead
@@ -219,11 +218,11 @@ the financial damage done to the customer).
\hline \hline
{\bf Operation} & {\bf Table row} & \multicolumn{2}{|c|}{ {\bf Exchange}} & \multicolumn{2}{|c}{ {\bf Auditor}} \\
\endfoot
- \hline
- {\bf Total} & &
- {{ data.total_arithmetic_delta_plus.value }}.{{ data.total_arithmetic_delta_plus.fraction }} &
+ \hline \hline
+ \multicolumn{2}{l|}{ {\bf $\sum$ Deltas (Auditor-Exchange)} } &
+ + {{ data.total_arithmetic_delta_plus.value }}.{{ data.total_arithmetic_delta_plus.fraction }} &
{{ data.total_arithmetic_delta_plus.currency }} &
- {{ data.total_arithmetic_delta_minus.value }}.{{ data.total_arithmetic_delta_minus.fraction }} &
+ - {{ data.total_arithmetic_delta_minus.value }}.{{ data.total_arithmetic_delta_minus.fraction }} &
{{ data.total_arithmetic_delta_minus.currency }} \\
\caption{Arithmetic inconsistencies.}
\label{table:amount:arithmetic:inconsistencies}
@@ -328,17 +327,17 @@ any effects on its own balance, those entries are excluded from the total.
{% if data.coin_inconsistencies|length() == 0 %}
{\bf All coin histories were unproblematic.}
{% else %}
- \begin{longtable}{l|p{5.5cm}|rl|rl}
- {\bf Operation} & {\bf Coin public key} & \multicolumn{2}{|c|}{ {\bf Exchange}} & \multicolumn{2}{|c|}{ {\bf Auditor}} \\
+ \begin{longtable}{p{1.8cm}|p{3cm}|rl|rl}
+ {\bf Operation} & {\bf Coin public key} & \multicolumn{2}{|c|}{ {\bf Exchange}} & \multicolumn{2}{|c}{ {\bf Auditor}} \\
\hline \hline
\endfirsthead
- {\bf Operation} & {\bf Coin public key} & \multicolumn{2}{|c|}{ {\bf Exchange}} & \multicolumn{2}{|c|}{ {\bf Auditor}} \\ \hline \hline
+ {\bf Operation} & {\bf Coin public key} & \multicolumn{2}{|c|}{ {\bf Exchange}} & \multicolumn{2}{|c}{ {\bf Auditor}} \\ \hline \hline
\endhead
\hline \hline
- {\bf Operation} & {\bf Coin public key} & \multicolumn{2}{|c|}{ {\bf Exchange}} & \multicolumn{2}{|c|}{ {\bf Auditor}} \\
+ {\bf Operation} & {\bf Coin public key} & \multicolumn{2}{|c|}{ {\bf Exchange}} & \multicolumn{2}{|c}{ {\bf Auditor}} \\
\endfoot
\hline
- {\bf Total} & &
+ \multicolumn{2}{l|}{ $\sum$ {\bf Delta (Auditor-Exchange)} } &
{{ data.total_coin_delta_plus.value }}.{{ data.total_coin_delta_plus.fraction }} &
{{ data.total_coin_delta_plus.currency }} &
- {{ data.total_coin_delta_minus.value }}.{{ data.total_coin_delta_minus.fraction }} &
@@ -593,7 +592,7 @@ have a clear financial impact.
\hline
{\bf Table} & {\bf Row} & {\bf Diagnostic} \\
\caption{Other wire table issues found (by table and row).}
- \label{table:misc}
+ \label{table:wire:misc}
\endlastfoot
{% for item in wire.row_inconsistencies %}
\verb! {{ item.table }} ! &
@@ -687,7 +686,7 @@ impact.
{% if data.row_inconsistencies|length() == 0 %}
{\bf No row inconsistencies found.}
{% else %}
- \begin{longtable}{p{1.5cm}|l|p{5.5}}
+ \begin{longtable}{p{1.5cm}|l|p{5.5cm}}
{\bf Table} & {\bf Row} & {\bf Diagnostic} \\
\hline \hline
\endfirsthead
@@ -697,13 +696,13 @@ impact.
\hline \hline
{\bf Table} & {\bf Row} & {\bf Diagnostic} \\
\endfoot
- \hline
+ \hline \hline
{\bf Table} & {\bf Row} & {\bf Diagnostic} \\
\caption{Other issues found (by table and row).}
\label{table:misc}
\endlastfoot
{% for item in data.row_inconsistencies %}
- {{ item.table }} &
+ \verb! {{ item.table }} ! &
{{ item.row }} &
{{ item.diagnostic }} \\ \hline
{% endfor %}
@@ -728,12 +727,12 @@ reserve expired.
{\bf All expired reserves were closed.}
{% else %}
\begin{longtable}{p{1.5cm}|c|rl}
- {\bf Reserve} & {\bf Expired} & \multicolumn{2}{|c|}{ {\bf Balance}} \\ \hline \hline
+ {\bf Reserve} & {\bf Expired} & \multicolumn{2}{|c}{ {\bf Balance}} \\ \hline \hline
\endfirsthead
- {\bf Reserve} & {\bf Expired} & \multicolumn{2}{|c|}{ {\bf Balance}} \\ \hline \hline
+ {\bf Reserve} & {\bf Expired} & \multicolumn{2}{|c}{ {\bf Balance}} \\ \hline \hline
\endhead
\hline \hline
- {\bf Reserve} & {\bf Expired} & \multicolumn{2}{|c|}{ {\bf Balance}}
+ {\bf Reserve} & {\bf Expired} & \multicolumn{2}{|c}{ {\bf Balance}}
\endfoot
\hline
{\bf Sum} & &
@@ -854,7 +853,7 @@ This section lists issues with wire transfers related to timestamps.
\label{table:wire:bad_time}
\endlastfoot
{% for item in wire.row_minor_inconsistencies %}
- {\tt {{ item.table }} } & {{ item.row }} & {{ item.diagnostic }} \\ \hline
+ \verb! {{ item.table }} ! & {{ item.row }} & {{ item.diagnostic }} \\ \hline
{% endfor %}
\end{longtable}
{% endif %}
diff --git a/src/auditor/taler-auditor.c b/src/auditor/taler-auditor.c
index 870a81a42..e807378fe 100644
--- a/src/auditor/taler-auditor.c
+++ b/src/auditor/taler-auditor.c
@@ -391,7 +391,7 @@ report_amount_arithmetic_inconsistency (const char *operation,
"profitable", (json_int_t) profitable));
if (0 != profitable)
{
- target = profitable
+ target = (1 == profitable)
? &total_arithmetic_delta_plus
: &total_arithmetic_delta_minus;
GNUNET_break (GNUNET_OK ==
@@ -452,7 +452,7 @@ report_coin_arithmetic_inconsistency (const char *operation,
"profitable", (json_int_t) profitable));
if (0 != profitable)
{
- target = profitable
+ target = (1 == profitable)
? &total_coin_delta_plus
: &total_coin_delta_minus;
GNUNET_break (GNUNET_OK ==
@@ -1664,10 +1664,15 @@ struct WireFeeInfo
struct GNUNET_TIME_Absolute end_date;
/**
- * How high is the fee.
+ * How high is the wire fee.
*/
struct TALER_Amount wire_fee;
+ /**
+ * How high is the closing fee.
+ */
+ struct TALER_Amount closing_fee;
+
};
@@ -1698,11 +1703,6 @@ struct AggregationContext
struct WireFeeInfo *fee_tail;
/**
- * How much did we make in aggregation fees.
- */
- struct TALER_Amount total_aggregation_feesX;
-
- /**
* Final result status.
*/
enum GNUNET_DB_QueryStatus qs;
@@ -1798,7 +1798,6 @@ struct WireCheckContext
* @param dki denomination information about the coin
* @param tl_head head of transaction history to verify
* @param[out] merchant_gain amount the coin contributes to the wire transfer to the merchant
- * @param[out] merchant_fees fees the exchange charged the merchant for the transaction(s)
* @return #GNUNET_OK on success, #GNUNET_SYSERR on error
*/
static int
@@ -1807,14 +1806,16 @@ check_transaction_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki,
const struct TALER_EXCHANGEDB_TransactionList *tl_head,
- struct TALER_Amount *merchant_gain,
- struct TALER_Amount *merchant_fees)
+ struct TALER_Amount *merchant_gain)
{
struct TALER_Amount expenditures;
struct TALER_Amount refunds;
struct TALER_Amount spent;
struct TALER_Amount value;
struct TALER_Amount merchant_loss;
+ struct TALER_Amount merchant_delta;
+ const struct TALER_Amount *deposit_fee;
+ int refund_deposit_fee;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Checking transaction history of coin %s\n",
@@ -1832,14 +1833,13 @@ check_transaction_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
merchant_gain));
GNUNET_assert (GNUNET_OK ==
TALER_amount_get_zero (currency,
- merchant_fees));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (currency,
&merchant_loss));
/* Go over transaction history to compute totals; note that we do not
know the order, so instead of subtracting we compute positive
(deposit, melt) and negative (refund) values separately here,
and then subtract the negative from the positive after the loop. */
+ refund_deposit_fee = GNUNET_NO;
+ deposit_fee = NULL;
for (const struct TALER_EXCHANGEDB_TransactionList *tl = tl_head;NULL != tl;tl = tl->next)
{
const struct TALER_Amount *amount_with_fee;
@@ -1871,7 +1871,7 @@ check_transaction_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
{
struct TALER_Amount amount_without_fee;
- if (GNUNET_OK !=
+ if (GNUNET_OK !=
TALER_amount_subtract (&amount_without_fee,
amount_with_fee,
fee))
@@ -1890,14 +1890,7 @@ check_transaction_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Detected applicable deposit of %s\n",
TALER_amount2s (&amount_without_fee));
- if (GNUNET_OK !=
- TALER_amount_add (merchant_fees,
- merchant_fees,
- fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
+ deposit_fee = fee;
}
/* Check that the fees given in the transaction list and in dki match */
TALER_amount_ntoh (&tmp,
@@ -1975,14 +1968,7 @@ check_transaction_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Detected applicable refund of %s\n",
TALER_amount2s (amount_with_fee));
- if (GNUNET_OK !=
- TALER_amount_add (merchant_fees,
- merchant_fees,
- fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
+ refund_deposit_fee = GNUNET_YES;
}
/* Check that the fees given in the transaction list and in dki match */
TALER_amount_ntoh (&tmp,
@@ -2008,9 +1994,19 @@ check_transaction_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
}
break;
}
-
} /* for 'tl' */
+ if ( (GNUNET_YES == refund_deposit_fee) &&
+ (NULL != deposit_fee) )
+ {
+ /* We had a /deposit operation AND a /refund operation,
+ and should thus not charge the merchant the /deposit fee */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (merchant_gain,
+ merchant_gain,
+ deposit_fee));
+ }
+
/* Calculate total balance change, i.e. expenditures minus refunds */
if (GNUNET_SYSERR ==
TALER_amount_subtract (&spent,
@@ -2022,7 +2018,7 @@ check_transaction_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
coin_pub,
&expenditures,
&refunds,
- 0);
+ 1);
return GNUNET_SYSERR;
}
@@ -2041,9 +2037,10 @@ check_transaction_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
return GNUNET_SYSERR;
}
- /* Finally, update @a merchant_gain by subtracting what he "lost" from refunds */
+ /* Finally, update @a merchant_gain by subtracting what he "lost"
+ from refunds */
if (GNUNET_SYSERR ==
- TALER_amount_subtract (merchant_gain,
+ TALER_amount_subtract (&merchant_delta,
merchant_gain,
&merchant_loss))
{
@@ -2052,9 +2049,10 @@ check_transaction_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
coin_pub,
merchant_gain,
&merchant_loss,
- 0);
+ 1);
return GNUNET_SYSERR;
}
+ *merchant_gain = merchant_delta;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Coin %s contributes %s to contract %s\n",
TALER_B2S (coin_pub),
@@ -2077,7 +2075,8 @@ check_transaction_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
* @param h_contract_terms which proposal was this payment about
* @param coin_pub which public key was this payment about
* @param coin_value amount contributed by this coin in total (with fee)
- * @param coin_fee applicable fee for this coin
+ * @param deposit_fee applicable deposit fee for this coin, actual
+ * fees charged may differ if coin was refunded
*/
static void
wire_transfer_information_cb (void *cls,
@@ -2089,12 +2088,11 @@ wire_transfer_information_cb (void *cls,
const struct GNUNET_HashCode *h_contract_terms,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *coin_value,
- const struct TALER_Amount *coin_fee)
+ const struct TALER_Amount *deposit_fee)
{
struct WireCheckContext *wcc = cls;
const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
struct TALER_Amount computed_value;
- struct TALER_Amount computed_fees;
struct TALER_Amount coin_value_without_fee;
struct TALER_EXCHANGEDB_TransactionList *tl;
const struct TALER_CoinPublicInfo *coin;
@@ -2156,18 +2154,17 @@ wire_transfer_information_cb (void *cls,
merchant_pub,
dki,
tl,
- &computed_value,
- &computed_fees);
+ &computed_value);
if (GNUNET_SYSERR ==
TALER_amount_subtract (&coin_value_without_fee,
coin_value,
- coin_fee))
+ deposit_fee))
{
wcc->qs = GNUNET_DB_STATUS_HARD_ERROR;
report_amount_arithmetic_inconsistency ("aggregation (fee structure)",
rowid,
coin_value,
- coin_fee,
+ deposit_fee,
-1);
return;
}
@@ -2182,17 +2179,6 @@ wire_transfer_information_cb (void *cls,
&computed_value,
-1);
}
- if (0 !=
- TALER_amount_cmp (&computed_fees,
- coin_fee))
- {
- wcc->qs = GNUNET_DB_STATUS_HARD_ERROR;
- report_amount_arithmetic_inconsistency ("aggregation (fee)",
- rowid,
- coin_fee,
- &computed_fees,
- 1);
- }
edb->free_coin_transaction_list (edb->cls,
tl);
@@ -2276,6 +2262,7 @@ get_wire_fee (struct AggregationContext *ac,
&wfi->start_date,
&wfi->end_date,
&wfi->wire_fee,
+ &wfi->closing_fee,
&master_sig))
{
GNUNET_break (0);
@@ -2299,6 +2286,8 @@ get_wire_fee (struct AggregationContext *ac,
wp.end_date = GNUNET_TIME_absolute_hton (wfi->end_date);
TALER_amount_hton (&wp.wire_fee,
&wfi->wire_fee);
+ TALER_amount_hton (&wp.closing_fee,
+ &wfi->closing_fee);
if (GNUNET_OK !=
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_WIRE_FEES,
&wp.purpose,
diff --git a/src/bank-lib/fakebank.c b/src/bank-lib/fakebank.c
index 3d4acf4c8..b54cf44c6 100644
--- a/src/bank-lib/fakebank.c
+++ b/src/bank-lib/fakebank.c
@@ -613,7 +613,7 @@ handle_reject (struct TALER_FAKEBANK_Handle *h,
if (GNUNET_OK != found)
return create_bank_error (connection,
MHD_HTTP_NOT_FOUND,
- TALER_EC_BANK_REJECT_NOT_FOUND,
+ TALER_EC_BANK_REJECT_TRANSACTION_NOT_FOUND,
"transaction unknown");
/* finally build regular response */
resp = MHD_create_response_from_buffer (0,
diff --git a/src/exchange-lib/exchange_api_deposit.c b/src/exchange-lib/exchange_api_deposit.c
index d90b1aa75..76e3e4da9 100644
--- a/src/exchange-lib/exchange_api_deposit.c
+++ b/src/exchange-lib/exchange_api_deposit.c
@@ -454,7 +454,7 @@ TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange,
" s:o," /* merchant_pub */
" s:o, s:o," /* refund_deadline, wire_deadline */
" s:o}", /* coin_sig */
- "f", TALER_JSON_from_amount (amount),
+ "contribution", TALER_JSON_from_amount (amount),
"wire", wire_details,
"H_wire", GNUNET_JSON_from_data_auto (&h_wire),
"h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms),
diff --git a/src/exchange-lib/exchange_api_refund.c b/src/exchange-lib/exchange_api_refund.c
index a39dd23a8..ef1d66b90 100644
--- a/src/exchange-lib/exchange_api_refund.c
+++ b/src/exchange-lib/exchange_api_refund.c
@@ -243,12 +243,8 @@ TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange,
TALER_EXCHANGE_RefundResultCallback cb,
void *cb_cls)
{
- struct TALER_EXCHANGE_RefundHandle *rh;
- struct GNUNET_CURL_Context *ctx;
struct TALER_RefundRequestPS rr;
struct TALER_MerchantSignatureP merchant_sig;
- json_t *refund_obj;
- CURL *eh;
GNUNET_assert (GNUNET_YES ==
MAH_handle_is_ready (exchange));
@@ -267,7 +263,68 @@ TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange,
GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv,
&rr.purpose,
&merchant_sig.eddsa_sig));
- refund_obj = json_pack ("{s:o, s:o," /* amount/fee */
+ return TALER_EXCHANGE_refund2 (exchange,
+ amount,
+ refund_fee,
+ h_contract_terms,
+ coin_pub,
+ rtransaction_id,
+ &rr.merchant,
+ &merchant_sig,
+ cb,
+ cb_cls);
+}
+
+
+/**
+ * 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 refund_fee fee applicable to this coin for the refund
+ * @param h_contract_terms hash of the contact of the merchant with the customer that is being refunded
+ * @param coin_pub coin’s 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_pub public key of the merchant
+ * @param merchant_sig signature affirming the refund from the merchant
+ * @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_refund2 (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *refund_fee,
+ const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t rtransaction_id,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantSignatureP *merchant_sig,
+ TALER_EXCHANGE_RefundResultCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_RefundHandle *rh;
+ struct GNUNET_CURL_Context *ctx;
+ json_t *refund_obj;
+ CURL *eh;
+
+refund_obj = json_pack ("{s:o, s:o," /* amount/fee */
" s:o, s:o," /* h_contract_terms, coin_pub */
" s:I," /* rtransaction id */
" s:o, s:o}", /* merchant_pub, merchant_sig */
@@ -276,8 +333,8 @@ TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange,
"h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms),
"coin_pub", GNUNET_JSON_from_data_auto (coin_pub),
"rtransaction_id", (json_int_t) rtransaction_id,
- "merchant_pub", GNUNET_JSON_from_data_auto (&rr.merchant),
- "merchant_sig", GNUNET_JSON_from_data_auto (&merchant_sig)
+ "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub),
+ "merchant_sig", GNUNET_JSON_from_data_auto (merchant_sig)
);
if (NULL == refund_obj)
{
@@ -294,7 +351,7 @@ TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange,
rh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND);
rh->depconf.h_contract_terms = *h_contract_terms;
rh->depconf.coin_pub = *coin_pub;
- rh->depconf.merchant = rr.merchant;
+ rh->depconf.merchant = *merchant_pub;
rh->depconf.rtransaction_id = GNUNET_htonll (rtransaction_id);
TALER_amount_hton (&rh->depconf.refund_amount,
amount);
diff --git a/src/exchange-lib/test_exchange_api.c b/src/exchange-lib/test_exchange_api.c
index 6fd9ad693..bccd4f99c 100644
--- a/src/exchange-lib/test_exchange_api.c
+++ b/src/exchange-lib/test_exchange_api.c
@@ -3562,7 +3562,66 @@ run (void *cls)
.details.refund.fee = "EUR:0.01",
.details.refund.deposit_ref = "deposit-refund-2",
},
+ { .oc = OC_CHECK_BANK_TRANSFERS_EMPTY,
+ .label = "check-empty-after-refund" },
+
+
+ /* Test refunded coins are never executed, even past
+ refund deadline */
+ { .oc = OC_ADMIN_ADD_INCOMING,
+ .label = "create-reserve-rb",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.admin_add_incoming.debit_account_no = 42,
+ .details.admin_add_incoming.credit_account_no = EXCHANGE_ACCOUNT_NO,
+ .details.admin_add_incoming.auth_username = "user42",
+ .details.admin_add_incoming.auth_password = "pass42",
+ .details.admin_add_incoming.amount = "EUR:5.01" },
+ /* Run wirewatch to observe /admin/add/incoming */
+ { .oc = OC_RUN_WIREWATCH,
+ .label = "wirewatch-3b" },
+ /* Withdraw a 5 EUR coin, at fee of 1 ct */
+ { .oc = OC_WITHDRAW_SIGN,
+ .label = "withdraw-coin-rb",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.reserve_withdraw.reserve_reference = "create-reserve-rb",
+ .details.reserve_withdraw.amount = "EUR:5" },
+ /* Spend 5 EUR of the 5 EUR coin (in full)
+ (merchant would receive EUR:4.99 due to 1 ct deposit fee) */
+ { .oc = OC_DEPOSIT,
+ .label = "deposit-refund-1b",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.deposit.amount = "EUR:5",
+ .details.deposit.coin_ref = "withdraw-coin-rb",
+ .details.deposit.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost:8082/\", \"account_number\":42 }",
+ .details.deposit.contract_terms = "{ \"items\" : [ { \"name\":\"ice cream\", \"value\":\"EUR:5\" } ] }",
+ .details.deposit.refund_deadline = { 0 },
+ },
+ { .oc = OC_CHECK_BANK_TRANSFER,
+ .label = "check_bank_transfer-aai-3b",
+ .details.check_bank_transfer.exchange_base_url = "https://exchange.com/",
+ .details.check_bank_transfer.amount = "EUR:5.01",
+ .details.check_bank_transfer.account_debit = 42,
+ .details.check_bank_transfer.account_credit = 2
+ },
+ /* Trigger refund (before aggregator had a chance to execute
+ deposit, even though refund deadline was zero) */
+ { .oc = OC_REFUND,
+ .label = "refund-ok-fast",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.refund.amount = "EUR:5",
+ .details.refund.fee = "EUR:0.01",
+ .details.refund.deposit_ref = "deposit-refund-1b",
+ },
+ /* Run transfers. This will do the transfer as refund deadline
+ was 0, except of course because the refund succeeded, the
+ transfer should no longer be done. */
+ { .oc = OC_RUN_AGGREGATOR,
+ .label = "run-aggregator-3b" },
+ /* check that aggregator didn't do anything, as expected */
+ { .oc = OC_CHECK_BANK_TRANSFERS_EMPTY,
+ .label = "check-refund-fast-not-run" },
+
/* ************** End of refund API testing************* */
/* ************** Test /payback API ************* */
diff --git a/src/exchange/taler-exchange-aggregator.c b/src/exchange/taler-exchange-aggregator.c
index 3de5630d4..d5d43052d 100644
--- a/src/exchange/taler-exchange-aggregator.c
+++ b/src/exchange/taler-exchange-aggregator.c
@@ -124,6 +124,11 @@ struct AggregationUnit
struct GNUNET_HashCode h_wire;
/**
+ * Hash code of contract we are currently looking into.
+ */
+ const struct GNUNET_HashCode *h_contract;
+
+ /**
* Wire transfer identifier we use.
*/
struct TALER_WireTransferIdentifierRawP wtid;
@@ -374,6 +379,7 @@ update_fees (struct WirePlugin *wp,
p->start_date,
p->end_date,
&p->wire_fee,
+ &p->closing_fee,
&p->master_sig);
if (qs < 0)
{
@@ -568,6 +574,53 @@ exchange_serve_process_config ()
/**
+ * Callback invoked with information about refunds applicable
+ * to a particular coin. Subtract refunded amount(s) from
+ * the aggregation unit's total amount.
+ *
+ * @param cls closure with a `struct AggregationUnit *`
+ * @param merchant_pub public key of merchant who authorized refund
+ * @param merchant_sig signature of merchant authorizing refund
+ * @param h_contract hash of contract being refunded
+ * @param rtransaction_id refund transaction ID
+ * @param amount_with_fee amount being refunded
+ * @param refund_fee fee the exchange keeps for the refund processing
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static int
+refund_by_coin_cb (void *cls,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantSignatureP *merchant_sig,
+ const struct GNUNET_HashCode *h_contract,
+ uint64_t rtransaction_id,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *refund_fee)
+{
+ struct AggregationUnit *au = cls;
+
+ /* TODO: potential optimization: include these conditions
+ in the SELECT! */
+ if (0 != memcmp (merchant_pub,
+ &au->merchant_pub,
+ sizeof (struct TALER_MerchantPublicKeyP)))
+ return GNUNET_OK; /* different merchant */
+ if (0 != memcmp (h_contract,
+ au->h_contract,
+ sizeof (struct GNUNET_HashCode)))
+ return GNUNET_OK; /* different contract */
+ if (GNUNET_OK !=
+ TALER_amount_subtract (&au->total_amount,
+ &au->total_amount,
+ amount_with_fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
* Function called with details about deposits that have been made,
* with the goal of executing the corresponding wire transaction.
*
@@ -609,6 +662,20 @@ deposit_cb (void *cls,
return GNUNET_DB_STATUS_HARD_ERROR;
}
au->row_id = row_id;
+
+ au->h_contract = h_contract_terms;
+ qs = db_plugin->select_refunds_by_coin (db_plugin->cls,
+ au->session,
+ coin_pub,
+ &refund_by_coin_cb,
+ au);
+ au->h_contract = NULL;
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+
GNUNET_assert (NULL == au->wire);
au->wire = json_incref ((json_t *) wire);
if (GNUNET_OK !=
@@ -730,6 +797,20 @@ aggregate_cb (void *cls,
/* Skip this one, but keep going! */
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
+
+ au->h_contract = h_contract_terms;
+ qs = db_plugin->select_refunds_by_coin (db_plugin->cls,
+ au->session,
+ coin_pub,
+ &refund_by_coin_cb,
+ au);
+ au->h_contract = NULL;
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+
if (au->rows_offset >= aggregation_limit)
{
/* Bug: we asked for at most #aggregation_limit results! */
diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c
index b7fb3452c..542c56c9c 100644
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ b/src/exchange/taler-exchange-httpd_deposit.c
@@ -149,6 +149,8 @@ deposit_transaction (void *cls,
{
struct TALER_Amount amount_without_fee;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "/deposit replay, accepting again!\n");
GNUNET_assert (GNUNET_OK ==
TALER_amount_subtract (&amount_without_fee,
&deposit->amount_with_fee,
@@ -191,6 +193,8 @@ deposit_transaction (void *cls,
if (0 < TALER_amount_cmp (&spent,
&dc->value))
{
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Deposited coin has insufficient funds left!\n");
*mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (connection,
TALER_EC_DEPOSIT_INSUFFICIENT_FUNDS,
tl);
@@ -376,7 +380,7 @@ TEH_DEPOSIT_handler_deposit (struct TEH_RequestHandler *rh,
struct GNUNET_HashCode my_h_wire;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_json ("wire", &wire),
- TALER_JSON_spec_amount ("f", &deposit.amount_with_fee),
+ TALER_JSON_spec_amount ("contribution", &deposit.amount_with_fee),
TALER_JSON_spec_denomination_public_key ("denom_pub", &deposit.coin.denom_pub),
TALER_JSON_spec_denomination_signature ("ub_sig", &deposit.coin.denom_sig),
GNUNET_JSON_spec_fixed_auto ("coin_pub", &deposit.coin.coin_pub),
diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c
index f0aaa65c0..986c9d312 100644
--- a/src/exchange/taler-exchange-httpd_refund.c
+++ b/src/exchange/taler-exchange-httpd_refund.c
@@ -285,7 +285,6 @@ refund_transaction (void *cls,
}
/* check if we already send the money for the /deposit */
- // FIXME: DB API...
qs = TEH_plugin->test_deposit_done (TEH_plugin->cls,
session,
dep);
diff --git a/src/exchange/taler-exchange-httpd_track_transfer.c b/src/exchange/taler-exchange-httpd_track_transfer.c
index 4d28096be..38c6c29e3 100644
--- a/src/exchange/taler-exchange-httpd_track_transfer.c
+++ b/src/exchange/taler-exchange-httpd_track_transfer.c
@@ -352,6 +352,7 @@ track_transfer_transaction (void *cls,
struct GNUNET_TIME_Absolute wire_fee_start_date;
struct GNUNET_TIME_Absolute wire_fee_end_date;
struct TALER_MasterSignatureP wire_fee_master_sig;
+ struct TALER_Amount closing_fee;
ctx->is_valid = GNUNET_NO;
ctx->wdd_head = NULL;
@@ -393,6 +394,7 @@ track_transfer_transaction (void *cls,
&wire_fee_start_date,
&wire_fee_end_date,
&ctx->wire_fee,
+ &closing_fee,
&wire_fee_master_sig);
if (0 >= qs)
{
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c
index 36ae3e54e..847746412 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -383,6 +383,9 @@ postgres_create_tables (void *cls)
",wire_fee_val INT8 NOT NULL"
",wire_fee_frac INT4 NOT NULL"
",wire_fee_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
+ ",closing_fee_val INT8 NOT NULL"
+ ",closing_fee_frac INT4 NOT NULL"
+ ",closing_fee_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
",master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)"
",PRIMARY KEY (wire_method, start_date)" /* this combo must be unique */
");"),
@@ -1170,6 +1173,9 @@ postgres_prepare (PGconn *db_conn)
",wire_fee_val"
",wire_fee_frac"
",wire_fee_curr"
+ ",closing_fee_val"
+ ",closing_fee_frac"
+ ",closing_fee_curr"
",master_sig"
" FROM wire_fee"
" WHERE wire_method=$1"
@@ -1185,10 +1191,13 @@ postgres_prepare (PGconn *db_conn)
",wire_fee_val"
",wire_fee_frac"
",wire_fee_curr"
+ ",closing_fee_val"
+ ",closing_fee_frac"
+ ",closing_fee_curr"
",master_sig"
") VALUES "
- "($1, $2, $3, $4, $5, $6, $7);",
- 7),
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);",
+ 19),
/* Used in #postgres_store_wire_transfer_out */
GNUNET_PQ_make_prepare ("insert_wire_out",
"INSERT INTO wire_out "
@@ -3033,6 +3042,128 @@ postgres_insert_refund (void *cls,
/**
+ * Closure for #get_refunds_cb().
+ */
+struct SelectRefundContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_RefundCoinCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Set to #GNUNET_SYSERR on error.
+ */
+ 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 SelectRefundContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+get_refunds_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct SelectRefundContext *srctx = cls;
+
+ for (unsigned int i=0;i<num_results;i++)
+ {
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct TALER_MerchantSignatureP merchant_sig;
+ struct GNUNET_HashCode h_contract;
+ uint64_t rtransaction_id;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount refund_fee;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &merchant_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
+ &merchant_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &h_contract),
+ GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
+ &rtransaction_id),
+ TALER_PQ_result_spec_amount ("amount_with_fee",
+ &amount_with_fee),
+ TALER_PQ_result_spec_amount ("fee_refund",
+ &refund_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ srctx->status = GNUNET_SYSERR;
+ return;
+ }
+ if (GNUNET_OK !=
+ srctx->cb (srctx->cb_cls,
+ &merchant_pub,
+ &merchant_sig,
+ &h_contract,
+ rtransaction_id,
+ &amount_with_fee,
+ &refund_fee))
+ return;
+ }
+}
+
+
+/**
+ * Select refunds by @a coin_pub.
+ *
+ * @param cls closure of plugin
+ * @param session database handle to use
+ * @param coin_pub coin to get refunds for
+ * @param cb function to call for each refund found
+ * @param cb_cls closure for @a cb
+ * @return query result status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_select_refunds_by_coin (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ TALER_EXCHANGEDB_RefundCoinCallback cb,
+ void *cb_cls)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct SelectRefundContext srctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .status = GNUNET_OK
+ };
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
+ "get_refunds_by_coin",
+ params,
+ &get_refunds_cb,
+ &srctx);
+ if (GNUNET_SYSERR == srctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
+
+
+/**
* Lookup refresh melt commitment data under the given @a rc.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
@@ -4234,6 +4365,7 @@ postgres_insert_aggregation_tracking (void *cls,
* @param[out] start_date when does the fee go into effect
* @param[out] end_date when does the fee end being valid
* @param[out] wire_fee how high is the wire transfer fee
+ * @param[out] closing_fee how high is the closing fee
* @param[out] master_sig signature over the above by the exchange master key
* @return status of the transaction
*/
@@ -4245,6 +4377,7 @@ postgres_get_wire_fee (void *cls,
struct GNUNET_TIME_Absolute *start_date,
struct GNUNET_TIME_Absolute *end_date,
struct TALER_Amount *wire_fee,
+ struct TALER_Amount *closing_fee,
struct TALER_MasterSignatureP *master_sig)
{
struct GNUNET_PQ_QueryParam params[] = {
@@ -4256,6 +4389,7 @@ postgres_get_wire_fee (void *cls,
TALER_PQ_result_spec_absolute_time ("start_date", start_date),
TALER_PQ_result_spec_absolute_time ("end_date", end_date),
TALER_PQ_result_spec_amount ("wire_fee", wire_fee),
+ TALER_PQ_result_spec_amount ("closing_fee", closing_fee),
GNUNET_PQ_result_spec_auto_from_type ("master_sig", master_sig),
GNUNET_PQ_result_spec_end
};
@@ -4276,6 +4410,7 @@ postgres_get_wire_fee (void *cls,
* @param start_date when does the fee go into effect
* @param end_date when does the fee end being valid
* @param wire_fee how high is the wire transfer fee
+ * @param closing_fee how high is the closing fee
* @param master_sig signature over the above by the exchange master key
* @return transaction status code
*/
@@ -4286,6 +4421,7 @@ postgres_insert_wire_fee (void *cls,
struct GNUNET_TIME_Absolute start_date,
struct GNUNET_TIME_Absolute end_date,
const struct TALER_Amount *wire_fee,
+ const struct TALER_Amount *closing_fee,
const struct TALER_MasterSignatureP *master_sig)
{
struct GNUNET_PQ_QueryParam params[] = {
@@ -4293,10 +4429,12 @@ postgres_insert_wire_fee (void *cls,
TALER_PQ_query_param_absolute_time (&start_date),
TALER_PQ_query_param_absolute_time (&end_date),
TALER_PQ_query_param_amount (wire_fee),
+ TALER_PQ_query_param_amount (closing_fee),
GNUNET_PQ_query_param_auto_from_type (master_sig),
GNUNET_PQ_query_param_end
};
struct TALER_Amount wf;
+ struct TALER_Amount cf;
struct TALER_MasterSignatureP sig;
struct GNUNET_TIME_Absolute sd;
struct GNUNET_TIME_Absolute ed;
@@ -4309,6 +4447,7 @@ postgres_insert_wire_fee (void *cls,
&sd,
&ed,
&wf,
+ &cf,
&sig);
if (qs < 0)
return qs;
@@ -4327,6 +4466,12 @@ postgres_insert_wire_fee (void *cls,
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
+ if (0 != TALER_amount_cmp (closing_fee,
+ &cf))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
if ( (sd.abs_value_us != start_date.abs_value_us) ||
(ed.abs_value_us != end_date.abs_value_us) )
{
@@ -6236,6 +6381,7 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
plugin->iterate_matching_deposits = &postgres_iterate_matching_deposits;
plugin->insert_deposit = &postgres_insert_deposit;
plugin->insert_refund = &postgres_insert_refund;
+ plugin->select_refunds_by_coin = &postgres_select_refunds_by_coin;
plugin->insert_melt = &postgres_insert_melt;
plugin->get_melt = &postgres_get_melt;
plugin->insert_refresh_reveal = &postgres_insert_refresh_reveal;
diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
index 5891a08a0..a0eb50f08 100644
--- a/src/exchangedb/test_exchangedb.c
+++ b/src/exchangedb/test_exchangedb.c
@@ -1062,10 +1062,12 @@ test_wire_fees (struct TALER_EXCHANGEDB_Session *session)
struct GNUNET_TIME_Absolute start_date;
struct GNUNET_TIME_Absolute end_date;
struct TALER_Amount wire_fee;
+ struct TALER_Amount closing_fee;
struct TALER_MasterSignatureP master_sig;
struct GNUNET_TIME_Absolute sd;
struct GNUNET_TIME_Absolute ed;
struct TALER_Amount fee;
+ struct TALER_Amount fee2;
struct TALER_MasterSignatureP ms;
start_date = GNUNET_TIME_absolute_get ();
@@ -1075,6 +1077,9 @@ test_wire_fees (struct TALER_EXCHANGEDB_Session *session)
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":1.424242",
&wire_fee));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":2.424242",
+ &closing_fee));
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
&master_sig,
sizeof (master_sig));
@@ -1085,6 +1090,7 @@ test_wire_fees (struct TALER_EXCHANGEDB_Session *session)
start_date,
end_date,
&wire_fee,
+ &closing_fee,
&master_sig))
{
GNUNET_break (0);
@@ -1097,6 +1103,7 @@ test_wire_fees (struct TALER_EXCHANGEDB_Session *session)
start_date,
end_date,
&wire_fee,
+ &closing_fee,
&master_sig))
{
GNUNET_break (0);
@@ -1112,6 +1119,7 @@ test_wire_fees (struct TALER_EXCHANGEDB_Session *session)
&sd,
&ed,
&fee,
+ &fee2,
&ms))
{
GNUNET_break (0);
@@ -1125,6 +1133,7 @@ test_wire_fees (struct TALER_EXCHANGEDB_Session *session)
&sd,
&ed,
&fee,
+ &fee2,
&ms))
{
GNUNET_break (0);
@@ -1134,6 +1143,8 @@ test_wire_fees (struct TALER_EXCHANGEDB_Session *session)
(ed.abs_value_us != end_date.abs_value_us) ||
(0 != TALER_amount_cmp (&fee,
&wire_fee)) ||
+ (0 != TALER_amount_cmp (&fee2,
+ &closing_fee)) ||
(0 != memcmp (&ms,
&master_sig,
sizeof (ms))) )
@@ -1394,6 +1405,72 @@ wire_missing_cb (void *cls,
/**
+ * Callback invoked with information about refunds applicable
+ * to a particular coin.
+ *
+ * @param cls closure with the `struct TALER_EXCHANGEDB_Refund *` we expect to get
+ * @param merchant_pub public key of merchant who authorized refund
+ * @param merchant_sig signature of merchant authorizing refund
+ * @param h_contract hash of contract being refunded
+ * @param rtransaction_id refund transaction ID
+ * @param amount_with_fee amount being refunded
+ * @param refund_fee fee the exchange keeps for the refund processing
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static int
+check_refund_cb (void *cls,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantSignatureP *merchant_sig,
+ const struct GNUNET_HashCode *h_contract,
+ uint64_t rtransaction_id,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *refund_fee)
+{
+ const struct TALER_EXCHANGEDB_Refund *refund = cls;
+
+ if (0 != memcmp (merchant_pub,
+ &refund->merchant_pub,
+ sizeof (struct TALER_MerchantPublicKeyP)))
+ {
+ GNUNET_break (0);
+ result = 66;
+ }
+ if (0 != memcmp (merchant_sig,
+ &refund->merchant_sig,
+ sizeof (struct TALER_MerchantSignatureP)))
+ {
+ GNUNET_break (0);
+ result = 66;
+ }
+ if (0 != memcmp (h_contract,
+ &refund->h_contract_terms,
+ sizeof (struct GNUNET_HashCode)))
+ {
+ GNUNET_break (0);
+ result = 66;
+ }
+ if (rtransaction_id != refund->rtransaction_id)
+ {
+ GNUNET_break (0);
+ result = 66;
+ }
+ if (0 != TALER_amount_cmp (amount_with_fee,
+ &refund->refund_amount))
+ {
+ GNUNET_break (0);
+ result = 66;
+ }
+ if (0 != TALER_amount_cmp (refund_fee,
+ &refund->refund_fee))
+ {
+ GNUNET_break (0);
+ result = 66;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
* Main function that will be run by the scheduler.
*
* @param cls closure with config
@@ -1890,14 +1967,20 @@ run (void *cls)
refund.merchant_pub = deposit.merchant_pub;
RND_BLK (&refund.merchant_sig);
refund.h_contract_terms = deposit.h_contract_terms;
- refund.rtransaction_id = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX);
+ refund.rtransaction_id = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
+ UINT64_MAX);
refund.refund_amount = deposit.amount_with_fee;
refund.refund_fee = fee_refund;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->insert_refund (plugin->cls,
session,
&refund));
-
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->select_refunds_by_coin (plugin->cls,
+ session,
+ &refund.coin.coin_pub,
+ &check_refund_cb,
+ &refund));
/* test payback / revocation */
RND_BLK (&master_sig);
@@ -2127,9 +2210,11 @@ main (int argc,
NULL);
plugin_name++;
(void) GNUNET_asprintf (&testname,
- "test-exchange-db-%s", plugin_name);
+ "test-exchange-db-%s",
+ plugin_name);
(void) GNUNET_asprintf (&config_filename,
- "%s.conf", testname);
+ "%s.conf",
+ testname);
cfg = GNUNET_CONFIGURATION_create ();
if (GNUNET_OK !=
GNUNET_CONFIGURATION_parse (cfg,
@@ -2140,7 +2225,8 @@ main (int argc,
GNUNET_free (testname);
return 2;
}
- GNUNET_SCHEDULER_run (&run, cfg);
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
GNUNET_CONFIGURATION_destroy (cfg);
GNUNET_free (config_filename);
GNUNET_free (testname);
diff --git a/src/exchangedb/test_exchangedb_fees.c b/src/exchangedb/test_exchangedb_fees.c
index 2bee77452..0c9eceaf1 100644
--- a/src/exchangedb/test_exchangedb_fees.c
+++ b/src/exchangedb/test_exchangedb_fees.c
@@ -86,6 +86,9 @@ main (int argc,
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:1.0",
&af->wire_fee));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("EUR:1.0",
+ &af->closing_fee));
sign_af (af,
priv);
n = GNUNET_new (struct TALER_EXCHANGEDB_AggregateFees);
@@ -94,6 +97,9 @@ main (int argc,
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:0.1",
&n->wire_fee));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("EUR:0.1",
+ &n->closing_fee));
sign_af (n,
priv);
af->next = n;
diff --git a/src/include/taler_error_codes.h b/src/include/taler_error_codes.h
index 2d0c8325a..d90bd4f30 100644
--- a/src/include/taler_error_codes.h
+++ b/src/include/taler_error_codes.h
@@ -1001,6 +1001,7 @@ enum TALER_ErrorCode
TALER_EC_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE = 2114,
/**
+
* The request fails to provide coins for the payment.
* This response is provided with HTTP status code
* MHD_HTTP_BAD_REQUEST.
@@ -1503,67 +1504,84 @@ enum TALER_ErrorCode
/* *************** Taler BANK/FAKEBANK error codes *************** */
/**
- * Authentication failed for an unspecified request.
- * To return when the view name is not available, or
- * no specific error code is defined yet.
+ * The request cannot be served because the client failed to
+ * login. To be returned along HTTP 401 Unauthorized.
+ */
+ TALER_EC_BANK_REJECT_LOGIN_FAILED = 5312,
+
+ /**
+ * The transaction cannot be rejected becasue it does not exist
+ * at the bank. To be returned along HTTP 404 Not Found.
+ */
+ TALER_EC_BANK_REJECT_TRANSACTION_NOT_FOUND = 5301,
+
+ /**
+ * The client does not own the account credited by the transaction
+ * which is to be rejected, so it has no rights do reject it. To be
+ * returned along HTTP 403 Forbidden.
+ */
+ TALER_EC_BANK_REJECT_NO_RIGHTS = 5313,
+
+ /**
+ * The POSTed JSON at /reject was invalid. To be returned along
+ * HTTP 400 Bad Request.
*/
- TALER_EC_BANK_NOT_AUTHORIZED = 5000,
+ TALER_EC_BANK_REJECT_JSON_INVALID = 5306,
/**
- * The bank could not find the bank account specified
- * in the request. Returned with a status code of MHD_HTTP_NOT_FOUND.
+ * A URL parameter for /history was missing. To be returned along
+ * HTTP 400 Bad Request.
*/
- TALER_EC_BANK_UNKNOWN_ACCOUNT = 5001,
+ TALER_EC_BANK_HISTORY_PARAMETER_MISSING = 5208,
/**
- * Authentication failed for the /admin/add/incoming request.
- * Returned with a status code of MHD_HTTP_FORBIDDEN.
+ * A URL parameter for /history was malformed. To be returned along
+ * HTTP 400 Bad Request.
*/
- TALER_EC_BANK_TRANSFER_NOT_AUHTORIZED = 5100,
+ TALER_EC_BANK_HISTORY_PARAMETER_MALFORMED = 5209,
/**
- * The wire transfer cannot be done because the debitor would
- * reach a unallowed debit.
+ * The client failed to login for /history. To be returned along
+ * HTTP 401 Unauthorized.
*/
- TALER_EC_BANK_TRANSFER_DEBIT = 5101,
+ TALER_EC_BANK_HISTORY_LOGIN_FAILED = 5212,
/**
- * The wire transfer cannot be done because the credit and
- * debit account are the same.
+ * The bank had trouble obtaining a valid HTTP response. To be returned
+ * along status code 0.
*/
- TALER_EC_BANK_TRANSFER_SAME_ACCOUNT = 5102,
+ TALER_EC_BANK_HISTORY_HTTP_FAILURE = 5213,
/**
- * Authentication failed for the /history request.
- * Returned with a status code of MHD_HTTP_FORBIDDEN.
+ * The debit account for /admin/add/incoming is not known to the
+ * bank. To be returned along HTTP 404 Not Found.
*/
- TALER_EC_BANK_HISTORY_NOT_AUHTORIZED = 5200,
+ TALER_EC_BANK_ADD_INCOMING_UNKNOWN_ACCOUNT = 5100,
/**
- * The bank library had trouble obtaining a valid
- * HTTP response.
- * Returned with a status code of 0.
+ * The client specified the same bank account for both the credit
+ * and the debit account. The bank will not accomplish this operation.
+ * To be returned along HTTP 403 Forbidden.
*/
- TALER_EC_BANK_HISTORY_HTTP_FAILURE = 5201,
+ TALER_EC_BANK_ADD_INCOMING_SAME_ACCOUNT = 5102,
/**
- * The bank could not find the wire transfer that was supposed to
- * be rejected.
- * Returned with a status code of MHD_HTTP_NOT_FOUND.
+ * The operation would put the client in a debit situation which is
+ * forbidden to them. To return along HTTP 403 Forbidden.
*/
- TALER_EC_BANK_REJECT_NOT_FOUND = 5300,
+ TALER_EC_BANK_ADD_INCOMING_UNALLOWED_DEBIT = 5103,
/**
- * Authentication failed for the /reject request.
- * Returned with a status code of MHD_HTTP_FORBIDDEN.
+ * The client POSTed an invalid JSON. To be returned along HTTP
+ * 400 Bad Request.
*/
- TALER_EC_BANK_REJECT_NOT_AUTHORIZED = 5301,
+ TALER_EC_BANK_ADD_INCOMING_JSON_INVALID = 5106,
/**
- * The client wants to reject a transaction where they are
- * not the _credit_ party, impossible!
+ * The client failed to login for /admin/add/incoming. To be returned
+ * along HTTP 401 Unauthorized.
*/
- TALER_EC_BANK_REJECT_NO_RIGHTS = 5302,
+ TALER_EC_BANK_ADD_INCOMING_LOGIN_FAILED = 5112,
/**
* End of error code range.
diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h
index da39a179f..f1af114cb 100644
--- a/src/include/taler_exchange_service.h
+++ b/src/include/taler_exchange_service.h
@@ -717,6 +717,50 @@ TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange,
/**
+ * 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 refund_fee fee applicable to this coin for the refund
+ * @param h_contract_terms hash of the contact of the merchant with the customer that is being refunded
+ * @param coin_pub coin’s 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_pub public key of the merchant
+ * @param merchant_sig signature affirming the refund from the merchant
+ * @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_refund2 (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *refund_fee,
+ const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t rtransaction_id,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantSignatureP *merchant_sig,
+ 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.
diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h
index e64b0ad4c..ae38856a3 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -733,6 +733,29 @@ typedef int
/**
+ * Callback invoked with information about refunds applicable
+ * to a particular coin.
+ *
+ * @param cls closure
+ * @param merchant_pub public key of merchant who authorized refund
+ * @param merchant_sig signature of merchant authorizing refund
+ * @param h_contract hash of contract being refunded
+ * @param rtransaction_id refund transaction ID
+ * @param amount_with_fee amount being refunded
+ * @param refund_fee fee the exchange keeps for the refund processing
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef int
+(*TALER_EXCHANGEDB_RefundCoinCallback)(void *cls,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantSignatureP *merchant_sig,
+ const struct GNUNET_HashCode *h_contract,
+ uint64_t rtransaction_id,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *refund_fee);
+
+
+/**
* Information about a coin that was revealed to the exchange
* during /refresh/reveal.
*/
@@ -1358,6 +1381,23 @@ struct TALER_EXCHANGEDB_Plugin
struct TALER_EXCHANGEDB_Session *session,
const struct TALER_EXCHANGEDB_Refund *refund);
+ /**
+ * Select refunds by @a coin_pub.
+ *
+ * @param cls closure of plugin
+ * @param session database handle to use
+ * @param coin_pub coin to get refunds for
+ * @param cb function to call for each refund found
+ * @param cb_cls closure for @a cb
+ * @return query result status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_refunds_by_coin)(void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ TALER_EXCHANGEDB_RefundCoinCallback cb,
+ void *cb_cls);
+
/**
* Mark a deposit as tiny, thereby declaring that it cannot be
@@ -1658,6 +1698,7 @@ struct TALER_EXCHANGEDB_Plugin
* @param start_date when does the fee go into effect
* @param end_date when does the fee end being valid
* @param wire_fee how high is the wire transfer fee
+ * @param closing_fee how high is the closing fee
* @param master_sig signature over the above by the exchange master key
* @return transaction status code
*/
@@ -1668,6 +1709,7 @@ struct TALER_EXCHANGEDB_Plugin
struct GNUNET_TIME_Absolute start_date,
struct GNUNET_TIME_Absolute end_date,
const struct TALER_Amount *wire_fee,
+ const struct TALER_Amount *closing_fee,
const struct TALER_MasterSignatureP *master_sig);
@@ -1681,6 +1723,7 @@ struct TALER_EXCHANGEDB_Plugin
* @param[out] start_date when does the fee go into effect
* @param[out] end_date when does the fee end being valid
* @param[out] wire_fee how high is the wire transfer fee
+ * @param[out] closing_fee how high is the closing fee
* @param[out] master_sig signature over the above by the exchange master key
* @return query status of the transaction
*/
@@ -1692,6 +1735,7 @@ struct TALER_EXCHANGEDB_Plugin
struct GNUNET_TIME_Absolute *start_date,
struct GNUNET_TIME_Absolute *end_date,
struct TALER_Amount *wire_fee,
+ struct TALER_Amount *closing_fee,
struct TALER_MasterSignatureP *master_sig);
diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h
index 6355303a0..c281d21fd 100644
--- a/src/include/taler_signatures.h
+++ b/src/include/taler_signatures.h
@@ -178,6 +178,12 @@
*/
#define TALER_SIGNATURE_MERCHANT_REFUND_OK 1105
+/**
+ * Signature where the merchant confirms that the user replayed
+ * a payment for a browser session.
+ */
+#define TALER_SIGNATURE_MERCHANT_PAY_SESSION 1106
+
/*********************/
/* Wallet signatures */
@@ -1291,6 +1297,31 @@ struct TALER_MerchantRefundConfirmationPS
};
+/**
+ * Used by the merchant to confirm to the frontend that
+ * the user did a payment replay with the current browser session.
+ */
+struct TALER_MerchantPaySessionSigPS
+{
+ /**
+ * Set to #TALER_SIGNATURE_MERCHANT_PAY_SESSION.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hashed order id.
+ * Hashed without the 0-termination.
+ */
+ struct GNUNET_HashCode h_order_id GNUNET_PACKED;
+
+ /**
+ * Hashed session id.
+ * Hashed without the 0-termination.
+ */
+ struct GNUNET_HashCode h_session_id GNUNET_PACKED;
+
+};
+
GNUNET_NETWORK_STRUCT_END
#endif
diff --git a/src/util/amount.c b/src/util/amount.c
index 7765c74bb..20d064243 100644
--- a/src/util/amount.c
+++ b/src/util/amount.c
@@ -207,6 +207,8 @@ void
TALER_amount_hton (struct TALER_AmountNBO *res,
const struct TALER_Amount *d)
{
+ GNUNET_assert (GNUNET_YES ==
+ TALER_amount_is_valid (d));
res->value = GNUNET_htonll (d->value);
res->fraction = htonl (d->fraction);
memcpy (res->currency,
@@ -230,6 +232,8 @@ TALER_amount_ntoh (struct TALER_Amount *res,
memcpy (res->currency,
dn->currency,
TALER_CURRENCY_LEN);
+ GNUNET_assert (GNUNET_YES ==
+ TALER_amount_is_valid (res));
}