diff options
-rw-r--r-- | src/backend/taler-merchant-httpd_exchanges.c | 33 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_exchanges.h | 16 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c | 42 | ||||
-rw-r--r-- | src/backenddb/pg_increase_refund.c | 423 | ||||
-rw-r--r-- | src/backenddb/pg_increase_refund.h | 16 | ||||
-rw-r--r-- | src/backenddb/test_merchantdb.c | 22 | ||||
-rw-r--r-- | src/include/taler_merchantdb_plugin.h | 135 |
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, ¤t_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 (¤t_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 ( ¤t_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); /** |