diff options
author | Christian Grothoff <christian@grothoff.org> | 2020-05-02 17:52:13 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2020-05-02 17:52:13 +0200 |
commit | 74c5fa81d74ff5d239f49f74b2bb937c03b83bcb (patch) | |
tree | 18f69732b52bc6df4fac0a740f561f8bcfcfe5fd /src/backenddb | |
parent | dcc083c0959d395bcd4bcf9aee276eb96ea76dee (diff) | |
download | merchant-74c5fa81d74ff5d239f49f74b2bb937c03b83bcb.tar.gz merchant-74c5fa81d74ff5d239f49f74b2bb937c03b83bcb.tar.bz2 merchant-74c5fa81d74ff5d239f49f74b2bb937c03b83bcb.zip |
work on refund
Diffstat (limited to 'src/backenddb')
-rw-r--r-- | src/backenddb/merchant-0001.sql | 3 | ||||
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 891 |
2 files changed, 458 insertions, 436 deletions
diff --git a/src/backenddb/merchant-0001.sql b/src/backenddb/merchant-0001.sql index 77c79052..e54e4781 100644 --- a/src/backenddb/merchant-0001.sql +++ b/src/backenddb/merchant-0001.sql @@ -323,6 +323,9 @@ COMMENT ON TABLE merchant_deposits IS 'Refunds approved by the merchant (backoffice) logic, excludes abort refunds'; COMMENT ON COLUMN merchant_refunds.rtransaction_id IS 'Needed for uniqueness in case a refund is increased for the same order'; +CREATE INDEX IF NOT EXISTS merchant_refunds_by_coin_and_order + ON merchant_refunds + (coin_pub,order_serial); CREATE TABLE IF NOT EXISTS merchant_refund_proofs (refund_serial BIGINT PRIMARY KEY diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index 14fb9b28..00df78a4 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -1979,6 +1979,450 @@ postgres_lookup_order_status (void *cls, } +/** + * Closure for #process_refund_cb(). + */ +struct FindRefundContext +{ + + /** + * Plugin context. + */ + struct PostgresClosure *pg; + + /** + * Updated to reflect total amount refunded so far. + */ + struct TALER_Amount refunded_amount; + + /** + * Set to the largest refund transaction ID encountered. + */ + uint64_t max_rtransaction_id; + + /** + * Set to true on hard errors. + */ + bool err; +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure, our `struct FindRefundContext` + * @param result the postgres result + * @param num_result the number of results in @a result + */ +static void +process_refund_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct FindRefundContext *ictx = cls; + struct PostgresClosure *pg = ictx->pg; + + for (unsigned int i = 0; i<num_results; i++) + { + /* Sum up existing refunds */ + struct TALER_Amount acc; + uint64_t rtransaction_id; + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", + &acc), + GNUNET_PQ_result_spec_uint64 ("rtransaction_id", + &rtransaction_id), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ictx->err = true; + return; + } + if (0 > + TALER_amount_add (&ictx->refunded_amount, + &ictx->refunded_amount, + &acc)) + { + GNUNET_break (0); + ictx->err = true; + return; + } + ictx->max_rtransaction_id = GNUNET_MAX (ictx->max_rtransaction_id, + rtransaction_id); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Found refund of %s\n", + TALER_amount2s (&acc)); + } +} + + +/** + * 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. + * + * @param cls closure, our `struct InsertRefundContext` + * @param result the postgres result + * @param num_result the number of results in @a result + */ +static void +process_deposits_for_refund_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct InsertRefundContext *ctx = cls; + struct PostgresClosure *pg = ctx->pg; + struct TALER_Amount current_refund; + struct RefundCoinData rcd[GNUNET_NZL (num_results)]; + + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (ctx->refund->currency, + ¤t_refund)); + 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 GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("coin_pub", + &rcd[i].coin_pub), + GNUNET_PQ_result_spec_uint64 ("order_serial", + &rcd[i].order_serial), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", + &rcd[i].deposited_with_fee), + GNUNET_PQ_result_spec_end + }; + struct FindRefundContext ictx = { + .pg = pg + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; + return; + } + + { + 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_end + }; + + GNUNET_assert (GNUNET_OK == + TALER_amount_get_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; + } + if (GNUNET_DB_STATUS_SOFT_ERROR == ires) + { + ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR; + return; + } + } + if (0 > + TALER_amount_add (¤t_refund, + ¤t_refund, + &ictx.refunded_amount)) + { + GNUNET_break (0); + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; + return; + } + rcd[i].refund_amount = ictx.refunded_amount; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Existing refund for coin %s is %s\n", + TALER_B2S (&rcd[i].coin_pub), + TALER_amount2s (&ictx.refunded_amount)); + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Total existing refund is %s\n", + TALER_amount2s (¤t_refund)); + + /* stop immediately if we are 'done' === amount already + * refunded. */ + if (0 >= TALER_amount_cmp (ctx->refund, + ¤t_refund)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Existing refund of %s at or above requested refund. Finished early.\n", + TALER_amount2s (¤t_refund)); + ctx->rs = TALER_MERCHANTDB_RS_SUCCESS; + return; + } + + /* Phase 2: Try to increase current refund until it matches desired refund */ + for (unsigned int i = 0; i<num_results; i++) + { + const struct TALER_Amount *increment; + struct TALER_Amount left; + struct TALER_Amount remaining_refund; + + /* 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)) + { + GNUNET_break (0); + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; + return; + } + + if ( (0 == left.value) && + (0 == left.fraction) ) + { + /* 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)); + continue; + } + + rcd[i].max_rtransaction_id++; + /* How much of the refund is still to be paid back? */ + if (0 > + TALER_amount_subtract (&remaining_refund, + ctx->refund, + ¤t_refund)) + { + GNUNET_break (0); + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; + return; + } + + /* By how much will we increase the refund for this coin? */ + if (0 >= TALER_amount_cmp (&remaining_refund, + &left)) + { + /* remaining_refund <= left */ + increment = &remaining_refund; + } + else + { + increment = &left; + } + + if (0 > + TALER_amount_add (¤t_refund, + ¤t_refund, + increment)) + { + GNUNET_break (0); + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; + return; + } + + /* 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)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Coin %s refund will be incremented by %s\n", + TALER_B2S (&rcd[i].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), + GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub), + GNUNET_PQ_query_param_string (ctx->reason), + TALER_PQ_query_param_amount (increment), + GNUNET_PQ_query_param_end + }; + + check_connection (pg); + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_refund", + params); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; + break; + case GNUNET_DB_STATUS_SOFT_ERROR: + ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR; + break; + default: + GNUNET_break (0); + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; + break; + } + return; + } + + /* stop immediately if we are done */ + if (0 == TALER_amount_cmp (ctx->refund, + ¤t_refund)) + { + ctx->rs = TALER_MERCHANTDB_RS_SUCCESS; + return; + } + } + + /** + * We end up here if not all of the refund has been covered. + * Although this should be checked as the business should never + * issue a refund bigger than the contract's actual price, we cannot + * rely upon the frontend being correct. + */// + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "The refund of %s is bigger than the order's value\n", + TALER_amount2s (ctx->refund)); + ctx->rs = TALER_MERCHANTDB_RS_TOO_HIGH; +} + + +/** + * Function called when some backoffice staff decides to award or + * increase the refund on an existing contract. This function + * MUST be called from within a transaction scope setup by the + * caller as it executes multiple SQL statements. + * + * @param cls closure + * @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 reason 0-terminated UTF-8 string giving the reason why the customer + * got a refund (free form, business-specific) + * @return transaction status + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a refund is ABOVE the amount we + * were originally paid and thus the transaction failed; + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid, + * regardless of whether it actually increased the refund beyond + * what was already refunded (idempotency!) + */ +static enum TALER_MERCHANTDB_RefundStatus +postgres_increase_refund (void *cls, + const char *instance_id, + const char *order_id, + const struct TALER_Amount *refund, + const char *reason) +{ + struct PostgresClosure *pg = cls; + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_end + }; + struct InsertRefundContext ctx = { + .pg = pg, + .refund = refund, + .reason = reason + }; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Asked to refund %s on order %s\n", + TALER_amount2s (refund), + order_id); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "find_deposits_for_refund", + params, + &process_deposits_for_refund_cb, + &ctx); + switch (qs) + { + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* never paid, means we clearly cannot refund anything */ + return TALER_MERCHANTDB_RS_NO_SUCH_ORDER; + case GNUNET_DB_STATUS_SOFT_ERROR: + return TALER_MERCHANTDB_RS_SOFT_ERROR; + case GNUNET_DB_STATUS_HARD_ERROR: + return TALER_MERCHANTDB_RS_HARD_ERROR; + default: + /* Got one or more deposits */ + return ctx.rs; + } +} + + /* ********************* OLD API ************************** */ /** @@ -3137,46 +3581,6 @@ postgres_put_refund_proof ( /** - * Insert a refund row into merchant_refunds. Not meant to be exported - * in the db API. - * - * @param cls closure, typically a connection to the db - * @param merchant_pub merchant instance public key - * @param h_contract_terms hashcode of the contract related to this refund - * @param coin_pub public key of the coin giving the (part of) refund - * @param reason human readable explanation behind the refund - * @param refund how much this coin is refunding - */ -static enum GNUNET_DB_QueryStatus -insert_refund (void *cls, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const char *reason, - const struct TALER_Amount *refund) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (merchant_pub), - GNUNET_PQ_query_param_auto_from_type (h_contract_terms), - GNUNET_PQ_query_param_auto_from_type (coin_pub), - GNUNET_PQ_query_param_string (reason), - TALER_PQ_query_param_amount (refund), - GNUNET_PQ_query_param_end - }; - - TALER_LOG_DEBUG ("Inserting refund %s + %s\n", - GNUNET_h2s (h_contract_terms), - TALER_B2S (merchant_pub)); - - check_connection (pg); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_refund", - params); -} - - -/** * Store information about wire fees charged by an exchange, * including signature (so we have proof). * @@ -3287,399 +3691,6 @@ postgres_lookup_wire_fee (void *cls, /** - * Closure for #process_refund_cb. - */ -struct FindRefundContext -{ - - /** - * Plugin context. - */ - struct PostgresClosure *pg; - - /** - * Updated to reflect total amount refunded so far. - */ - struct TALER_Amount refunded_amount; - - /** - * Set to #GNUNET_SYSERR on hard errors. - */ - int err; -}; - - -/** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. - * - * @param cls closure, our `struct FindRefundContext` - * @param result the postgres result - * @param num_result the number of results in @a result - */ -static void -process_refund_cb (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct FindRefundContext *ictx = cls; - struct PostgresClosure *pg = ictx->pg; - - for (unsigned int i = 0; i<num_results; i++) - { - /* Sum up existing refunds */ - struct TALER_Amount acc; - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", - &acc), - GNUNET_PQ_result_spec_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - ictx->err = GNUNET_SYSERR; - return; - } - if (0 > - TALER_amount_add (&ictx->refunded_amount, - &ictx->refunded_amount, - &acc)) - { - GNUNET_break (0); - ictx->err = GNUNET_SYSERR; - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Found refund of %s\n", - TALER_amount2s (&acc)); - } -} - - -/** - * 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; - - /** - * Merchant instance public key - */ - const struct TALER_MerchantPublicKeyP *merchant_pub; - - /** - * Hash code representing the contract - */ - const struct GNUNET_HashCode *h_contract_terms; - - /** - * Human-readable reason behind this refund - */ - const char *reason; - - /** - * Transaction status code. - */ - enum GNUNET_DB_QueryStatus qs; -}; - - -/** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. - * - * @param cls closure, our `struct InsertRefundContext` - * @param result the postgres result - * @param num_result the number of results in @a result - */ -static void -process_deposits_for_refund_cb (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct InsertRefundContext *ctx = cls; - struct PostgresClosure *pg = ctx->pg; - struct TALER_Amount current_refund; - struct TALER_Amount deposit_refund[GNUNET_NZL (num_results)]; - struct TALER_CoinSpendPublicKeyP deposit_coin_pubs[GNUNET_NZL (num_results)]; - struct TALER_Amount deposit_amount_with_fee[GNUNET_NZL (num_results)]; - - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (ctx->refund->currency, - ¤t_refund)); - - /* 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 TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_Amount amount_with_fee; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("coin_pub", - &coin_pub), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", - &amount_with_fee), - GNUNET_PQ_result_spec_end - }; - struct FindRefundContext ictx = { - .err = GNUNET_OK, - .pg = pg - }; - enum GNUNET_DB_QueryStatus ires; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (&coin_pub), - GNUNET_PQ_query_param_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (ctx->refund->currency, - &ictx.refunded_amount)); - ires = GNUNET_PQ_eval_prepared_multi_select (ctx->pg->conn, - "find_refunds", - params, - &process_refund_cb, - &ictx); - if ( (GNUNET_OK != ictx.err) || - (GNUNET_DB_STATUS_HARD_ERROR == ires) ) - { - GNUNET_break (0); - ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } - if (GNUNET_DB_STATUS_SOFT_ERROR == ires) - { - ctx->qs = GNUNET_DB_STATUS_SOFT_ERROR; - return; - } - deposit_refund[i] = ictx.refunded_amount; - deposit_amount_with_fee[i] = amount_with_fee; - deposit_coin_pubs[i] = coin_pub; - if (0 > - TALER_amount_add (¤t_refund, - ¤t_refund, - &ictx.refunded_amount)) - { - GNUNET_break (0); - ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Existing refund for coin %s is %s\n", - TALER_B2S (&coin_pub), - TALER_amount2s (&ictx.refunded_amount)); - } - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Total existing refund is %s\n", - TALER_amount2s (¤t_refund)); - - /* stop immediately if we are 'done' === amount already - * refunded. */ - if (0 >= TALER_amount_cmp (ctx->refund, - ¤t_refund)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Existing refund of %s at or above requested refund. Finished early.\n", - TALER_amount2s (¤t_refund)); - return; - } - - /* Phase 2: Try to increase current refund until it matches desired refund */ - for (unsigned int i = 0; i<num_results; i++) - { - const struct TALER_Amount *increment; - struct TALER_Amount left; - struct TALER_Amount remaining_refund; - - /* How much of the coin is left after the existing refunds? */ - if (0 > - TALER_amount_subtract (&left, - &deposit_amount_with_fee[i], - &deposit_refund[i])) - { - GNUNET_break (0); - ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } - - if ( (0 == left.value) && - (0 == left.fraction) ) - { - /* 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 (&deposit_coin_pubs[i])); - continue; - } - - /* How much of the refund is still to be paid back? */ - if (0 > - TALER_amount_subtract (&remaining_refund, - ctx->refund, - ¤t_refund)) - { - GNUNET_break (0); - ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } - - /* By how much will we increase the refund for this coin? */ - if (0 >= TALER_amount_cmp (&remaining_refund, - &left)) - { - /* remaining_refund <= left */ - increment = &remaining_refund; - } - else - { - increment = &left; - } - - if (0 > - TALER_amount_add (¤t_refund, - ¤t_refund, - increment)) - { - GNUNET_break (0); - ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } - - /* actually run the refund */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Coin %s deposit amount is %s\n", - TALER_B2S (&deposit_coin_pubs[i]), - TALER_amount2s (&deposit_amount_with_fee[i])); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Coin %s refund will be incremented by %s\n", - TALER_B2S (&deposit_coin_pubs[i]), - TALER_amount2s (increment)); - { - enum GNUNET_DB_QueryStatus qs; - - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - (qs = insert_refund (ctx->pg, - ctx->merchant_pub, - ctx->h_contract_terms, - &deposit_coin_pubs[i], - ctx->reason, - increment))) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - ctx->qs = qs; - return; - } - } - - /* stop immediately if we are done */ - if (0 == TALER_amount_cmp (ctx->refund, - ¤t_refund)) - return; - } - - /** - * We end up here if not all of the refund has been covered. - * Although this should be checked as the business should never - * issue a refund bigger than the contract's actual price, we cannot - * rely upon the frontend being correct. - */// - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "The refund of %s is bigger than the order's value\n", - TALER_amount2s (ctx->refund)); - ctx->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; -} - - -/** - * Function called when some backoffice staff decides to award or - * increase the refund on an existing contract. This function - * MUST be called from within a transaction scope setup by the - * caller as it executes multiple SQL statements (NT). - * - * @param cls closure - * @param h_contract_terms - * @param merchant_pub merchant's instance public key - * @param refund maximum refund to return to the customer for this contract - * @param reason 0-terminated UTF-8 string giving the reason why the customer - * got a refund (free form, business-specific) - * @return transaction status - * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a refund is ABOVE the amount we - * were originally paid and thus the transaction failed; - * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid, - * regardless of whether it actually increased the refund beyond - * what was already refunded (idempotency!) - */ -static enum GNUNET_DB_QueryStatus -postgres_increase_refund_for_contract_NT ( - void *cls, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct TALER_Amount *refund, - const char *reason) -{ - struct PostgresClosure *pg = cls; - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (h_contract_terms), - GNUNET_PQ_query_param_auto_from_type (merchant_pub), - GNUNET_PQ_query_param_end - }; - struct InsertRefundContext ctx = { - .pg = pg, - .qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, - .refund = refund, - .reason = reason, - .h_contract_terms = h_contract_terms, - .merchant_pub = merchant_pub - }; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Asked to refund %s on contract %s\n", - TALER_amount2s (refund), - GNUNET_h2s (h_contract_terms)); - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "find_deposits", - params, - &process_deposits_for_refund_cb, - &ctx); - switch (qs) - { - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - /* never paid, means we clearly cannot refund anything */ - return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; - case GNUNET_DB_STATUS_SOFT_ERROR: - case GNUNET_DB_STATUS_HARD_ERROR: - return qs; - default: - /* Got one or more deposits */ - return ctx.qs; - } -} - - -/** * Lookup proof information about a wire transfer. * * @param cls closure @@ -5527,6 +5538,14 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) " WHERE merchant_id=$1)" " AND order_id=$2", 2), + GNUNET_PQ_make_prepare ("find_refunds_by_coin", + "SELECT" + " refund_amount_val" + ",refund_amount_frac" + " FROM merchant_refunds" + " WHERE coin_pub=$1" + " AND order_serial=$2", + 2), /* OLD API: */ @@ -5926,7 +5945,9 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) plugin->mark_contract_paid = &postgres_mark_contract_paid; plugin->refund_coin = &postgres_refund_coin; plugin->lookup_order_status = &postgres_lookup_order_status; - /* OLD API: */ + plugin->increase_refund = &postgres_increase_refund; + +/* OLD API: */ plugin->find_contract_terms_from_hash = &postgres_find_contract_terms_from_hash; plugin->find_paid_contract_terms_from_hash = @@ -5941,8 +5962,6 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) plugin->find_proof_by_wtid = &postgres_find_proof_by_wtid; plugin->get_authorized_tip_amount = &postgres_get_authorized_tip_amount; plugin->lookup_wire_fee = &postgres_lookup_wire_fee; - plugin->increase_refund_for_contract_NT = - &postgres_increase_refund_for_contract_NT; plugin->get_refund_proof = &postgres_get_refund_proof; plugin->put_refund_proof = &postgres_put_refund_proof; plugin->insert_session_info = &postgres_insert_session_info; |