merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit 74c5fa81d74ff5d239f49f74b2bb937c03b83bcb
parent dcc083c0959d395bcd4bcf9aee276eb96ea76dee
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat,  2 May 2020 17:52:13 +0200

work on refund

Diffstat:
Msrc/backenddb/merchant-0001.sql | 3+++
Msrc/backenddb/plugin_merchantdb_postgres.c | 2477++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/include/taler_merchantdb_plugin.h | 68++++++++++++++++++++++++++++++++++----------------------------------
3 files changed, 1285 insertions(+), 1263 deletions(-)

diff --git 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 @@ -1979,254 +1979,31 @@ postgres_lookup_order_status (void *cls, } -/* ********************* OLD API ************************** */ - -/** - * Retrieve proposal data given its proposal data's hashcode - * - * @param cls closure - * @param contract_terms where to store the retrieved proposal data - * @param h_contract_terms proposal data's hashcode that will be used to - * perform the lookup - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_find_contract_terms_from_hash ( - void *cls, - json_t **contract_terms, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_MerchantPublicKeyP *merchant_pub) -{ - struct PostgresClosure *pg = cls; - 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 GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_result_spec_json ("contract_terms", - contract_terms), - GNUNET_PQ_result_spec_end - }; - - check_connection (pg); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "find_contract_terms_from_hash", - params, - rs); -} - - -/** - * Retrieve proposal data given its proposal data's hashcode - * - * @param cls closure - * @param contract_terms where to store the retrieved proposal data - * @param h_contract_terms proposal data's hashcode that will be used to - * perform the lookup - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_find_paid_contract_terms_from_hash (void *cls, - json_t **contract_terms, - const struct - GNUNET_HashCode *h_contract_terms, - const struct - TALER_MerchantPublicKeyP * - merchant_pub) -{ - struct PostgresClosure *pg = cls; - 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 GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_result_spec_json ("contract_terms", - contract_terms), - GNUNET_PQ_result_spec_end - }; - - /* no preflight check here, runs in its own transaction from - caller (in /pay case) */ - check_connection (pg); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "find_paid_contract_terms_from_hash", - params, - rs); -} - - -/** - * Store the order ID that was used to pay for a resource within a session. - * - * @param cls closure - * @param session_id session id - * @param fulfillment_url URL that canonically identifies the resource - * being paid for - * @param order_id the order ID that was used when paying for the resource URL - * @param merchant_pub public key of the merchant, identifying the instance - * @return transaction status - */ -enum GNUNET_DB_QueryStatus -postgres_insert_session_info (void *cls, - const char *session_id, - const char *fulfillment_url, - const char *order_id, - const struct - TALER_MerchantPublicKeyP *merchant_pub) -{ - struct PostgresClosure *pg = cls; - - struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (session_id), - GNUNET_PQ_query_param_string (fulfillment_url), - GNUNET_PQ_query_param_string (order_id), - GNUNET_PQ_query_param_auto_from_type (merchant_pub), - GNUNET_PQ_query_param_absolute_time (&now), - GNUNET_PQ_query_param_end - }; - - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_session_info", - params); -} - - /** - * Retrieve the order ID that was used to pay for a resource within a session. - * - * @param cls closure - * @param[out] order_id where to store the order ID that was used when - * paying for the resource URL - * @param session_id session id - * @param fulfillment_url URL that canonically identifies the resource - * being paid for - * @param merchant_pub public key of the merchant, identifying the instance - * @return transaction status + * Closure for #process_refund_cb(). */ -enum GNUNET_DB_QueryStatus -postgres_find_session_info (void *cls, - char **order_id, - const char *session_id, - const char *fulfillment_url, - const struct TALER_MerchantPublicKeyP *merchant_pub) -{ - struct PostgresClosure *pg = cls; - - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (fulfillment_url), - GNUNET_PQ_query_param_string (session_id), - GNUNET_PQ_query_param_auto_from_type (merchant_pub), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_string ("order_id", - order_id), - GNUNET_PQ_result_spec_end - }; - // We don't clean up the result spec since we want - // to keep around the memory for order_id. - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "find_session_info", - params, - rs); -} - - -/** - * Insert mapping of @a coin_pub and @a h_contract_terms to - * corresponding @a wtid. - * - * @param cls closure - * @param h_contract_terms hashcode of the proposal data paid by @a coin_pub - * @param coin_pub public key of the coin - * @param wtid identifier of the wire transfer in which the exchange - * send us the money for the coin deposit - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_store_coin_to_transfer (void *cls, - const struct GNUNET_HashCode *h_contract_terms, - const struct - TALER_CoinSpendPublicKeyP *coin_pub, - const struct - TALER_WireTransferIdentifierRawP *wtid) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (h_contract_terms), - GNUNET_PQ_query_param_auto_from_type (coin_pub), - GNUNET_PQ_query_param_auto_from_type (wtid), - GNUNET_PQ_query_param_end - }; - - check_connection (pg); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_transfer", - params); -} - - -/** - * Insert wire transfer confirmation from the exchange into the database. - * - * @param cls closure - * @param exchange_url URL of the exchange - * @param wtid identifier of the wire transfer - * @param execution_time when was @a wtid executed - * @param signkey_pub public key used by the exchange for @a exchange_proof - * @param exchange_proof proof from exchange about what the deposit was for - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_store_transfer_to_proof (void *cls, - const char *exchange_url, - const struct - TALER_WireTransferIdentifierRawP *wtid, - struct GNUNET_TIME_Absolute execution_time, - const struct - TALER_ExchangePublicKeyP *signkey_pub, - const json_t *exchange_proof) +struct FindRefundContext { - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (exchange_url), - GNUNET_PQ_query_param_auto_from_type (wtid), - GNUNET_PQ_query_param_absolute_time (&execution_time), - GNUNET_PQ_query_param_auto_from_type (signkey_pub), - TALER_PQ_query_param_json (exchange_proof), - GNUNET_PQ_query_param_end - }; - - check_connection (pg); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_proof", - params); -} - -/** - * Closure for #find_tip_authorizations_cb(). - */ -struct GetAuthorizedTipAmountContext -{ /** - * Total authorized amount. + * Plugin context. */ - struct TALER_Amount authorized_amount; + struct PostgresClosure *pg; /** - * Transaction status code to set. + * Updated to reflect total amount refunded so far. */ - enum GNUNET_DB_QueryStatus qs; + struct TALER_Amount refunded_amount; /** - * Plugin context. + * Set to the largest refund transaction ID encountered. */ - struct PostgresClosure *pg; + uint64_t max_rtransaction_id; + /** + * Set to true on hard errors. + */ + bool err; }; @@ -2234,34 +2011,28 @@ struct GetAuthorizedTipAmountContext * Function to be called with the results of a SELECT statement * that has returned @a num_results results. * - * @param cls of type `struct GetAuthorizedTipAmountContext *` + * @param cls closure, our `struct FindRefundContext` * @param result the postgres result * @param num_result the number of results in @a result */ static void -find_tip_authorizations_cb (void *cls, - PGresult *result, - unsigned int num_results) +process_refund_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct GetAuthorizedTipAmountContext *ctx = cls; - struct PostgresClosure *pg = ctx->pg; - unsigned int i; + struct FindRefundContext *ictx = cls; + struct PostgresClosure *pg = ictx->pg; - for (i = 0; i < num_results; i++) + for (unsigned int i = 0; i<num_results; i++) { - struct TALER_Amount amount; - char *just; - json_t *extra; - struct GNUNET_HashCode h; + /* Sum up existing refunds */ + struct TALER_Amount acc; + uint64_t rtransaction_id; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_string ("justification", - &just), - GNUNET_PQ_result_spec_auto_from_type ("tip_id", - &h), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount", - &amount), - TALER_PQ_result_spec_json ("extra", - &extra), + TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", + &acc), + GNUNET_PQ_result_spec_uint64 ("rtransaction_id", + &rtransaction_id), GNUNET_PQ_result_spec_end }; @@ -2271,111 +2042,85 @@ find_tip_authorizations_cb (void *cls, i)) { GNUNET_break (0); - ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; + ictx->err = true; return; } - - if (0 == i) - { - ctx->authorized_amount = amount; - } - else + if (0 > + TALER_amount_add (&ictx->refunded_amount, + &ictx->refunded_amount, + &acc)) { - if (0 > - TALER_amount_add (&ctx->authorized_amount, - &ctx->authorized_amount, - &amount)) - { - GNUNET_break (0); - GNUNET_PQ_cleanup_result (rs); - ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } + GNUNET_break (0); + ictx->err = true; + return; } - GNUNET_PQ_cleanup_result (rs); - } - - if (0 == i) - { - ctx->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; - } - else - { - /* one aggregated result */ - ctx->qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; + 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)); } } /** - * Get the total amount of authorized tips for a tipping reserve. - * - * @param cls closure, typically a connection to the db - * @param reserve_priv which reserve to check - * @param[out] authorzed_amount amount we've authorized so far for tips - * @return transaction status, usually - * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success - * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the reserve_priv - * does not identify a known tipping reserve + * Closure for #process_deposits_for_refund_cb(). */ -static enum GNUNET_DB_QueryStatus -postgres_get_authorized_tip_amount (void *cls, - const struct - TALER_ReservePrivateKeyP *reserve_priv, - struct TALER_Amount *authorized_amount) +struct InsertRefundContext { - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (reserve_priv), - GNUNET_PQ_query_param_end - }; - enum GNUNET_DB_QueryStatus qs; - struct GetAuthorizedTipAmountContext ctx = { - .pg = pg - }; + /** + * Used to provide a connection to the db + */ + struct PostgresClosure *pg; - check_connection (pg); - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "find_tip_authorizations", - params, - &find_tip_authorizations_cb, - &ctx); - if (0 >= qs) - return qs; - *authorized_amount = ctx.authorized_amount; - return ctx.qs; -} + /** + * 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; +}; /** - * Closure for #find_payments_cb(). + * Data extracted per coin. */ -struct FindPaymentsContext +struct RefundCoinData { + /** - * Function to call with results. + * Public key of a coin. */ - TALER_MERCHANTDB_CoinDepositCallback cb; + struct TALER_CoinSpendPublicKeyP coin_pub; /** - * Closure for @e cls. + * Amount deposited for this coin. */ - void *cb_cls; + struct TALER_Amount deposited_with_fee; /** - * Plugin context. + * Amount refunded already for this coin. */ - struct PostgresClosure *pg; + struct TALER_Amount refund_amount; /** - * Contract term hash used for the search. + * Order serial (actually not really per-coin). */ - const struct GNUNET_HashCode *h_contract_terms; + uint64_t order_serial; /** - * Transaction status (set). + * Maximum rtransaction_id for this coin so far. */ - enum GNUNET_DB_QueryStatus qs; + uint64_t max_rtransaction_id; + }; @@ -2383,419 +2128,549 @@ struct FindPaymentsContext * Function to be called with the results of a SELECT statement * that has returned @a num_results results. * - * @param cls of type `struct FindPaymentsContext *` + * @param cls closure, our `struct InsertRefundContext` * @param result the postgres result * @param num_result the number of results in @a result */ static void -find_payments_cb (void *cls, - PGresult *result, - unsigned int num_results) +process_deposits_for_refund_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct FindPaymentsContext *fpc = cls; - struct PostgresClosure *pg = fpc->pg; + 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, + &current_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 TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_Amount amount_with_fee; - struct TALER_Amount deposit_fee; - struct TALER_Amount refund_fee; - struct TALER_Amount wire_fee; - json_t *exchange_proof; - char *exchange_url; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("coin_pub", - &coin_pub), - GNUNET_PQ_result_spec_string ("exchange_url", - &exchange_url), + &rcd[i].coin_pub), + GNUNET_PQ_result_spec_uint64 ("order_serial", + &rcd[i].order_serial), TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", - &amount_with_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee", - &deposit_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee", - &refund_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", - &wire_fee), - TALER_PQ_result_spec_json ("exchange_proof", - &exchange_proof), + &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 (&current_refund, + &current_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 (&current_refund)); + + /* stop immediately if we are 'done' === amount already + * refunded. */ + if (0 >= TALER_amount_cmp (ctx->refund, + &current_refund)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Existing refund of %s at or above requested refund. Finished early.\n", + TALER_amount2s (&current_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, + &current_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 (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) + if (0 > + TALER_amount_add (&current_refund, + &current_refund, + increment)) { GNUNET_break (0); - fpc->qs = GNUNET_DB_STATUS_HARD_ERROR; + 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, + &current_refund)) + { + ctx->rs = TALER_MERCHANTDB_RS_SUCCESS; return; } - fpc->qs = i + 1; - fpc->cb (fpc->cb_cls, - fpc->h_contract_terms, - &coin_pub, - exchange_url, - &amount_with_fee, - &deposit_fee, - &refund_fee, - &wire_fee, - exchange_proof); - GNUNET_PQ_cleanup_result (rs); } + + /** + * 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; } /** - * Lookup information about coin payments by proposal data hash - * (and @a merchant_pub) + * 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 h_contract_terms key for the search - * @param merchant_pub merchant's public key - * @param cb function to call with payment data - * @param cb_cls closure for @a cb + * @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!) */ -enum GNUNET_DB_QueryStatus -postgres_find_payments (void *cls, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_MerchantPublicKeyP *merchant_pub, - TALER_MERCHANTDB_CoinDepositCallback cb, - void *cb_cls) +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_auto_from_type (h_contract_terms), - GNUNET_PQ_query_param_auto_from_type (merchant_pub), + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_end }; - struct FindPaymentsContext fpc = { - .h_contract_terms = h_contract_terms, - .cb = cb, - .cb_cls = cb_cls, - .pg = pg + struct InsertRefundContext ctx = { + .pg = pg, + .refund = refund, + .reason = reason }; - enum GNUNET_DB_QueryStatus qs; - /* no preflight check here, run in its own transaction by the - caller! */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Finding payment for h_contract_terms '%s'\n", - GNUNET_h2s (h_contract_terms)); - check_connection (pg); + "Asked to refund %s on order %s\n", + TALER_amount2s (refund), + order_id); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "find_deposits", + "find_deposits_for_refund", params, - &find_payments_cb, - &fpc); - if (qs <= 0) - return qs; - return fpc.qs; + &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; + } } -/** - * Closure for #find_payments_by_coin_cb(). - */ -struct FindPaymentsByCoinContext -{ - /** - * Function to call with results. - */ - TALER_MERCHANTDB_CoinDepositCallback cb; - - /** - * Closure for @e cls. - */ - void *cb_cls; - - /** - * Plugin context. - */ - struct PostgresClosure *pg; - - /** - * Coin we are looking for. - */ - const struct TALER_CoinSpendPublicKeyP *coin_pub; - - /** - * Hash of the contract we are looking for. - */ - const struct GNUNET_HashCode *h_contract_terms; - - /** - * Transaction status (set). - */ - enum GNUNET_DB_QueryStatus qs; -}; - +/* ********************* OLD API ************************** */ /** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. + * Retrieve proposal data given its proposal data's hashcode * - * @param cls of type `struct FindPaymentsByCoinContext *` - * @param result the postgres result - * @param num_result the number of results in @a result + * @param cls closure + * @param contract_terms where to store the retrieved proposal data + * @param h_contract_terms proposal data's hashcode that will be used to + * perform the lookup + * @return transaction status */ -static void -find_payments_by_coin_cb (void *cls, - PGresult *result, - unsigned int num_results) +static enum GNUNET_DB_QueryStatus +postgres_find_contract_terms_from_hash ( + void *cls, + json_t **contract_terms, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_MerchantPublicKeyP *merchant_pub) { - struct FindPaymentsByCoinContext *fpc = cls; - struct PostgresClosure *pg = fpc->pg; - - for (unsigned int i = 0; i<num_results; i++) - { - struct TALER_Amount amount_with_fee; - struct TALER_Amount deposit_fee; - struct TALER_Amount refund_fee; - struct TALER_Amount wire_fee; - char *exchange_url; - json_t *exchange_proof; - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", - &amount_with_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee", - &deposit_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee", - &refund_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", - &wire_fee), - GNUNET_PQ_result_spec_string ("exchange_url", - &exchange_url), - TALER_PQ_result_spec_json ("exchange_proof", - &exchange_proof), - GNUNET_PQ_result_spec_end - }; + struct PostgresClosure *pg = cls; + 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 GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_result_spec_json ("contract_terms", + contract_terms), + GNUNET_PQ_result_spec_end + }; - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - fpc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } - fpc->qs = i + 1; - fpc->cb (fpc->cb_cls, - fpc->h_contract_terms, - fpc->coin_pub, - exchange_url, - &amount_with_fee, - &deposit_fee, - &refund_fee, - &wire_fee, - exchange_proof); - GNUNET_PQ_cleanup_result (rs); - } + check_connection (pg); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "find_contract_terms_from_hash", + params, + rs); } /** - * Retrieve information about a deposited coin. + * Retrieve proposal data given its proposal data's hashcode * * @param cls closure - * @param h_contract_terms hashcode of the proposal data paid by @a coin_pub - * @param merchant_pub merchant's public key. - * @param coin_pub coin's public key used for the search - * @param cb function to call with payment data - * @param cb_cls closure for @a cb + * @param contract_terms where to store the retrieved proposal data + * @param h_contract_terms proposal data's hashcode that will be used to + * perform the lookup * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_find_payments_by_hash_and_coin (void *cls, - const struct - GNUNET_HashCode *h_contract_terms, - const struct - TALER_MerchantPublicKeyP *merchant_pub, - const struct - TALER_CoinSpendPublicKeyP *coin_pub, - TALER_MERCHANTDB_CoinDepositCallback cb, - void *cb_cls) +postgres_find_paid_contract_terms_from_hash (void *cls, + json_t **contract_terms, + const struct + GNUNET_HashCode *h_contract_terms, + const struct + TALER_MerchantPublicKeyP * + merchant_pub) { struct PostgresClosure *pg = cls; 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_auto_from_type (coin_pub), GNUNET_PQ_query_param_end }; - struct FindPaymentsByCoinContext fpc = { - .cb = cb, - .cb_cls = cb_cls, - .pg = pg, - .h_contract_terms = h_contract_terms, - .coin_pub = coin_pub + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_result_spec_json ("contract_terms", + contract_terms), + GNUNET_PQ_result_spec_end }; - enum GNUNET_DB_QueryStatus qs; + /* no preflight check here, runs in its own transaction from + caller (in /pay case) */ check_connection (pg); - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "find_deposits_by_hash_and_coin", - params, - &find_payments_by_coin_cb, - &fpc); - if (0 >= qs) - return qs; - return fpc.qs; + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "find_paid_contract_terms_from_hash", + params, + rs); } /** - * Closure for #find_transfers_cb(). + * Store the order ID that was used to pay for a resource within a session. + * + * @param cls closure + * @param session_id session id + * @param fulfillment_url URL that canonically identifies the resource + * being paid for + * @param order_id the order ID that was used when paying for the resource URL + * @param merchant_pub public key of the merchant, identifying the instance + * @return transaction status */ -struct FindTransfersContext +enum GNUNET_DB_QueryStatus +postgres_insert_session_info (void *cls, + const char *session_id, + const char *fulfillment_url, + const char *order_id, + const struct + TALER_MerchantPublicKeyP *merchant_pub) { - /** - * Function to call on results. - */ - TALER_MERCHANTDB_TransferCallback cb; - - /** - * Closure for @e cb. - */ - void *cb_cls; + struct PostgresClosure *pg = cls; - /** - * Hash of the contract we are looking under. - */ - const struct GNUNET_HashCode *h_contract_terms; + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (session_id), + GNUNET_PQ_query_param_string (fulfillment_url), + GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_auto_from_type (merchant_pub), + GNUNET_PQ_query_param_absolute_time (&now), + GNUNET_PQ_query_param_end + }; - /** - * Transaction status (set). - */ - enum GNUNET_DB_QueryStatus qs; -}; + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_session_info", + params); +} /** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. + * Retrieve the order ID that was used to pay for a resource within a session. * - * @param cls of type `struct FindTransfersContext *` - * @param result the postgres result - * @param num_result the number of results in @a result + * @param cls closure + * @param[out] order_id where to store the order ID that was used when + * paying for the resource URL + * @param session_id session id + * @param fulfillment_url URL that canonically identifies the resource + * being paid for + * @param merchant_pub public key of the merchant, identifying the instance + * @return transaction status */ -static void -find_transfers_cb (void *cls, - PGresult *result, - unsigned int num_results) +enum GNUNET_DB_QueryStatus +postgres_find_session_info (void *cls, + char **order_id, + const char *session_id, + const char *fulfillment_url, + const struct TALER_MerchantPublicKeyP *merchant_pub) { - struct FindTransfersContext *ftc = cls; - - for (unsigned int i = 0; i<num_results; i++) - { - struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_WireTransferIdentifierRawP wtid; - struct GNUNET_TIME_Absolute execution_time; - json_t *proof; - - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("coin_pub", - &coin_pub), - GNUNET_PQ_result_spec_auto_from_type ("wtid", - &wtid), - GNUNET_PQ_result_spec_absolute_time ("execution_time", - &execution_time), - TALER_PQ_result_spec_json ("proof", - &proof), - GNUNET_PQ_result_spec_end - }; + struct PostgresClosure *pg = cls; - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - ftc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } - ftc->qs = i + 1; - ftc->cb (ftc->cb_cls, - ftc->h_contract_terms, - &coin_pub, - &wtid, - execution_time, - proof); - GNUNET_PQ_cleanup_result (rs); - } + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (fulfillment_url), + GNUNET_PQ_query_param_string (session_id), + GNUNET_PQ_query_param_auto_from_type (merchant_pub), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_string ("order_id", + order_id), + GNUNET_PQ_result_spec_end + }; + // We don't clean up the result spec since we want + // to keep around the memory for order_id. + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "find_session_info", + params, + rs); } /** - * Lookup information about a transfer by @a h_contract_terms. Note - * that in theory there could be multiple wire transfers for a - * single @a h_contract_terms, as the transaction may have involved - * multiple coins and the coins may be spread over different wire - * transfers. + * Insert mapping of @a coin_pub and @a h_contract_terms to + * corresponding @a wtid. * * @param cls closure - * @param h_contract_terms key for the search - * @param cb function to call with transfer data - * @param cb_cls closure for @a cb + * @param h_contract_terms hashcode of the proposal data paid by @a coin_pub + * @param coin_pub public key of the coin + * @param wtid identifier of the wire transfer in which the exchange + * send us the money for the coin deposit * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_find_transfers_by_hash (void *cls, +postgres_store_coin_to_transfer (void *cls, const struct GNUNET_HashCode *h_contract_terms, - TALER_MERCHANTDB_TransferCallback cb, - void *cb_cls) + const struct + TALER_CoinSpendPublicKeyP *coin_pub, + const struct + TALER_WireTransferIdentifierRawP *wtid) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_auto_from_type (coin_pub), + GNUNET_PQ_query_param_auto_from_type (wtid), GNUNET_PQ_query_param_end }; - struct FindTransfersContext ftc = { - .h_contract_terms = h_contract_terms, - .cb = cb, - .cb_cls = cb_cls - }; - enum GNUNET_DB_QueryStatus qs; check_connection (pg); - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "find_transfers_by_hash", - params, - &find_transfers_cb, - &ftc); - if (0 >= qs) - return qs; - return ftc.qs; + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_transfer", + params); } /** - * Closure for #find_deposits_cb(). + * Insert wire transfer confirmation from the exchange into the database. + * + * @param cls closure + * @param exchange_url URL of the exchange + * @param wtid identifier of the wire transfer + * @param execution_time when was @a wtid executed + * @param signkey_pub public key used by the exchange for @a exchange_proof + * @param exchange_proof proof from exchange about what the deposit was for + * @return transaction status */ -struct FindDepositsContext +static enum GNUNET_DB_QueryStatus +postgres_store_transfer_to_proof (void *cls, + const char *exchange_url, + const struct + TALER_WireTransferIdentifierRawP *wtid, + struct GNUNET_TIME_Absolute execution_time, + const struct + TALER_ExchangePublicKeyP *signkey_pub, + const json_t *exchange_proof) { + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (exchange_url), + GNUNET_PQ_query_param_auto_from_type (wtid), + GNUNET_PQ_query_param_absolute_time (&execution_time), + GNUNET_PQ_query_param_auto_from_type (signkey_pub), + TALER_PQ_query_param_json (exchange_proof), + GNUNET_PQ_query_param_end + }; + + check_connection (pg); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_proof", + params); +} + +/** + * Closure for #find_tip_authorizations_cb(). + */ +struct GetAuthorizedTipAmountContext +{ /** - * Function to call for each result. + * Total authorized amount. */ - TALER_MERCHANTDB_CoinDepositCallback cb; + struct TALER_Amount authorized_amount; /** - * Closure for @e cb. + * Transaction status code to set. */ - void *cb_cls; + enum GNUNET_DB_QueryStatus qs; /** * Plugin context. */ struct PostgresClosure *pg; - /** - * Transaction status (set). - */ - enum GNUNET_DB_QueryStatus qs; }; @@ -2803,45 +2678,34 @@ struct FindDepositsContext * Function to be called with the results of a SELECT statement * that has returned @a num_results results. * - * @param cls of type `struct FindDepositsContext *` + * @param cls of type `struct GetAuthorizedTipAmountContext *` * @param result the postgres result * @param num_result the number of results in @a result */ static void -find_deposits_cb (void *cls, - PGresult *result, - unsigned int num_results) +find_tip_authorizations_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct FindDepositsContext *fdc = cls; - struct PostgresClosure *pg = fdc->pg; + struct GetAuthorizedTipAmountContext *ctx = cls; + struct PostgresClosure *pg = ctx->pg; + unsigned int i; - for (unsigned int i = 0; i<num_results; i++) + for (i = 0; i < num_results; i++) { - struct GNUNET_HashCode h_contract_terms; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_Amount amount_with_fee; - struct TALER_Amount deposit_fee; - struct TALER_Amount refund_fee; - struct TALER_Amount wire_fee; - char *exchange_url; - json_t *exchange_proof; + struct TALER_Amount amount; + char *just; + json_t *extra; + struct GNUNET_HashCode h; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", - &h_contract_terms), - GNUNET_PQ_result_spec_auto_from_type ("coin_pub", - &coin_pub), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", - &amount_with_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee", - &deposit_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee", - &refund_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", - &wire_fee), - GNUNET_PQ_result_spec_string ("exchange_url", - &exchange_url), - TALER_PQ_result_spec_json ("exchange_proof", - &exchange_proof), + GNUNET_PQ_result_spec_string ("justification", + &just), + GNUNET_PQ_result_spec_auto_from_type ("tip_id", + &h), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount", + &amount), + TALER_PQ_result_spec_json ("extra", + &extra), GNUNET_PQ_result_spec_end }; @@ -2851,78 +2715,96 @@ find_deposits_cb (void *cls, i)) { GNUNET_break (0); - fdc->qs = GNUNET_DB_STATUS_HARD_ERROR; + ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } - fdc->qs = i + 1; - fdc->cb (fdc->cb_cls, - &h_contract_terms, - &coin_pub, - exchange_url, - &amount_with_fee, - &deposit_fee, - &refund_fee, - &wire_fee, - exchange_proof); + + if (0 == i) + { + ctx->authorized_amount = amount; + } + else + { + if (0 > + TALER_amount_add (&ctx->authorized_amount, + &ctx->authorized_amount, + &amount)) + { + GNUNET_break (0); + GNUNET_PQ_cleanup_result (rs); + ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; + return; + } + } GNUNET_PQ_cleanup_result (rs); } + + if (0 == i) + { + ctx->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + } + else + { + /* one aggregated result */ + ctx->qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; + } } /** - * Lookup information about a coin deposits by @a wtid. + * Get the total amount of authorized tips for a tipping reserve. * - * @param cls closure - * @param wtid wire transfer identifier to find matching transactions for - * @param cb function to call with payment data - * @param cb_cls closure for @a cb - * @return transaction status + * @param cls closure, typically a connection to the db + * @param reserve_priv which reserve to check + * @param[out] authorzed_amount amount we've authorized so far for tips + * @return transaction status, usually + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the reserve_priv + * does not identify a known tipping reserve */ static enum GNUNET_DB_QueryStatus -postgres_find_deposits_by_wtid (void *cls, - const struct - TALER_WireTransferIdentifierRawP *wtid, - TALER_MERCHANTDB_CoinDepositCallback cb, - void *cb_cls) +postgres_get_authorized_tip_amount (void *cls, + const struct + TALER_ReservePrivateKeyP *reserve_priv, + struct TALER_Amount *authorized_amount) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (wtid), + GNUNET_PQ_query_param_auto_from_type (reserve_priv), GNUNET_PQ_query_param_end }; - struct FindDepositsContext fdc = { - .cb = cb, - .cb_cls = cb_cls, + enum GNUNET_DB_QueryStatus qs; + struct GetAuthorizedTipAmountContext ctx = { .pg = pg }; - enum GNUNET_DB_QueryStatus qs; check_connection (pg); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "find_deposits_by_wtid", + "find_tip_authorizations", params, - &find_deposits_cb, - &fdc); + &find_tip_authorizations_cb, + &ctx); if (0 >= qs) return qs; - return fdc.qs; + *authorized_amount = ctx.authorized_amount; + return ctx.qs; } /** - * Closure for #get_refunds_cb(). + * Closure for #find_payments_cb(). */ -struct GetRefundsContext +struct FindPaymentsContext { /** - * Function to call for each refund. + * Function to call with results. */ - TALER_MERCHANTDB_CoinRefundCallback rc; + TALER_MERCHANTDB_CoinDepositCallback cb; /** - * Closure for @e rc. + * Closure for @e cls. */ - void *rc_cls; + void *cb_cls; /** * Plugin context. @@ -2930,7 +2812,12 @@ struct GetRefundsContext struct PostgresClosure *pg; /** - * Transaction result. + * Contract term hash used for the search. + */ + const struct GNUNET_HashCode *h_contract_terms; + + /** + * Transaction status (set). */ enum GNUNET_DB_QueryStatus qs; }; @@ -2940,39 +2827,42 @@ struct GetRefundsContext * Function to be called with the results of a SELECT statement * that has returned @a num_results results. * - * @param cls of type `struct GetRefundsContext *` + * @param cls of type `struct FindPaymentsContext *` * @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) +find_payments_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct GetRefundsContext *grc = cls; - struct PostgresClosure *pg = grc->pg; + struct FindPaymentsContext *fpc = cls; + struct PostgresClosure *pg = fpc->pg; for (unsigned int i = 0; i<num_results; i++) { struct TALER_CoinSpendPublicKeyP coin_pub; - uint64_t rtransaction_id; - struct TALER_Amount refund_amount; + struct TALER_Amount amount_with_fee; + struct TALER_Amount deposit_fee; struct TALER_Amount refund_fee; - char *reason; + struct TALER_Amount wire_fee; + json_t *exchange_proof; char *exchange_url; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("coin_pub", &coin_pub), GNUNET_PQ_result_spec_string ("exchange_url", &exchange_url), - GNUNET_PQ_result_spec_uint64 ("rtransaction_id", - &rtransaction_id), - TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", - &refund_amount), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", + &amount_with_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee", + &deposit_fee), TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee", &refund_fee), - GNUNET_PQ_result_spec_string ("reason", - &reason), + TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", + &wire_fee), + TALER_PQ_result_spec_json ("exchange_proof", + &exchange_proof), GNUNET_PQ_result_spec_end }; @@ -2982,330 +2872,248 @@ get_refunds_cb (void *cls, i)) { GNUNET_break (0); - grc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } - grc->qs = i + 1; - grc->rc (grc->rc_cls, - &coin_pub, - exchange_url, - rtransaction_id, - reason, - &refund_amount, - &refund_fee); - GNUNET_PQ_cleanup_result (rs); - } -} - - -/** - * Obtain refunds associated with a contract. - * - * @param cls closure, typically a connection to the db - * @param merchant_pub public key of the merchant instance - * @param h_contract_terms hash code of the contract - * @param rc function to call for each coin on which there is a refund - * @param rc_cls closure for @a rc - * @return transaction status - */ -enum GNUNET_DB_QueryStatus -postgres_get_refunds_from_contract_terms_hash ( - void *cls, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct GNUNET_HashCode *h_contract_terms, - TALER_MERCHANTDB_CoinRefundCallback rc, - void *rc_cls) -{ - 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_end - }; - struct GetRefundsContext grc = { - .rc = rc, - .rc_cls = rc_cls, - .pg = pg - }; - enum GNUNET_DB_QueryStatus qs; - - /* no preflight check here, run in transaction by caller! */ - TALER_LOG_DEBUG ("Looking for refund %s + %s\n", - GNUNET_h2s (h_contract_terms), - TALER_B2S (merchant_pub)); - check_connection (pg); - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "find_refunds_from_contract_terms_hash", - params, - &get_refunds_cb, - &grc); - if (0 >= qs) - return qs; - return grc.qs; + fpc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return; + } + fpc->qs = i + 1; + fpc->cb (fpc->cb_cls, + fpc->h_contract_terms, + &coin_pub, + exchange_url, + &amount_with_fee, + &deposit_fee, + &refund_fee, + &wire_fee, + exchange_proof); + GNUNET_PQ_cleanup_result (rs); + } } /** - * Obtain refund proofs associated with a refund operation on a - * coin. + * Lookup information about coin payments by proposal data hash + * (and @a merchant_pub) * - * @param cls closure, typically a connection to the db - * @param merchant_pub public key of the merchant instance - * @param h_contract_terms hash code of the contract - * @param coin_pub public key of the coin - * @param rtransaction_id identificator of the refund - * @param[out] exchange_pub public key of the exchange affirming the refund - * @param[out] exchange_sig signature of the exchange affirming the refund + * @param cls closure + * @param h_contract_terms key for the search + * @param merchant_pub merchant's public key + * @param cb function to call with payment data + * @param cb_cls closure for @a cb * @return transaction status */ -static enum GNUNET_DB_QueryStatus -postgres_get_refund_proof ( - void *cls, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - uint64_t rtransaction_id, - struct TALER_ExchangePublicKeyP *exchange_pub, - struct TALER_ExchangeSignatureP *exchange_sig) +enum GNUNET_DB_QueryStatus +postgres_find_payments (void *cls, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_MerchantPublicKeyP *merchant_pub, + TALER_MERCHANTDB_CoinDepositCallback cb, + void *cb_cls) { struct PostgresClosure *pg = cls; 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_auto_from_type (coin_pub), - GNUNET_PQ_query_param_uint64 (&rtransaction_id), GNUNET_PQ_query_param_end }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("exchange_sig", - exchange_sig), - GNUNET_PQ_result_spec_auto_from_type ("exchange_pub", - exchange_pub), - GNUNET_PQ_result_spec_end + struct FindPaymentsContext fpc = { + .h_contract_terms = h_contract_terms, + .cb = cb, + .cb_cls = cb_cls, + .pg = pg }; + enum GNUNET_DB_QueryStatus qs; + /* no preflight check here, run in its own transaction by the + caller! */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Finding payment for h_contract_terms '%s'\n", + GNUNET_h2s (h_contract_terms)); check_connection (pg); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "get_refund_proof", - params, - rs); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "find_deposits", + params, + &find_payments_cb, + &fpc); + if (qs <= 0) + return qs; + return fpc.qs; } /** - * Store refund proofs associated with a refund operation on a - * coin. - * - * @param cls closure, typically a connection to the db - * @param merchant_pub public key of the merchant instance - * @param h_contract_terms hash code of the contract - * @param coin_pub public key of the coin - * @param rtransaction_id identificator of the refund - * @param exchange_pub public key of the exchange affirming the refund - * @param exchange_sig signature of the exchange affirming the refund - * @return transaction status + * Closure for #find_payments_by_coin_cb(). */ -static enum GNUNET_DB_QueryStatus -postgres_put_refund_proof ( - void *cls, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - uint64_t rtransaction_id, - const struct TALER_ExchangePublicKeyP *exchange_pub, - const struct TALER_ExchangeSignatureP *exchange_sig) +struct FindPaymentsByCoinContext { - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&rtransaction_id), - 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_auto_from_type (exchange_sig), - GNUNET_PQ_query_param_auto_from_type (exchange_pub), - GNUNET_PQ_query_param_end - }; + /** + * Function to call with results. + */ + TALER_MERCHANTDB_CoinDepositCallback cb; - TALER_LOG_DEBUG ("Inserting refund proof %s + %s\n", - GNUNET_h2s (h_contract_terms), - TALER_B2S (coin_pub)); - check_connection (pg); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_refund_proof", - params); -} + /** + * Closure for @e cls. + */ + void *cb_cls; + /** + * Plugin context. + */ + struct PostgresClosure *pg; -/** - * 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 - }; + /** + * Coin we are looking for. + */ + const struct TALER_CoinSpendPublicKeyP *coin_pub; - TALER_LOG_DEBUG ("Inserting refund %s + %s\n", - GNUNET_h2s (h_contract_terms), - TALER_B2S (merchant_pub)); + /** + * Hash of the contract we are looking for. + */ + const struct GNUNET_HashCode *h_contract_terms; - check_connection (pg); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_refund", - params); -} + /** + * Transaction status (set). + */ + enum GNUNET_DB_QueryStatus qs; +}; /** - * Store information about wire fees charged by an exchange, - * including signature (so we have proof). + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. * - * @param cls closure - * @paramm exchange_pub public key of the exchange - * @param h_wire_method hash of wire method - * @param wire_fee wire fee charged - * @param closing_fee closing fee charged (irrelevant for us, - * but needed to check signature) - * @param start_date start of fee being used - * @param end_date end of fee being used - * @param exchange_sig signature of exchange over fee structure - * @return transaction status code + * @param cls of type `struct FindPaymentsByCoinContext *` + * @param result the postgres result + * @param num_result the number of results in @a result */ -static enum GNUNET_DB_QueryStatus -postgres_store_wire_fee_by_exchange ( - void *cls, - const struct - TALER_MasterPublicKeyP *exchange_pub, - const struct - GNUNET_HashCode *h_wire_method, - const struct TALER_Amount *wire_fee, - const struct TALER_Amount *closing_fee, - struct GNUNET_TIME_Absolute start_date, - struct GNUNET_TIME_Absolute end_date, - const struct - TALER_MasterSignatureP *exchange_sig) +static void +find_payments_by_coin_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (exchange_pub), - GNUNET_PQ_query_param_auto_from_type (h_wire_method), - TALER_PQ_query_param_amount (wire_fee), - TALER_PQ_query_param_amount (closing_fee), - GNUNET_PQ_query_param_absolute_time (&start_date), - GNUNET_PQ_query_param_absolute_time (&end_date), - GNUNET_PQ_query_param_auto_from_type (exchange_sig), - GNUNET_PQ_query_param_end - }; + struct FindPaymentsByCoinContext *fpc = cls; + struct PostgresClosure *pg = fpc->pg; - /* no preflight check here, run in its own transaction by the caller */ - check_connection (pg); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Storing wire fee for %s starting at %s of %s\n", - TALER_B2S (exchange_pub), - GNUNET_STRINGS_absolute_time_to_string (start_date), - TALER_amount2s (wire_fee)); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_wire_fee", - params); + for (unsigned int i = 0; i<num_results; i++) + { + struct TALER_Amount amount_with_fee; + struct TALER_Amount deposit_fee; + struct TALER_Amount refund_fee; + struct TALER_Amount wire_fee; + char *exchange_url; + json_t *exchange_proof; + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", + &amount_with_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee", + &deposit_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee", + &refund_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", + &wire_fee), + GNUNET_PQ_result_spec_string ("exchange_url", + &exchange_url), + TALER_PQ_result_spec_json ("exchange_proof", + &exchange_proof), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + fpc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return; + } + fpc->qs = i + 1; + fpc->cb (fpc->cb_cls, + fpc->h_contract_terms, + fpc->coin_pub, + exchange_url, + &amount_with_fee, + &deposit_fee, + &refund_fee, + &wire_fee, + exchange_proof); + GNUNET_PQ_cleanup_result (rs); + } } /** - * Obtain information about wire fees charged by an exchange, - * including signature (so we have proof). + * Retrieve information about a deposited coin. * * @param cls closure - * @param exchange_pub public key of the exchange - * @param h_wire_method hash of wire method - * @param contract_date date of the contract to use for the lookup - * @param[out] wire_fee wire fee charged - * @param[out] closing_fee closing fee charged (irrelevant for us, - * but needed to check signature) - * @param[out] start_date start of fee being used - * @param[out] end_date end of fee being used - * @param[out] exchange_sig signature of exchange over fee structure - * @return transaction status code + * @param h_contract_terms hashcode of the proposal data paid by @a coin_pub + * @param merchant_pub merchant's public key. + * @param coin_pub coin's public key used for the search + * @param cb function to call with payment data + * @param cb_cls closure for @a cb + * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_lookup_wire_fee (void *cls, - const struct TALER_MasterPublicKeyP *exchange_pub, - const struct GNUNET_HashCode *h_wire_method, - struct GNUNET_TIME_Absolute contract_date, - struct TALER_Amount *wire_fee, - struct TALER_Amount *closing_fee, - struct GNUNET_TIME_Absolute *start_date, - struct GNUNET_TIME_Absolute *end_date, - struct TALER_MasterSignatureP *exchange_sig) +postgres_find_payments_by_hash_and_coin (void *cls, + const struct + GNUNET_HashCode *h_contract_terms, + const struct + TALER_MerchantPublicKeyP *merchant_pub, + const struct + TALER_CoinSpendPublicKeyP *coin_pub, + TALER_MERCHANTDB_CoinDepositCallback cb, + void *cb_cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (exchange_pub), - GNUNET_PQ_query_param_auto_from_type (h_wire_method), - GNUNET_PQ_query_param_absolute_time (&contract_date), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_auto_from_type (merchant_pub), + GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_end }; - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", - wire_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee", - closing_fee), - GNUNET_PQ_result_spec_absolute_time ("start_date", - start_date), - GNUNET_PQ_result_spec_absolute_time ("end_date", - end_date), - GNUNET_PQ_result_spec_auto_from_type ("exchange_sig", - exchange_sig), - GNUNET_PQ_result_spec_end + struct FindPaymentsByCoinContext fpc = { + .cb = cb, + .cb_cls = cb_cls, + .pg = pg, + .h_contract_terms = h_contract_terms, + .coin_pub = coin_pub }; + enum GNUNET_DB_QueryStatus qs; check_connection (pg); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_wire_fee", - params, - rs); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "find_deposits_by_hash_and_coin", + params, + &find_payments_by_coin_cb, + &fpc); + if (0 >= qs) + return qs; + return fpc.qs; } /** - * Closure for #process_refund_cb. + * Closure for #find_transfers_cb(). */ -struct FindRefundContext +struct FindTransfersContext { + /** + * Function to call on results. + */ + TALER_MERCHANTDB_TransferCallback cb; /** - * Plugin context. + * Closure for @e cb. */ - struct PostgresClosure *pg; + void *cb_cls; /** - * Updated to reflect total amount refunded so far. + * Hash of the contract we are looking under. */ - struct TALER_Amount refunded_amount; + const struct GNUNET_HashCode *h_contract_terms; /** - * Set to #GNUNET_SYSERR on hard errors. + * Transaction status (set). */ - int err; + enum GNUNET_DB_QueryStatus qs; }; @@ -3313,25 +3121,33 @@ struct FindRefundContext * 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 cls of type `struct FindTransfersContext *` * @param result the postgres result * @param num_result the number of results in @a result */ static void -process_refund_cb (void *cls, +find_transfers_cb (void *cls, PGresult *result, unsigned int num_results) { - struct FindRefundContext *ictx = cls; - struct PostgresClosure *pg = ictx->pg; + struct FindTransfersContext *ftc = cls; for (unsigned int i = 0; i<num_results; i++) { - /* Sum up existing refunds */ - struct TALER_Amount acc; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_WireTransferIdentifierRawP wtid; + struct GNUNET_TIME_Absolute execution_time; + json_t *proof; + struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", - &acc), + GNUNET_PQ_result_spec_auto_from_type ("coin_pub", + &coin_pub), + GNUNET_PQ_result_spec_auto_from_type ("wtid", + &wtid), + GNUNET_PQ_result_spec_absolute_time ("execution_time", + &execution_time), + TALER_PQ_result_spec_json ("proof", + &proof), GNUNET_PQ_result_spec_end }; @@ -3341,57 +3157,224 @@ process_refund_cb (void *cls, i)) { GNUNET_break (0); - ictx->err = GNUNET_SYSERR; + ftc->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } - if (0 > - TALER_amount_add (&ictx->refunded_amount, - &ictx->refunded_amount, - &acc)) + ftc->qs = i + 1; + ftc->cb (ftc->cb_cls, + ftc->h_contract_terms, + &coin_pub, + &wtid, + execution_time, + proof); + GNUNET_PQ_cleanup_result (rs); + } +} + + +/** + * Lookup information about a transfer by @a h_contract_terms. Note + * that in theory there could be multiple wire transfers for a + * single @a h_contract_terms, as the transaction may have involved + * multiple coins and the coins may be spread over different wire + * transfers. + * + * @param cls closure + * @param h_contract_terms key for the search + * @param cb function to call with transfer data + * @param cb_cls closure for @a cb + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +postgres_find_transfers_by_hash (void *cls, + const struct GNUNET_HashCode *h_contract_terms, + TALER_MERCHANTDB_TransferCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_end + }; + struct FindTransfersContext ftc = { + .h_contract_terms = h_contract_terms, + .cb = cb, + .cb_cls = cb_cls + }; + enum GNUNET_DB_QueryStatus qs; + + check_connection (pg); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "find_transfers_by_hash", + params, + &find_transfers_cb, + &ftc); + if (0 >= qs) + return qs; + return ftc.qs; +} + + +/** + * Closure for #find_deposits_cb(). + */ +struct FindDepositsContext +{ + + /** + * Function to call for each result. + */ + TALER_MERCHANTDB_CoinDepositCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Plugin context. + */ + struct PostgresClosure *pg; + + /** + * Transaction status (set). + */ + 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 of type `struct FindDepositsContext *` + * @param result the postgres result + * @param num_result the number of results in @a result + */ +static void +find_deposits_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct FindDepositsContext *fdc = cls; + struct PostgresClosure *pg = fdc->pg; + + for (unsigned int i = 0; i<num_results; i++) + { + struct GNUNET_HashCode h_contract_terms; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_Amount amount_with_fee; + struct TALER_Amount deposit_fee; + struct TALER_Amount refund_fee; + struct TALER_Amount wire_fee; + char *exchange_url; + json_t *exchange_proof; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", + &h_contract_terms), + GNUNET_PQ_result_spec_auto_from_type ("coin_pub", + &coin_pub), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", + &amount_with_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee", + &deposit_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee", + &refund_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", + &wire_fee), + GNUNET_PQ_result_spec_string ("exchange_url", + &exchange_url), + TALER_PQ_result_spec_json ("exchange_proof", + &exchange_proof), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) { GNUNET_break (0); - ictx->err = GNUNET_SYSERR; + fdc->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Found refund of %s\n", - TALER_amount2s (&acc)); + fdc->qs = i + 1; + fdc->cb (fdc->cb_cls, + &h_contract_terms, + &coin_pub, + exchange_url, + &amount_with_fee, + &deposit_fee, + &refund_fee, + &wire_fee, + exchange_proof); + GNUNET_PQ_cleanup_result (rs); } } /** - * Closure for #process_deposits_for_refund_cb. + * Lookup information about a coin deposits by @a wtid. + * + * @param cls closure + * @param wtid wire transfer identifier to find matching transactions for + * @param cb function to call with payment data + * @param cb_cls closure for @a cb + * @return transaction status */ -struct InsertRefundContext +static enum GNUNET_DB_QueryStatus +postgres_find_deposits_by_wtid (void *cls, + const struct + TALER_WireTransferIdentifierRawP *wtid, + TALER_MERCHANTDB_CoinDepositCallback cb, + void *cb_cls) { - /** - * Used to provide a connection to the db - */ - struct PostgresClosure *pg; + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (wtid), + GNUNET_PQ_query_param_end + }; + struct FindDepositsContext fdc = { + .cb = cb, + .cb_cls = cb_cls, + .pg = pg + }; + enum GNUNET_DB_QueryStatus qs; - /** - * Amount to which increase the refund for this contract - */ - const struct TALER_Amount *refund; + check_connection (pg); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "find_deposits_by_wtid", + params, + &find_deposits_cb, + &fdc); + if (0 >= qs) + return qs; + return fdc.qs; +} + +/** + * Closure for #get_refunds_cb(). + */ +struct GetRefundsContext +{ /** - * Merchant instance public key + * Function to call for each refund. */ - const struct TALER_MerchantPublicKeyP *merchant_pub; + TALER_MERCHANTDB_CoinRefundCallback rc; /** - * Hash code representing the contract + * Closure for @e rc. */ - const struct GNUNET_HashCode *h_contract_terms; + void *rc_cls; /** - * Human-readable reason behind this refund + * Plugin context. */ - const char *reason; + struct PostgresClosure *pg; /** - * Transaction status code. + * Transaction result. */ enum GNUNET_DB_QueryStatus qs; }; @@ -3401,48 +3384,41 @@ struct InsertRefundContext * 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 cls of type `struct GetRefundsContext *` * @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) +get_refunds_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, - &current_refund)); + struct GetRefundsContext *grc = cls; + struct PostgresClosure *pg = grc->pg; - /* 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; + uint64_t rtransaction_id; + struct TALER_Amount refund_amount; + struct TALER_Amount refund_fee; + char *reason; + char *exchange_url; 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_string ("exchange_url", + &exchange_url), + GNUNET_PQ_result_spec_uint64 ("rtransaction_id", + &rtransaction_id), + TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", + &refund_amount), + TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee", + &refund_fee), + GNUNET_PQ_result_spec_string ("reason", + &reason), 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, @@ -3450,232 +3426,267 @@ process_deposits_for_refund_cb (void *cls, 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 (&current_refund, - &current_refund, - &ictx.refunded_amount)) - { - GNUNET_break (0); - ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; + grc->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)); + grc->qs = i + 1; + grc->rc (grc->rc_cls, + &coin_pub, + exchange_url, + rtransaction_id, + reason, + &refund_amount, + &refund_fee); + GNUNET_PQ_cleanup_result (rs); } +} - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Total existing refund is %s\n", - TALER_amount2s (&current_refund)); - /* stop immediately if we are 'done' === amount already - * refunded. */ - if (0 >= TALER_amount_cmp (ctx->refund, - &current_refund)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Existing refund of %s at or above requested refund. Finished early.\n", - TALER_amount2s (&current_refund)); - return; - } +/** + * Obtain refunds associated with a contract. + * + * @param cls closure, typically a connection to the db + * @param merchant_pub public key of the merchant instance + * @param h_contract_terms hash code of the contract + * @param rc function to call for each coin on which there is a refund + * @param rc_cls closure for @a rc + * @return transaction status + */ +enum GNUNET_DB_QueryStatus +postgres_get_refunds_from_contract_terms_hash ( + void *cls, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct GNUNET_HashCode *h_contract_terms, + TALER_MERCHANTDB_CoinRefundCallback rc, + void *rc_cls) +{ + 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_end + }; + struct GetRefundsContext grc = { + .rc = rc, + .rc_cls = rc_cls, + .pg = pg + }; + enum GNUNET_DB_QueryStatus qs; - /* 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; + /* no preflight check here, run in transaction by caller! */ + TALER_LOG_DEBUG ("Looking for refund %s + %s\n", + GNUNET_h2s (h_contract_terms), + TALER_B2S (merchant_pub)); + check_connection (pg); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "find_refunds_from_contract_terms_hash", + params, + &get_refunds_cb, + &grc); + if (0 >= qs) + return qs; + return grc.qs; +} - /* 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; - } +/** + * Obtain refund proofs associated with a refund operation on a + * coin. + * + * @param cls closure, typically a connection to the db + * @param merchant_pub public key of the merchant instance + * @param h_contract_terms hash code of the contract + * @param coin_pub public key of the coin + * @param rtransaction_id identificator of the refund + * @param[out] exchange_pub public key of the exchange affirming the refund + * @param[out] exchange_sig signature of the exchange affirming the refund + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +postgres_get_refund_proof ( + void *cls, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + uint64_t rtransaction_id, + struct TALER_ExchangePublicKeyP *exchange_pub, + struct TALER_ExchangeSignatureP *exchange_sig) +{ + struct PostgresClosure *pg = cls; + 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_auto_from_type (coin_pub), + GNUNET_PQ_query_param_uint64 (&rtransaction_id), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("exchange_sig", + exchange_sig), + GNUNET_PQ_result_spec_auto_from_type ("exchange_pub", + exchange_pub), + GNUNET_PQ_result_spec_end + }; - /* How much of the refund is still to be paid back? */ - if (0 > - TALER_amount_subtract (&remaining_refund, - ctx->refund, - &current_refund)) - { - GNUNET_break (0); - ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } + check_connection (pg); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "get_refund_proof", + params, + rs); +} - /* 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 (&current_refund, - &current_refund, - increment)) - { - GNUNET_break (0); - ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } +/** + * Store refund proofs associated with a refund operation on a + * coin. + * + * @param cls closure, typically a connection to the db + * @param merchant_pub public key of the merchant instance + * @param h_contract_terms hash code of the contract + * @param coin_pub public key of the coin + * @param rtransaction_id identificator of the refund + * @param exchange_pub public key of the exchange affirming the refund + * @param exchange_sig signature of the exchange affirming the refund + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +postgres_put_refund_proof ( + void *cls, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + uint64_t rtransaction_id, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const struct TALER_ExchangeSignatureP *exchange_sig) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&rtransaction_id), + 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_auto_from_type (exchange_sig), + GNUNET_PQ_query_param_auto_from_type (exchange_pub), + GNUNET_PQ_query_param_end + }; - /* 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; + TALER_LOG_DEBUG ("Inserting refund proof %s + %s\n", + GNUNET_h2s (h_contract_terms), + TALER_B2S (coin_pub)); + check_connection (pg); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_refund_proof", + params); +} - 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, - &current_refund)) - return; - } +/** + * Store information about wire fees charged by an exchange, + * including signature (so we have proof). + * + * @param cls closure + * @paramm exchange_pub public key of the exchange + * @param h_wire_method hash of wire method + * @param wire_fee wire fee charged + * @param closing_fee closing fee charged (irrelevant for us, + * but needed to check signature) + * @param start_date start of fee being used + * @param end_date end of fee being used + * @param exchange_sig signature of exchange over fee structure + * @return transaction status code + */ +static enum GNUNET_DB_QueryStatus +postgres_store_wire_fee_by_exchange ( + void *cls, + const struct + TALER_MasterPublicKeyP *exchange_pub, + const struct + GNUNET_HashCode *h_wire_method, + const struct TALER_Amount *wire_fee, + const struct TALER_Amount *closing_fee, + struct GNUNET_TIME_Absolute start_date, + struct GNUNET_TIME_Absolute end_date, + const struct + TALER_MasterSignatureP *exchange_sig) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (exchange_pub), + GNUNET_PQ_query_param_auto_from_type (h_wire_method), + TALER_PQ_query_param_amount (wire_fee), + TALER_PQ_query_param_amount (closing_fee), + GNUNET_PQ_query_param_absolute_time (&start_date), + GNUNET_PQ_query_param_absolute_time (&end_date), + GNUNET_PQ_query_param_auto_from_type (exchange_sig), + GNUNET_PQ_query_param_end + }; - /** - * 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; + /* no preflight check here, run in its own transaction by the caller */ + check_connection (pg); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Storing wire fee for %s starting at %s of %s\n", + TALER_B2S (exchange_pub), + GNUNET_STRINGS_absolute_time_to_string (start_date), + TALER_amount2s (wire_fee)); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_wire_fee", + params); } /** - * 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). + * Obtain information about wire fees charged by an exchange, + * including signature (so we have proof). * * @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!) + * @param exchange_pub public key of the exchange + * @param h_wire_method hash of wire method + * @param contract_date date of the contract to use for the lookup + * @param[out] wire_fee wire fee charged + * @param[out] closing_fee closing fee charged (irrelevant for us, + * but needed to check signature) + * @param[out] start_date start of fee being used + * @param[out] end_date end of fee being used + * @param[out] exchange_sig signature of exchange over fee structure + * @return transaction status code */ 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) +postgres_lookup_wire_fee (void *cls, + const struct TALER_MasterPublicKeyP *exchange_pub, + const struct GNUNET_HashCode *h_wire_method, + struct GNUNET_TIME_Absolute contract_date, + struct TALER_Amount *wire_fee, + struct TALER_Amount *closing_fee, + struct GNUNET_TIME_Absolute *start_date, + struct GNUNET_TIME_Absolute *end_date, + struct TALER_MasterSignatureP *exchange_sig) { 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_auto_from_type (exchange_pub), + GNUNET_PQ_query_param_auto_from_type (h_wire_method), + GNUNET_PQ_query_param_absolute_time (&contract_date), 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 + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", + wire_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee", + closing_fee), + GNUNET_PQ_result_spec_absolute_time ("start_date", + start_date), + GNUNET_PQ_result_spec_absolute_time ("end_date", + end_date), + GNUNET_PQ_result_spec_auto_from_type ("exchange_sig", + exchange_sig), + GNUNET_PQ_result_spec_end }; - 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; - } + check_connection (pg); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_wire_fee", + params, + rs); } @@ -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; diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -335,6 +335,40 @@ typedef void const struct TALER_Amount *refund_amount); +/** + * Results from trying to increase a refund. + */ +enum TALER_MERCHANTDB_RefundStatus +{ + + /** + * Refund amount exceeds original payment. + */ + TALER_MERCHANTDB_RS_TOO_HIGH = -3, + + /** + * Hard database failure. + */ + TALER_MERCHANTDB_RS_HARD_ERROR = -2, + + /** + * Soft database failure. + */ + TALER_MERCHANTDB_RS_SOFT_ERROR = -1, + + /** + * Order not found. + */ + TALER_MERCHANTDB_RS_NO_SUCH_ORDER = 0, + + /** + * Refund is now at or above the requested amount. + */ + TALER_MERCHANTDB_RS_SUCCESS = 1 + +}; + + /* **************** OLD: ******************** */ /** @@ -462,40 +496,6 @@ typedef void /** - * Results from trying to increase a refund. - */ -enum TALER_MERCHANTDB_RefundStatus -{ - - /** - * Refund amount exceeds original payment. - */ - TALER_MERCHANTDB_RS_TOO_HIGH = -3, - - /** - * Hard database failure. - */ - TALER_MERCHANTDB_RS_HARD_ERROR = -2, - - /** - * Soft database failure. - */ - TALER_MERCHANTDB_RS_SOFT_ERROR = -1, - - /** - * Order not found. - */ - TALER_MERCHANTDB_RS_NO_SUCH_ORDER = 0, - - /** - * Refund is now at or above the requested amount. - */ - TALER_MERCHANTDB_RS_SUCCESS = 1 - -}; - - -/** * Handle to interact with the database. * * Functions ending with "_TR" run their OWN transaction scope