aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/taler-merchant-httpd_exchanges.c33
-rw-r--r--src/backend/taler-merchant-httpd_exchanges.h16
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c42
-rw-r--r--src/backenddb/pg_increase_refund.c423
-rw-r--r--src/backenddb/pg_increase_refund.h16
-rw-r--r--src/backenddb/test_merchantdb.c22
-rw-r--r--src/include/taler_merchantdb_plugin.h135
7 files changed, 509 insertions, 178 deletions
diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c
index 90649a7e..26c2f5cd 100644
--- a/src/backend/taler-merchant-httpd_exchanges.c
+++ b/src/backend/taler-merchant-httpd_exchanges.c
@@ -1100,6 +1100,39 @@ TMH_EXCHANGES_is_below_limit (
}
+void
+TMH_EXCHANGES_get_limit (
+ const char *exchange_url,
+ enum TALER_KYCLOGIC_KycTriggerEvent operation_type,
+ struct TALER_Amount *amount)
+{
+ struct TMH_Exchange *exchange;
+ const struct TALER_EXCHANGE_Keys *keys;
+
+ exchange = lookup_exchange (exchange_url);
+ if ( (NULL == exchange) ||
+ (NULL == (keys = exchange->keys)) )
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (
+ amount->currency,
+ amount));
+ return;
+ }
+ for (unsigned int i = 0; i<keys->hard_limits_length; i++)
+ {
+ const struct TALER_EXCHANGE_AccountLimit *al
+ = &keys->hard_limits[i];
+
+ if (operation_type != al->operation_type)
+ continue;
+ TALER_amount_min (amount,
+ amount,
+ &al->threshold);
+ }
+}
+
+
enum GNUNET_GenericReturnValue
TMH_EXCHANGES_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
diff --git a/src/backend/taler-merchant-httpd_exchanges.h b/src/backend/taler-merchant-httpd_exchanges.h
index 5c70494c..7c11df61 100644
--- a/src/backend/taler-merchant-httpd_exchanges.h
+++ b/src/backend/taler-merchant-httpd_exchanges.h
@@ -220,4 +220,20 @@ TMH_test_exchange_configured_for_currency (
const char *currency);
+/**
+ * Determines the legal limit for a given @a operation_type
+ * at a given @a exchange_url.
+ *
+ * @param exchange_url base URL of the exchange to get
+ * the refund limit for
+ * @param operation_type type of operation
+ * @param[in,out] amount lowered to the maximum
+ * allowed at the exchange
+ */
+void
+TMH_EXCHANGES_get_limit (
+ const char *exchange_url,
+ enum TALER_KYCLOGIC_KycTriggerEvent operation_type,
+ struct TALER_Amount *amount);
+
#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c b/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
index 1a7ffe37..a9f15405 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2023 Taler Systems SA
+ (C) 2014-2024 Taler Systems SA
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
@@ -27,6 +27,7 @@
#include "taler-merchant-httpd_private-post-orders-ID-refund.h"
#include "taler-merchant-httpd_private-get-orders.h"
#include "taler-merchant-httpd_helper.h"
+#include "taler-merchant-httpd_exchanges.h"
/**
@@ -35,9 +36,6 @@
*/
#define MAX_RETRIES 5
-/* FIXME-9061: check exchange refund limits and return 403/
- MERCHANT_POST_ORDERS_ID_REFUND_EXCHANGE_TRANSACTION_LIMIT_VIOLATION
- if they are violated! */
/**
* Use database to notify other clients about the
@@ -109,6 +107,28 @@ make_taler_refund_uri (struct MHD_Connection *connection,
/**
+ * Wrapper around #TMH_EXCHANGES_get_limit() that
+ * determines the refund limit for a given @a exchange_url
+ *
+ * @param cls unused
+ * @param exchange_url base URL of the exchange to get
+ * the refund limit for
+ * @param[in,out] amount lowered to the maximum refund
+ * allowed at the exchange
+ */
+static void
+get_refund_limit (void *cls,
+ const char *exchange_url,
+ struct TALER_Amount *amount)
+{
+ (void) cls;
+ TMH_EXCHANGES_get_limit (exchange_url,
+ TALER_KYCLOGIC_KYC_TRIGGER_REFUND,
+ amount);
+}
+
+
+/**
* Handle request for increasing the refund associated with
* a contract.
*
@@ -252,6 +272,8 @@ TMH_private_post_orders_ID_refund (
hc->instance->settings.id,
hc->infix,
&refund,
+ &get_refund_limit,
+ NULL,
reason);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"increase refund returned %d\n",
@@ -315,6 +337,15 @@ TMH_private_post_orders_ID_refund (
switch (rs)
{
+ case TALER_MERCHANTDB_RS_LEGAL_FAILURE:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Refund amount %s exceeded legal limits of the exchanges involved\n",
+ TALER_amount2s (&refund));
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_REFUND_EXCHANGE_TRANSACTION_LIMIT_VIOLATION,
+ NULL);
case TALER_MERCHANTDB_RS_BAD_CURRENCY:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Refund amount %s is not in the currency of the original payment\n",
@@ -323,8 +354,7 @@ TMH_private_post_orders_ID_refund (
connection,
MHD_HTTP_CONFLICT,
TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
- "Order was paid in a different currency")
- ;
+ "Order was paid in a different currency");
case TALER_MERCHANTDB_RS_TOO_HIGH:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Refusing refund amount %s that is larger than original payment\n",
diff --git a/src/backenddb/pg_increase_refund.c b/src/backenddb/pg_increase_refund.c
index eef7adc6..ed0b752e 100644
--- a/src/backenddb/pg_increase_refund.c
+++ b/src/backenddb/pg_increase_refund.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -27,6 +27,34 @@
/**
+ * Information about refund limits per exchange.
+ */
+struct ExchangeLimit
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct ExchangeLimit *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct ExchangeLimit *prev;
+
+ /**
+ * Exchange the limit is about.
+ */
+ char *exchange_url;
+
+ /**
+ * Refund amount remaining at this exchange.
+ */
+ struct TALER_Amount remaining_refund_limit;
+
+};
+
+
+/**
* Closure for #process_refund_cb().
*/
struct FindRefundContext
@@ -55,6 +83,176 @@ struct FindRefundContext
/**
+ * Closure for #process_deposits_for_refund_cb().
+ */
+struct InsertRefundContext
+{
+ /**
+ * Used to provide a connection to the db
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Head of DLL of per-exchange refund limits.
+ */
+ struct ExchangeLimit *el_head;
+
+ /**
+ * Tail of DLL of per-exchange refund limits.
+ */
+ struct ExchangeLimit *el_tail;
+
+ /**
+ * Amount to which increase the refund for this contract
+ */
+ const struct TALER_Amount *refund;
+
+ /**
+ * Human-readable reason behind this refund
+ */
+ const char *reason;
+
+ /**
+ * Function to call to determine per-exchange limits.
+ * NULL for no limits.
+ */
+ TALER_MERCHANTDB_OperationLimitCallback olc;
+
+ /**
+ * Closure for @e olc.
+ */
+ void *olc_cls;
+
+ /**
+ * Transaction status code.
+ */
+ enum TALER_MERCHANTDB_RefundStatus rs;
+
+ /**
+ * Did we have to cap refunds of any coin
+ * due to legal limits?
+ */
+ bool legal_capped;
+};
+
+
+/**
+ * Data extracted per coin.
+ */
+struct RefundCoinData
+{
+
+ /**
+ * Public key of a coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Amount deposited for this coin.
+ */
+ struct TALER_Amount deposited_with_fee;
+
+ /**
+ * Amount refunded already for this coin.
+ */
+ struct TALER_Amount refund_amount;
+
+ /**
+ * Order serial (actually not really per-coin).
+ */
+ uint64_t order_serial;
+
+ /**
+ * Maximum rtransaction_id for this coin so far.
+ */
+ uint64_t max_rtransaction_id;
+
+ /**
+ * Exchange this coin was issued by.
+ */
+ char *exchange_url;
+
+};
+
+
+/**
+ * Find an exchange record for the refund limit enforcement.
+ *
+ * @param irc refund context
+ * @param exchange_url base URL of the exchange
+ */
+static struct ExchangeLimit *
+find_exchange (struct InsertRefundContext *irc,
+ const char *exchange_url)
+{
+ if (NULL == irc->olc)
+ return NULL; /* no limits */
+ /* Check if entry exists, if so, do nothing */
+ for (struct ExchangeLimit *el = irc->el_head;
+ NULL != el;
+ el = el->next)
+ if (0 == strcmp (exchange_url,
+ el->exchange_url))
+ return el;
+ return NULL;
+}
+
+
+/**
+ * Setup an exchange for the refund limit enforcement and initialize the
+ * original refund limit for the exchange.
+ *
+ * @param irc refund context
+ * @param exchange_url base URL of the exchange
+ * @return limiting data structure
+ */
+static struct ExchangeLimit *
+setup_exchange (struct InsertRefundContext *irc,
+ const char *exchange_url)
+{
+ struct ExchangeLimit *el;
+
+ if (NULL == irc->olc)
+ return NULL; /* no limits */
+ /* Check if entry exists, if so, do nothing */
+ if (NULL !=
+ (el = find_exchange (irc,
+ exchange_url)))
+ return el;
+ el = GNUNET_new (struct ExchangeLimit);
+ el->exchange_url = GNUNET_strdup (exchange_url);
+ /* olc only lowers, so set to the maximum amount we care about */
+ el->remaining_refund_limit = *irc->refund;
+ irc->olc (irc->olc_cls,
+ exchange_url,
+ &el->remaining_refund_limit);
+ GNUNET_CONTAINER_DLL_insert (irc->el_head,
+ irc->el_tail,
+ el);
+ return el;
+}
+
+
+/**
+ * Lower the remaining refund limit in @a el by @a val.
+ *
+ * @param[in,out] el exchange limit to lower
+ * @param val amount to lower limit by
+ * @return true on success, false on failure
+ */
+static bool
+lower_balance (struct ExchangeLimit *el,
+ const struct TALER_Amount *val)
+{
+ if (NULL == el)
+ return true;
+ return 0 <= TALER_amount_subtract (&el->remaining_refund_limit,
+ &el->remaining_refund_limit,
+ val);
+}
+
+
+/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
@@ -118,67 +316,6 @@ process_refund_cb (void *cls,
/**
- * Closure for #process_deposits_for_refund_cb().
- */
-struct InsertRefundContext
-{
- /**
- * Used to provide a connection to the db
- */
- struct PostgresClosure *pg;
-
- /**
- * Amount to which increase the refund for this contract
- */
- const struct TALER_Amount *refund;
-
- /**
- * Human-readable reason behind this refund
- */
- const char *reason;
-
- /**
- * Transaction status code.
- */
- enum TALER_MERCHANTDB_RefundStatus rs;
-};
-
-
-/**
- * Data extracted per coin.
- */
-struct RefundCoinData
-{
-
- /**
- * Public key of a coin.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Amount deposited for this coin.
- */
- struct TALER_Amount deposited_with_fee;
-
- /**
- * Amount refunded already for this coin.
- */
- struct TALER_Amount refund_amount;
-
- /**
- * Order serial (actually not really per-coin).
- */
- uint64_t order_serial;
-
- /**
- * Maximum rtransaction_id for this coin so far.
- */
- uint64_t max_rtransaction_id;
-
-};
-
-
-/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
@@ -202,23 +339,29 @@ process_deposits_for_refund_cb (
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (ctx->refund->currency,
&current_refund));
- memset (rcd, 0, sizeof (rcd));
+ memset (rcd,
+ 0,
+ sizeof (rcd));
/* Pass 1: Collect amount of existing refunds into current_refund.
* Also store existing refunded amount for each deposit in deposit_refund. */
for (unsigned int i = 0; i<num_results; i++)
{
+ struct RefundCoinData *rcdi = &rcd[i];
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &rcd[i].coin_pub),
+ &rcdi->coin_pub),
GNUNET_PQ_result_spec_uint64 ("order_serial",
- &rcd[i].order_serial),
+ &rcdi->order_serial),
+ GNUNET_PQ_result_spec_string ("exchange_url",
+ &rcdi->exchange_url),
TALER_PQ_result_spec_amount_with_currency ("amount_with_fee",
- &rcd[i].deposited_with_fee),
+ &rcdi->deposited_with_fee),
GNUNET_PQ_result_spec_end
};
struct FindRefundContext ictx = {
.pg = pg
};
+ struct ExchangeLimit *el;
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
@@ -227,44 +370,47 @@ process_deposits_for_refund_cb (
{
GNUNET_break (0);
ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
- return;
+ goto cleanup;
}
-
- if (0 != strcmp (rcd[i].deposited_with_fee.currency,
+ el = setup_exchange (ctx,
+ rcdi->exchange_url);
+ if (0 != strcmp (rcdi->deposited_with_fee.currency,
ctx->refund->currency))
{
GNUNET_break_op (0);
ctx->rs = TALER_MERCHANTDB_RS_BAD_CURRENCY;
- return;
+ goto cleanup;
}
{
enum GNUNET_DB_QueryStatus ires;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub),
- GNUNET_PQ_query_param_uint64 (&rcd[i].order_serial),
+ GNUNET_PQ_query_param_auto_from_type (&rcdi->coin_pub),
+ GNUNET_PQ_query_param_uint64 (&rcdi->order_serial),
GNUNET_PQ_query_param_end
};
GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (ctx->refund->currency,
- &ictx.refunded_amount));
- ires = GNUNET_PQ_eval_prepared_multi_select (ctx->pg->conn,
- "find_refunds_by_coin",
- params,
- &process_refund_cb,
- &ictx);
+ TALER_amount_set_zero (
+ ctx->refund->currency,
+ &ictx.refunded_amount));
+ ires = GNUNET_PQ_eval_prepared_multi_select (
+ ctx->pg->conn,
+ "find_refunds_by_coin",
+ params,
+ &process_refund_cb,
+ &ictx);
if ( (ictx.err) ||
(GNUNET_DB_STATUS_HARD_ERROR == ires) )
{
GNUNET_break (0);
ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
- return;
+ goto cleanup;
}
if (GNUNET_DB_STATUS_SOFT_ERROR == ires)
{
ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
- return;
+ goto cleanup;
}
}
if (0 >
@@ -274,15 +420,17 @@ process_deposits_for_refund_cb (
{
GNUNET_break (0);
ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
- return;
+ goto cleanup;
}
- rcd[i].refund_amount = ictx.refunded_amount;
- rcd[i].max_rtransaction_id = ictx.max_rtransaction_id;
+ rcdi->refund_amount = ictx.refunded_amount;
+ rcdi->max_rtransaction_id = ictx.max_rtransaction_id;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Existing refund for coin %s is %s\n",
- TALER_B2S (&rcd[i].coin_pub),
+ TALER_B2S (&rcdi->coin_pub),
TALER_amount2s (&ictx.refunded_amount));
- }
+ GNUNET_break (lower_balance (el,
+ &ictx.refunded_amount));
+ } /* end for all deposited coins */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Total existing refund is %s\n",
@@ -297,38 +445,50 @@ process_deposits_for_refund_cb (
"Existing refund of %s at or above requested refund. Finished early.\n",
TALER_amount2s (&current_refund));
ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
- return;
+ goto cleanup;
}
/* Phase 2: Try to increase current refund until it matches desired refund */
for (unsigned int i = 0; i<num_results; i++)
{
+ struct RefundCoinData *rcdi = &rcd[i];
const struct TALER_Amount *increment;
struct TALER_Amount left;
struct TALER_Amount remaining_refund;
+ struct ExchangeLimit *el;
/* How much of the coin is left after the existing refunds? */
if (0 >
TALER_amount_subtract (&left,
- &rcd[i].deposited_with_fee,
- &rcd[i].refund_amount))
+ &rcdi->deposited_with_fee,
+ &rcdi->refund_amount))
{
GNUNET_break (0);
ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
- return;
+ goto cleanup;
}
- if ( (0 == left.value) &&
- (0 == left.fraction) )
+ if (TALER_amount_is_zero (&left))
{
/* coin was fully refunded, move to next coin */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Coin %s fully refunded, moving to next coin\n",
- TALER_B2S (&rcd[i].coin_pub));
+ TALER_B2S (&rcdi->coin_pub));
+ continue;
+ }
+ el = find_exchange (ctx,
+ rcdi->exchange_url);
+ if ( (NULL != el) &&
+ (TALER_amount_is_zero (&el->remaining_refund_limit)) )
+ {
+ /* legal limit reached, move to next coin */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Exchange %s legal limit reached, moving to next coin\n",
+ rcdi->exchange_url);
continue;
}
- rcd[i].max_rtransaction_id++;
+ rcdi->max_rtransaction_id++;
/* How much of the refund is still to be paid back? */
if (0 >
TALER_amount_subtract (&remaining_refund,
@@ -337,9 +497,23 @@ process_deposits_for_refund_cb (
{
GNUNET_break (0);
ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
- return;
+ goto cleanup;
}
+ /* cap by legal limit */
+ if (NULL != el)
+ {
+ struct TALER_Amount new_limit;
+ TALER_amount_min (&new_limit,
+ &remaining_refund,
+ &el->remaining_refund_limit);
+ if (0 != TALER_amount_cmp (&new_limit,
+ &remaining_refund))
+ {
+ remaining_refund = new_limit;
+ ctx->legal_capped = true;
+ }
+ }
/* By how much will we increase the refund for this coin? */
if (0 >= TALER_amount_cmp (&remaining_refund,
&left))
@@ -359,25 +533,26 @@ process_deposits_for_refund_cb (
{
GNUNET_break (0);
ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
- return;
+ goto cleanup;
}
-
+ GNUNET_break (lower_balance (el,
+ increment));
/* actually run the refund */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Coin %s deposit amount is %s\n",
- TALER_B2S (&rcd[i].coin_pub),
- TALER_amount2s (&rcd[i].deposited_with_fee));
+ TALER_B2S (&rcdi->coin_pub),
+ TALER_amount2s (&rcdi->deposited_with_fee));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Coin %s refund will be incremented by %s\n",
- TALER_B2S (&rcd[i].coin_pub),
+ TALER_B2S (&rcdi->coin_pub),
TALER_amount2s (increment));
{
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&rcd[i].order_serial),
- GNUNET_PQ_query_param_uint64 (&rcd[i].max_rtransaction_id), /* already inc'ed */
+ GNUNET_PQ_query_param_uint64 (&rcdi->order_serial),
+ GNUNET_PQ_query_param_uint64 (&rcdi->max_rtransaction_id), /* already inc'ed */
GNUNET_PQ_query_param_timestamp (&now),
- GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&rcdi->coin_pub),
GNUNET_PQ_query_param_string (ctx->reason),
TALER_PQ_query_param_amount_with_currency (pg->conn,
increment),
@@ -393,10 +568,10 @@ process_deposits_for_refund_cb (
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
- return;
+ goto cleanup;
case GNUNET_DB_STATUS_SOFT_ERROR:
ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
- return;
+ goto cleanup;
default:
ctx->rs = (enum TALER_MERCHANTDB_RefundStatus) qs;
break;
@@ -408,10 +583,15 @@ process_deposits_for_refund_cb (
&current_refund))
{
ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
- return;
+ goto cleanup;
}
}
+ if (ctx->legal_capped)
+ {
+ ctx->rs = TALER_MERCHANTDB_RS_LEGAL_FAILURE;
+ goto cleanup;
+ }
/**
* We end up here if not all of the refund has been covered.
* Although this should be checked as the business should never
@@ -422,15 +602,21 @@ process_deposits_for_refund_cb (
"The refund of %s is bigger than the order's value\n",
TALER_amount2s (ctx->refund));
ctx->rs = TALER_MERCHANTDB_RS_TOO_HIGH;
+cleanup:
+ for (unsigned int i = 0; i<num_results; i++)
+ GNUNET_free (rcd[i].exchange_url);
}
enum TALER_MERCHANTDB_RefundStatus
-TMH_PG_increase_refund (void *cls,
- const char *instance_id,
- const char *order_id,
- const struct TALER_Amount *refund,
- const char *reason)
+TMH_PG_increase_refund (
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const struct TALER_Amount *refund,
+ TALER_MERCHANTDB_OperationLimitCallback olc,
+ void *olc_cls,
+ const char *reason)
{
struct PostgresClosure *pg = cls;
enum GNUNET_DB_QueryStatus qs;
@@ -442,6 +628,8 @@ TMH_PG_increase_refund (void *cls,
struct InsertRefundContext ctx = {
.pg = pg,
.refund = refund,
+ .olc = olc,
+ .olc_cls = olc_cls,
.reason = reason
};
@@ -470,6 +658,7 @@ TMH_PG_increase_refund (void *cls,
" dep.coin_pub"
",dco.order_serial"
",dep.amount_with_fee"
+ ",dco.exchange_url"
" FROM merchant_deposits dep"
" JOIN merchant_deposit_confirmations dco"
" USING (deposit_confirmation_serial)"
@@ -491,6 +680,18 @@ TMH_PG_increase_refund (void *cls,
params,
&process_deposits_for_refund_cb,
&ctx);
+ {
+ struct ExchangeLimit *el;
+
+ while (NULL != (el = ctx.el_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (ctx.el_head,
+ ctx.el_tail,
+ el);
+ GNUNET_free (el->exchange_url);
+ GNUNET_free (el);
+ }
+ }
switch (qs)
{
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
diff --git a/src/backenddb/pg_increase_refund.h b/src/backenddb/pg_increase_refund.h
index 0fe8a470..549d79be 100644
--- a/src/backenddb/pg_increase_refund.h
+++ b/src/backenddb/pg_increase_refund.h
@@ -36,6 +36,9 @@
* @param instance_id instance identifier
* @param order_id the order to increase the refund for
* @param refund maximum refund to return to the customer for this contract
+ * @param olc function to call to obtain legal refund
+ * limits per exchange, NULL for no limits
+ * @param olc_cls closure for @a olc
* @param reason 0-terminated UTF-8 string giving the reason why the customer
* got a refund (free form, business-specific)
* @return transaction status
@@ -46,11 +49,14 @@
* what was already refunded (idempotency!)
*/
enum TALER_MERCHANTDB_RefundStatus
-TMH_PG_increase_refund (void *cls,
- const char *instance_id,
- const char *order_id,
- const struct TALER_Amount *refund,
- const char *reason);
+TMH_PG_increase_refund (
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const struct TALER_Amount *refund,
+ TALER_MERCHANTDB_OperationLimitCallback olc,
+ void *olc_cls,
+ const char *reason);
#endif
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
index f1d6e392..104a1d5d 100644
--- a/src/backenddb/test_merchantdb.c
+++ b/src/backenddb/test_merchantdb.c
@@ -5119,6 +5119,7 @@ run_test_refunds (struct TestRefunds_Closure *cls)
{
struct TALER_Amount inc;
uint64_t refund_serial;
+ bool legal_failure = false;
/* Insert an instance */
TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
@@ -5213,7 +5214,10 @@ run_test_refunds (struct TestRefunds_Closure *cls)
cls->instance.instance.id,
cls->orders[0].id,
&inc,
- "more"),
+ NULL,
+ NULL,
+ "more",
+ &legal_failure),
"Increase refund failed\n");
/* Test increase refund */
GNUNET_assert (GNUNET_OK ==
@@ -5224,7 +5228,10 @@ run_test_refunds (struct TestRefunds_Closure *cls)
cls->instance.instance.id,
cls->orders[0].id,
&inc,
- "more"),
+ NULL,
+ NULL,
+ "more",
+ &legal_failure),
"Increase refund failed\n");
/* Test lookup refund proof */
TEST_RET_ON_FAIL (test_lookup_refund_proof (1,
@@ -5252,7 +5259,10 @@ run_test_refunds (struct TestRefunds_Closure *cls)
cls->instance.instance.id,
cls->orders[1].id,
&inc,
- cls->refunds[1].reason),
+ NULL,
+ NULL,
+ cls->refunds[1].reason,
+ &legal_failure),
"Increase refund failed\n");
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:20.00",
@@ -5262,8 +5272,12 @@ run_test_refunds (struct TestRefunds_Closure *cls)
cls->instance.instance.id,
cls->orders[1].id,
&inc,
- cls->refunds[2].reason),
+ NULL,
+ NULL,
+ cls->refunds[2].reason,
+ &legal_failure),
"Increase refund failed\n");
+ GNUNET_assert (! legal_failure);
TEST_RET_ON_FAIL (test_lookup_refunds_detailed (&cls->instance,
&cls->deposits[2].
h_contract_terms,
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
index 0c301a0a..ff8793ce 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -293,6 +293,23 @@ typedef void
/**
+ * Determines the maximum @a amount for a particular
+ * type of operation for a given @a exchange_url.
+ *
+ * @param cls closure
+ * @param exchange_url base URL of the exchange to get
+ * the limit for
+ * @param[in,out] amount lowered to the maximum amount
+ * allowed at the exchange
+ */
+typedef void
+(*TALER_MERCHANTDB_OperationLimitCallback)(
+ void *cls,
+ const char *exchange_url,
+ struct TALER_Amount *amount);
+
+
+/**
* Typically called by `lookup_products`.
*
* @param cls a `json_t *` JSON array to build
@@ -882,6 +899,11 @@ enum TALER_MERCHANTDB_RefundStatus
{
/**
+ * Refund amount exceeds legal exchange limits.
+ */
+ TALER_MERCHANTDB_RS_LEGAL_FAILURE = -5,
+
+ /**
* Refund amount currency does not match original payment.
*/
TALER_MERCHANTDB_RS_BAD_CURRENCY = -4,
@@ -2601,6 +2623,9 @@ struct TALER_MERCHANTDB_Plugin
* @param instance_id instance identifier
* @param order_id the order to increase the refund for
* @param refund maximum refund to return to the customer for this contract
+ * @param olc function to call to obtain legal refund
+ * limits per exchange, NULL for no limits
+ * @param olc_cls closure for @a olc
* @param reason 0-terminated UTF-8 string giving the reason why the customer
* got a refund (free form, business-specific)
* @return transaction status
@@ -2611,11 +2636,14 @@ struct TALER_MERCHANTDB_Plugin
* what was already refunded (idempotency!)
*/
enum TALER_MERCHANTDB_RefundStatus
- (*increase_refund)(void *cls,
- const char *instance_id,
- const char *order_id,
- const struct TALER_Amount *refund,
- const char *reason);
+ (*increase_refund)(
+ void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const struct TALER_Amount *refund,
+ TALER_MERCHANTDB_OperationLimitCallback olc,
+ void *olc_cls,
+ const char *reason);
/**
@@ -2646,10 +2674,11 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*insert_refund_proof)(void *cls,
- uint64_t refund_serial,
- const struct TALER_ExchangeSignatureP *exchange_sig,
- const struct TALER_ExchangePublicKeyP *exchange_pub);
+ (*insert_refund_proof)(
+ void *cls,
+ uint64_t refund_serial,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_ExchangePublicKeyP *exchange_pub);
/**
@@ -2664,14 +2693,14 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_spent_token)(void *cls,
- const struct TALER_PrivateContractHashP *
- h_contract_terms,
- const struct TALER_TokenIssuePublicKeyHashP *h_issue_pub
- ,
- const struct TALER_TokenUsePublicKeyP *use_pub,
- const struct TALER_TokenUseSignatureP *use_sig,
- const struct TALER_TokenIssueSignatureP *issue_sig);
+ (*insert_spent_token)(
+ void *cls,
+ const struct TALER_PrivateContractHashP *
+ h_contract_terms,
+ const struct TALER_TokenIssuePublicKeyHashP *h_issue_pub,
+ const struct TALER_TokenUsePublicKeyP *use_pub,
+ const struct TALER_TokenUseSignatureP *use_sig,
+ const struct TALER_TokenIssueSignatureP *issue_sig);
/**
@@ -2684,13 +2713,11 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_issued_token) (void *cls,
- const struct TALER_PrivateContractHashP *
- h_contract_terms,
- const struct TALER_TokenIssuePublicKeyHashP *
- h_issue_pub,
- const struct TALER_TokenIssueBlindSignatureP *
- blind_sig);
+ (*insert_issued_token) (
+ void *cls,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_TokenIssuePublicKeyHashP *h_issue_pub,
+ const struct TALER_TokenIssueBlindSignatureP *blind_sig);
/**
@@ -2703,10 +2730,11 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_refund_proof)(void *cls,
- uint64_t refund_serial,
- struct TALER_ExchangeSignatureP *exchange_sig,
- struct TALER_ExchangePublicKeyP *exchange_pub);
+ (*lookup_refund_proof)(
+ void *cls,
+ uint64_t refund_serial,
+ struct TALER_ExchangeSignatureP *exchange_sig,
+ struct TALER_ExchangePublicKeyP *exchange_pub);
/**
@@ -2723,12 +2751,13 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_order_by_fulfillment)(void *cls,
- const char *instance_id,
- const char *fulfillment_url,
- const char *session_id,
- bool allow_refunded_for_repurchase,
- char **order_id);
+ (*lookup_order_by_fulfillment)(
+ void *cls,
+ const char *instance_id,
+ const char *fulfillment_url,
+ const char *session_id,
+ bool allow_refunded_for_repurchase,
+ char **order_id);
/**
* Update information about progress made by taler-merchant-wirewatch.
@@ -2872,14 +2901,15 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_wire_fee)(void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *wire_method,
- struct GNUNET_TIME_Timestamp contract_date,
- struct TALER_WireFeeSet *fees,
- struct GNUNET_TIME_Timestamp *start_date,
- struct GNUNET_TIME_Timestamp *end_date,
- struct TALER_MasterSignatureP *master_sig);
+ (*lookup_wire_fee)(
+ void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp contract_date,
+ struct TALER_WireFeeSet *fees,
+ struct GNUNET_TIME_Timestamp *start_date,
+ struct GNUNET_TIME_Timestamp *end_date,
+ struct TALER_MasterSignatureP *master_sig);
/**
@@ -3014,16 +3044,17 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_transfers)(void *cls,
- const char *instance_id,
- const char *payto_uri,
- struct GNUNET_TIME_Timestamp before,
- struct GNUNET_TIME_Timestamp after,
- int64_t limit,
- uint64_t offset,
- enum TALER_EXCHANGE_YesNoAll yna,
- TALER_MERCHANTDB_TransferCallback cb,
- void *cb_cls);
+ (*lookup_transfers)(
+ void *cls,
+ const char *instance_id,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp before,
+ struct GNUNET_TIME_Timestamp after,
+ int64_t limit,
+ uint64_t offset,
+ enum TALER_EXCHANGE_YesNoAll yna,
+ TALER_MERCHANTDB_TransferCallback cb,
+ void *cb_cls);
/**