summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-05-01 16:12:51 +0200
committerChristian Grothoff <christian@grothoff.org>2020-05-01 16:14:51 +0200
commitd5a57b88a7ccbfdfe49378410417d170eff2f14f (patch)
treed31e1df1c9b3ea465d04ada5478f5f63d753ab88
parent49217808de28559964ca3f2b42e50c3990436520 (diff)
downloadmerchant-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.c259
-rw-r--r--src/backenddb/plugin_merchantdb_postgres.c133
-rw-r--r--src/include/taler_merchantdb_plugin.h40
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 ******************** */