merchant

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

commit c3916068ea8ccf7c5c26119e08ebb701a2544816
parent 4fbf3a105e22c7cbb08abb91730c3e5ca703e9d9
Author: Marcello Stanisci <marcello.stanisci@inria.fr>
Date:   Sun, 16 Jul 2017 11:48:31 +0200

flagging proposal as paid (#5054)

Diffstat:
Msrc/backend/taler-merchant-httpd_pay.c | 62++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/backend/taler-merchant-httpd_track-transaction.c | 3++-
Msrc/backenddb/plugin_merchantdb_postgres.c | 69++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/include/taler_merchantdb_plugin.h | 51+++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/lib/test_merchant_api.c | 4+++-
5 files changed, 156 insertions(+), 33 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c @@ -1317,6 +1317,8 @@ handler_pay_json (struct MHD_Connection *connection, { int ret; enum GNUNET_DB_QueryStatus qs; + enum GNUNET_DB_QueryStatus qs_st; + enum GNUNET_DB_QueryStatus qs_mp; ret = parse_pay (connection, root, @@ -1404,34 +1406,62 @@ handler_pay_json (struct MHD_Connection *connection, "The time to pay for this contract has expired."); } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Storing transaction '%s'\n", - GNUNET_h2s (&pc->h_contract_terms)); for (unsigned int i=0;i<MAX_RETRIES;i++) { - qs = db->store_transaction (db->cls, - &pc->h_contract_terms, - &pc->mi->pubkey, - pc->chosen_exchange, - &pc->mi->h_wire, - pc->timestamp, - pc->refund_deadline, - &pc->amount); - if (GNUNET_DB_STATUS_SOFT_ERROR != qs) - break; + + if (GNUNET_OK != db->start (db->cls)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not start transaction to store successful payment\n"); + return TMH_RESPONSE_reply_internal_error (connection, + TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR, + "Merchant database error"); + } + + qs_st = db->store_transaction (db->cls, + &pc->h_contract_terms, + &pc->mi->pubkey, + pc->chosen_exchange, + &pc->mi->h_wire, + pc->timestamp, + pc->refund_deadline, + &pc->amount); + + qs_mp = db->mark_proposal_paid (db->cls, + &pc->h_contract_terms, + &pc->mi->pubkey); + + /* Only retry if SOFT error occurred. Exit in case of OK or HARD failure */ + if ( (GNUNET_DB_STATUS_SOFT_ERROR == qs_st) && + (GNUNET_DB_STATUS_SOFT_ERROR == qs_mp) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Rolling back db transaction\n"); + db->rollback (db->cls); + } + + break; } - if (0 > qs) + + /* Break if AT LEAST one error occurred */ + if (2 != (qs_st + qs_mp)) { + db->rollback (db->cls); /* Special report if retries insufficient */ - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs_st); + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs_mp); /* Always report on hard error as well to enable diagnostics */ - GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs_st); + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs_mp); + return TMH_RESPONSE_reply_internal_error (connection, TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR, "Merchant database error"); } } + db->commit (db->cls); + MHD_suspend_connection (connection); pc->suspended = GNUNET_YES; diff --git a/src/backend/taler-merchant-httpd_track-transaction.c b/src/backend/taler-merchant-httpd_track-transaction.c @@ -812,7 +812,8 @@ handle_track_transaction_timeout (void *cls) /** * Function called with information about a transaction. - * Its duty is to fill up the "context" for the whole operation. + * Responsible to fill up the "context" for the whole + * tracking operation. * * @param cls closure * @param transaction_id of the contract diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c @@ -87,6 +87,7 @@ postgres_initialize (void *cls) ",h_contract_terms BYTEA NOT NULL" ",timestamp INT8 NOT NULL" ",row_id BIGSERIAL UNIQUE" + ",paid BYTEA NOT NULL " ",PRIMARY KEY (order_id, merchant_pub)" ",UNIQUE (h_contract_terms, merchant_pub)" ");"), @@ -134,13 +135,13 @@ postgres_initialize (void *cls) ",PRIMARY KEY (h_contract_terms, coin_pub)" ");"), GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_proofs (" - " exchange_uri VARCHAR NOT NULL" - ",wtid BYTEA CHECK (LENGTH(wtid)=32)" - ",execution_time INT8 NOT NULL" - ",signkey_pub BYTEA NOT NULL CHECK (LENGTH(signkey_pub)=32)" - ",proof BYTEA NOT NULL" - ",PRIMARY KEY (wtid, exchange_uri)" - ");"), + " exchange_uri VARCHAR NOT NULL" + ",wtid BYTEA CHECK (LENGTH(wtid)=32)" + ",execution_time INT8 NOT NULL" + ",signkey_pub BYTEA NOT NULL CHECK (LENGTH(signkey_pub)=32)" + ",proof BYTEA NOT NULL" + ",PRIMARY KEY (wtid, exchange_uri)" + ");"), /* Note that h_contract_terms + coin_pub may actually be unknown to us, e.g. someone else deposits something for us at the exchange. Hence those cannot be foreign keys into deposits/transactions! */ @@ -239,11 +240,17 @@ postgres_initialize (void *cls) "(order_id" ",merchant_pub" ",timestamp" + ",paid" ",contract_terms" ",h_contract_terms)" " VALUES " - "($1, $2, $3, $4, $5)", - 4), + "($1, $2, $3, $4, $5, $6)", + 6), + GNUNET_PQ_make_prepare ("mark_proposal_paid", + "UPDATE merchant_contract_terms SET" + " paid=$1 WHERE h_contract_terms=$2" + " AND merchant_pub=$3", + 3), GNUNET_PQ_make_prepare ("insert_wire_fee", "INSERT INTO exchange_wire_fees" "(exchange_pub" @@ -471,7 +478,7 @@ check_connection (struct PostgresClosure *pg) * @param cls the `struct PostgresClosure` with the plugin-specific state * @return #GNUNET_OK on success */ -static int +int postgres_start (void *cls) { struct PostgresClosure *pg = cls; @@ -502,7 +509,7 @@ postgres_start (void *cls) * @param cls the `struct PostgresClosure` with the plugin-specific state * @return #GNUNET_OK on success */ -static void +void postgres_rollback (void *cls) { struct PostgresClosure *pg = cls; @@ -627,10 +634,13 @@ postgres_insert_contract_terms (void *cls, { struct PostgresClosure *pg = cls; struct GNUNET_HashCode h_contract_terms; + unsigned int no = GNUNET_NO; + struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_absolute_time (&timestamp), + GNUNET_PQ_query_param_auto_from_type (&no), TALER_PQ_query_param_json (contract_terms), GNUNET_PQ_query_param_auto_from_type (&h_contract_terms), GNUNET_PQ_query_param_end @@ -651,6 +661,39 @@ postgres_insert_contract_terms (void *cls, params); } +/** + * Mark contract terms as payed. Needed by /history as only payed + * contracts must be shown. NOTE: we can't get the list of (payed) + * contracts from the transactions table because it lacks contract_terms + * plain JSON. In facts, the protocol doesn't allow to store contract_terms + * in transactions table, as /pay handler doesn't receive this data + * (only /proposal does). + */ +enum GNUNET_DB_QueryStatus +postgres_mark_proposal_paid (void *cls, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_MerchantPublicKeyP *merchant_pub) +{ + unsigned int yes = GNUNET_YES; + struct PostgresClosure *pg = cls; + + TALER_LOG_DEBUG ("Marking proposal paid, h_contract_terms: '%s'," + " merchant_pub: '%s'\n", + GNUNET_h2s (h_contract_terms), + TALER_B2S (merchant_pub)); + + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (&yes), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_auto_from_type (merchant_pub), + GNUNET_PQ_query_param_end + }; + + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "mark_proposal_paid", + params); +} + /** * Insert transaction data into the database. @@ -2351,6 +2394,10 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) plugin->find_contract_terms_from_hash = &postgres_find_contract_terms_from_hash; plugin->get_refunds_from_contract_terms_hash = &postgres_get_refunds_from_contract_terms_hash; plugin->increase_refund_for_contract = postgres_increase_refund_for_contract; + plugin->mark_proposal_paid = postgres_mark_proposal_paid; + plugin->start = postgres_start; + plugin->commit = postgres_commit; + plugin->rollback = postgres_rollback; return plugin; } diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -196,10 +196,23 @@ struct TALER_MERCHANTDB_Plugin */ enum GNUNET_DB_QueryStatus (*insert_contract_terms) (void *cls, - const char *order_id, - const struct TALER_MerchantPublicKeyP *merchant_pub, - struct GNUNET_TIME_Absolute timestamp, - const json_t *contract_terms); + const char *order_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, + struct GNUNET_TIME_Absolute timestamp, + const json_t *contract_terms); + + /** + * Mark contract terms as payed. Needed by /history as only payed + * contracts must be shown. NOTE: we can't get the list of (payed) + * contracts from the transactions table because it lacks contract_terms + * plain JSON. In facts, the protocol doesn't allow to store contract_terms + * in transactions table, as /pay handler doesn't receive this data + * (only /proposal does). + */ + enum GNUNET_DB_QueryStatus + (*mark_proposal_paid) (void *cls, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_MerchantPublicKeyP *merchant_pub); /** * Retrieve proposal data given its order ID. @@ -581,6 +594,36 @@ struct TALER_MERCHANTDB_Plugin const struct GNUNET_HashCode *h_contract_terms, TALER_MERCHANTDB_RefundCallback rc, void *rc_cls); + + /** + * Roll back the current transaction of a database connection. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @return #GNUNET_OK on success + */ + void + (*rollback) (void *cls); + + + /** + * Start a transaction. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @return #GNUNET_OK on success + */ + int + (*start) (void *cls); + + + /** + * Commit the current transaction of a database connection. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @return transaction status code + */ + enum GNUNET_DB_QueryStatus + (*commit) (void *cls); + }; #endif diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c @@ -1396,7 +1396,9 @@ pay_cb (void *cls, "Unexpected response code %u to command %s\n", http_status, cmd->label); - json_dumpf (obj, stderr, 0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Response was %s\n", + json_dumps (obj, JSON_INDENT (2))); fail (is); return; }