diff options
author | Christian Grothoff <christian@grothoff.org> | 2020-05-01 16:12:51 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2020-05-01 16:14:51 +0200 |
commit | d5a57b88a7ccbfdfe49378410417d170eff2f14f (patch) | |
tree | d31e1df1c9b3ea465d04ada5478f5f63d753ab88 | |
parent | 49217808de28559964ca3f2b42e50c3990436520 (diff) | |
download | merchant-d5a57b88a7ccbfdfe49378410417d170eff2f14f.tar.gz merchant-d5a57b88a7ccbfdfe49378410417d170eff2f14f.tar.bz2 merchant-d5a57b88a7ccbfdfe49378410417d170eff2f14f.zip |
DB API for /abort
-rw-r--r-- | src/backend/taler-merchant-httpd_post-orders-ID-abort.c | 259 | ||||
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 133 | ||||
-rw-r--r-- | src/include/taler_merchantdb_plugin.h | 40 |
3 files changed, 286 insertions, 146 deletions
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c index 27a83812..e6e8e406 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c @@ -704,36 +704,66 @@ begin_transaction (struct AbortContext *ac) return; } - /* check payment was indeed incomplete */ - qs = TMH_db->lookup_paid_order (TMH_db->cls, - ac->hc->instance->settings.id, - &ac->h_contract_terms, - NULL); - if (0 < qs) + /* check payment was indeed incomplete + (now that we are in the transaction scope!) */ { - /* Payment is complete, refuse to abort. */ - TMH_db->rollback (TMH_db->cls); - resume_abort_with_error (ac, - MHD_HTTP_FORBIDDEN, - TALER_EC_ABORT_ABORT_REFUND_REFUSED_ABORTMENT_COMPLETE, - "Payment was complete, refusing to abort"); - return; - } - if (0 > qs) - { - TMH_db->rollback (TMH_db->cls); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + struct GNUNET_HashCode h_contract_terms; + bool paid; + + qs = TMH_db->lookup_order_status (TMH_db->cls, + ac->hc->instance->settings.id, + ac->hc->infix, + &h_contract_terms, + &paid); + switch (qs) { - begin_transaction (ac); + case GNUNET_DB_STATUS_SOFT_ERROR: + case GNUNET_DB_STATUS_HARD_ERROR: + /* Always report on hard error to enable diagnostics */ + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + TMH_db->rollback (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + begin_transaction (ac); + return; + } + /* Always report on hard error as well to enable diagnostics */ + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + resume_abort_with_error (ac, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_ABORT_DB_FETCH_TRANSACTION_ERROR, + "Merchant database error"); + return; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + TMH_db->rollback (TMH_db->cls); + resume_abort_with_error (ac, + MHD_HTTP_NOT_FOUND, + TALER_EC_ABORT_CONTRACT_NOT_FOUND, + "Could not find contract"); + return; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + if (paid) + { + /* Payment is complete, refuse to abort. */ + TMH_db->rollback (TMH_db->cls); + resume_abort_with_error (ac, + MHD_HTTP_FORBIDDEN, + TALER_EC_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE, + "Payment was complete, refusing to abort"); + return; + } + } + if (0 != + GNUNET_memcmp (&ac->h_contract_terms, + &h_contract_terms)) + { + GNUNET_break_op (0); + resume_abort_with_error (ac, + MHD_HTTP_BAD_REQUEST, + TALER_EC_ABORT_CONTRACT_HASH_MISSMATCH, + "Provided hash does not match order on file"); return; } - /* Always report on hard error as well to enable diagnostics */ - GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - resume_abort_with_error (ac, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_ABORT_DB_STORE_ABORT_ERROR, - "Merchant database error"); - return; } /* Mark all deposits we have in our database for the order as refunded. */ @@ -802,132 +832,77 @@ parse_abort (struct MHD_Connection *connection, struct TMH_HandlerContext *hc, struct AbortContext *ac) { + json_t *coins; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("coins", + &coins), + GNUNET_JSON_spec_fixed_auto ("h_contract", + &ac->h_contract_terms), + + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + hc->request_body, + spec); + if (GNUNET_YES != res) + { + GNUNET_break_op (0); + return res; + } + ac->coins_cnt = json_array_size (coins); + if (0 == ac->coins_cnt) { - json_t *coins; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("coins", - &coins), - GNUNET_JSON_spec_fixed_auto ("h_contract", - &ac->h_contract_terms), - - GNUNET_JSON_spec_end () - }; - enum GNUNET_GenericReturnValue res; - - res = TALER_MHD_parse_json_data (connection, - hc->request_body, - spec); - if (GNUNET_YES != res) - { - GNUNET_break_op (0); - return res; - } - ac->coins_cnt = json_array_size (coins); - if (0 == ac->coins_cnt) - { - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_ABORT_COINS_ARRAY_EMPTY, - "coins"); - } - /* note: 1 coin = 1 deposit confirmation expected */ - ac->pending = ac->coins_cnt; - ac->rd = GNUNET_new_array (ac->coins_cnt, - struct RefundDetails); - /* This loop populates the array 'rd' in 'ac' */ - { - unsigned int coins_index; - json_t *coin; - json_array_foreach (coins, coins_index, coin) - { - struct RefundDetails *rd = &ac->rd[coins_index]; - const char *exchange_url; - struct GNUNET_JSON_Specification ispec[] = { - TALER_JSON_spec_amount ("contribution", - &rd->amount_with_fee), - TALER_JSON_spec_amount ("refund_fee", - &rd->refund_fee), - GNUNET_JSON_spec_string ("exchange_url", - &exchange_url), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &rd->coin_pub), - GNUNET_JSON_spec_end () - }; - - res = TALER_MHD_parse_json_data (connection, - coin, - ispec); - if (GNUNET_YES != res) - { - GNUNET_JSON_parse_free (spec); - GNUNET_break_op (0); - return res; - } - rd->exchange_url = GNUNET_strdup (exchange_url); - rd->index = coins_index; - rd->ac = ac; - } - } GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_ABORT_COINS_ARRAY_EMPTY, + "coins"); } - - /* Check request against contract on file */ + /* note: 1 coin = 1 deposit confirmation expected */ + ac->pending = ac->coins_cnt; + ac->rd = GNUNET_new_array (ac->coins_cnt, + struct RefundDetails); + /* This loop populates the array 'rd' in 'ac' */ { - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_HashCode h_contract_terms; - - qs = TMH_db->lookup_contract_terms_hash (TMH_db->cls, - hc->instance->settings.id, - hc->infix, - &h_contract_terms); - if (0 > qs) - { - /* single, read-only SQL statements should never cause - serialization problems */ - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); - /* Always report on hard error to enable diagnostics */ - GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_ABORT_DB_FETCH_ABORT_ERROR, - "Failed to obtain contract terms from DB")) - ? GNUNET_NO - : GNUNET_SYSERR; - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_ABORT_PROPOSAL_NOT_FOUND, - "Order not found")) - ? GNUNET_NO - : GNUNET_SYSERR; - } - - /* check client provided the right hash and is thus authorized to request aborting */ + unsigned int coins_index; + json_t *coin; + json_array_foreach (coins, coins_index, coin) { - if (0 != - GNUNET_memcmp (&ac->h_contract_terms, - &h_contract_terms)) + struct RefundDetails *rd = &ac->rd[coins_index]; + const char *exchange_url; + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_amount ("contribution", + &rd->amount_with_fee), + TALER_JSON_spec_amount ("refund_fee", + &rd->refund_fee), + GNUNET_JSON_spec_string ("exchange_url", + &exchange_url), + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &rd->coin_pub), + GNUNET_JSON_spec_end () + }; + + res = TALER_MHD_parse_json_data (connection, + coin, + ispec); + if (GNUNET_YES != res) { + GNUNET_JSON_parse_free (spec); GNUNET_break_op (0); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_ABORT_CONTRACT_HASH_MISSMATCH, - "Provided hash does not match order on file")) - ? GNUNET_NO - : GNUNET_SYSERR; + return res; } + rd->exchange_url = GNUNET_strdup (exchange_url); + rd->index = coins_index; + rd->ac = ac; } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Handling /abort for order `%s' with contract hash `%s'\n", - ac->hc->infix, - GNUNET_h2s (&ac->h_contract_terms)); } + GNUNET_JSON_parse_free (spec); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handling /abort for order `%s' with contract hash `%s'\n", + ac->hc->infix, + GNUNET_h2s (&ac->h_contract_terms)); return GNUNET_OK; } diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index 46929c0a..14fb9b28 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -1095,7 +1095,7 @@ postgres_lookup_order (void *cls, instance_id); check_connection (pg); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "find_order", + "lookup_order", params, rs); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) @@ -1551,7 +1551,7 @@ lookup_deposits_cb (void *cls, struct TALER_Amount deposit_fee; struct TALER_Amount refund_fee; struct TALER_Amount wire_fee; - const char *exchange_url; + char *exchange_url; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_string ("exchange_url", &exchange_url), @@ -1585,7 +1585,7 @@ lookup_deposits_cb (void *cls, &deposit_fee, &refund_fee, &wire_fee); - GNUNET_PQ_cleanup_result (rs); /* technically useless here */ + GNUNET_PQ_cleanup_result (rs); } } @@ -1897,6 +1897,88 @@ postgres_mark_contract_paid (void *cls, } +/** + * Function called during aborts to refund a coin. Marks the + * respective coin as refunded. + * + * @param cls closure + * @param instance_id instance to refund payment for + * @param h_contract_terms hash of the contract to refund coin for + * @param coin_pub public key of the coin to refund (fully) + * @param reason text justifying the refund + * @return transaction status + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a coin_pub is unknown to us; + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid, + * regardless of whether it actually increased the refund + */ +static enum GNUNET_DB_QueryStatus +postgres_refund_coin (void *cls, + const char *instance_id, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const char *reason) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + 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), + GNUNET_PQ_query_param_end + }; + + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "refund_coin", + params); +} + + +/** + * Retrieve contract terms given its @a order_id + * + * @param cls closure + * @param instance_id instance's identifier + * @param order_id order to lookup contract for + * @param[out] h_contract_terms set to the hash of the contract. + * @param[out] paid set to the payment status of the contract + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +postgres_lookup_order_status (void *cls, + const char *instance_id, + const char *order_id, + struct GNUNET_HashCode *h_contract_terms, + bool *paid) +{ + struct PostgresClosure *pg = cls; + uint8_t paid8; + 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 GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", + h_contract_terms), + GNUNET_PQ_result_spec_auto_from_type ("paid", + &paid8), + GNUNET_PQ_result_spec_end + }; + + check_connection (pg); + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_order_status", + params, + rs); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + *paid = (0 != paid8); + else + *paid = false; /* just to be safe(r) */ + return qs; +} + + /* ********************* OLD API ************************** */ /** @@ -5404,6 +5486,48 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) " FROM merchant_instances" " WHERE merchant_id=$1)", 3), + /* for postgres_refund_coin() */ + GNUNET_PQ_make_prepare ("refund_coin", + "INSERT INTO merchant_refunds" + "(order_serial" + ",rtransaction_id" + ",coin_pub" + ",reason" + ",refund_amount_val" + ",refund_amount_frac" + ") " + "SELECT " + " order_serial" + ",0" /* rtransaction_id always 0 for /abort */ + ",coin_pub" + ",$4" + ",amount_with_fee_val" + ",amount_with_fee_frac" + " FROM merchant_deposits" + " WHERE coin_pub=$3" + " AND order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " WHERE h_contract_terms=$2" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1))", + 4), + + /* for postgres_lookup_order_status() */ + GNUNET_PQ_make_prepare ("lookup_order_status", + "SELECT" + " h_contract_terms" + ",paid" + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND order_id=$2", + 2), + /* OLD API: */ #if 0 @@ -5800,7 +5924,8 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) plugin->insert_deposit = &postgres_insert_deposit; plugin->lookup_refunds = &postgres_lookup_refunds; 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->find_contract_terms_from_hash = &postgres_find_contract_terms_from_hash; diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index f995d98f..bca10715 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -997,6 +997,46 @@ struct TALER_MERCHANTDB_Plugin const char *session_id); + /** + * Function called during aborts to refund a coin. Marks the + * respective coin as refunded. + * + * @param cls closure + * @param instance_id instance to refund payment for + * @param h_contract_terms hash of the contract to refund coin for + * @param coin_pub public key of the coin to refund (fully) + * @param reason text justifying the refund + * @return transaction status + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a coin_pub is unknown to us; + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid, + * regardless of whether it actually increased the refund + */ + enum GNUNET_DB_QueryStatus + (*refund_coin)(void *cls, + const char *instance_id, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const char *reason); + + + /** + * Retrieve contract terms given its @a order_id + * + * @param cls closure + * @param instance_id instance's identifier + * @param order_id order to lookup contract for + * @param[out] h_contract_terms set to the hash of the contract. + * @param[out] paid set to the payment status of the contract + * @return transaction status + */ + enum GNUNET_DB_QueryStatus + (*lookup_order_status)(void *cls, + const char *instance_id, + const char *order_id, + struct GNUNET_HashCode *h_contract_terms, + bool *paid); + + /* ****************** OLD API ******************** */ |