diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/taler-merchant-httpd_pay.c | 236 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_track.c | 2 | ||||
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 833 | ||||
-rw-r--r-- | src/backenddb/test_merchantdb.c | 8 | ||||
-rw-r--r-- | src/include/taler_merchant_service.h | 2 | ||||
-rw-r--r-- | src/include/taler_merchantdb_plugin.h | 198 | ||||
-rw-r--r-- | src/lib/merchant_api_track.c | 8 |
7 files changed, 1078 insertions, 209 deletions
diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c index 8a32732d..50cbe79f 100644 --- a/src/backend/taler-merchant-httpd_pay.c +++ b/src/backend/taler-merchant-httpd_pay.c @@ -48,7 +48,7 @@ struct PayContext; /** * Information kept during a /pay request for each coin. */ -struct MERCHANT_DepositConfirmation +struct DepositConfirmation { /** @@ -68,15 +68,15 @@ struct MERCHANT_DepositConfirmation struct TALER_DenominationPublicKey denom; /** - * Amount "f" that this coin contributes to the overall payment. + * Amount this coin contributes to the total purchase price. * This amount includes the deposit fee. */ - struct TALER_Amount percoin_amount; + struct TALER_Amount amount_with_fee; /** - * Amount this coin contributes to the total purchase price. + * Fee charged by the exchange for the deposit operation of this coin. */ - struct TALER_Amount amount_without_fee; + struct TALER_Amount deposit_fee; /** * Public key of the coin. @@ -99,6 +99,11 @@ struct MERCHANT_DepositConfirmation */ unsigned int index; + /** + * #GNUNET_YES if we found this coin in the database. + */ + int found_in_db; + }; @@ -117,7 +122,7 @@ struct PayContext /** * Array with @e coins_cnt coins we are despositing. */ - struct MERCHANT_DepositConfirmation *dc; + struct DepositConfirmation *dc; /** * MHD connection to return to @@ -220,6 +225,14 @@ struct PayContext */ struct GNUNET_SCHEDULER_Task *timeout_task; + /** + * #GNUNET_NO if the transaction is not in our database, + * #GNUNET_YES if the transaction is known to our database, + * #GNUNET_SYSERR if the transaction ID is used for a different + * transaction in our database. + */ + int transaction_exits; + }; @@ -264,8 +277,11 @@ denomination_to_string_alloc (struct TALER_DenominationPublicKey *dk) char *buf; char *buf2; size_t buf_size; - buf_size = GNUNET_CRYPTO_rsa_public_key_encode (dk->rsa_public_key, &buf); - buf2 = GNUNET_STRINGS_data_to_string_alloc (buf, buf_size); + + buf_size = GNUNET_CRYPTO_rsa_public_key_encode (dk->rsa_public_key, + &buf); + buf2 = GNUNET_STRINGS_data_to_string_alloc (buf, + buf_size); GNUNET_free (buf); return buf2; } @@ -285,7 +301,7 @@ abort_deposit (struct PayContext *pc) "Aborting pending /deposit operations\n"); for (i=0;i<pc->coins_cnt;i++) { - struct MERCHANT_DepositConfirmation *dci = &pc->dc[i]; + struct DepositConfirmation *dci = &pc->dc[i]; if (NULL != dci->dh) { @@ -299,7 +315,7 @@ abort_deposit (struct PayContext *pc) /** * Callback to handle a deposit permission's response. * - * @param cls a `struct MERCHANT_DepositConfirmation` (i.e. a pointer + * @param cls a `struct DepositConfirmation` (i.e. a pointer * into the global array of confirmations and an index for this call * in that array). That way, the last executed callback can detect * that no other confirmations are on the way, and can pack a response @@ -316,7 +332,7 @@ deposit_cb (void *cls, unsigned int http_status, const json_t *proof) { - struct MERCHANT_DepositConfirmation *dc = cls; + struct DepositConfirmation *dc = cls; struct PayContext *pc = dc->pc; dc->dh = NULL; @@ -358,14 +374,11 @@ deposit_cb (void *cls, } /* store result to DB */ if (GNUNET_OK != - db->store_payment (db->cls, - &pc->h_contract, - &h_wire, + db->store_deposit (db->cls, pc->transaction_id, - pc->timestamp, - pc->refund_deadline, - &dc->amount_without_fee, &dc->coin_pub, + &dc->amount_with_fee, + &dc->deposit_fee, proof)) { GNUNET_break (0); @@ -377,6 +390,7 @@ deposit_cb (void *cls, TMH_RESPONSE_make_internal_error ("Merchant database error")); return; } + if (0 != pc->pending) return; /* still more to do */ resume_pay_with_response (pc, @@ -407,7 +421,7 @@ pay_context_cleanup (struct TM_HandlerContext *hc) TMH_PARSE_post_cleanup_callback (pc->json_parse_context); for (i=0;i<pc->coins_cnt;i++) { - struct MERCHANT_DepositConfirmation *dc = &pc->dc[i]; + struct DepositConfirmation *dc = &pc->dc[i]; if (NULL != dc->dh) { @@ -484,7 +498,7 @@ process_pay_with_exchange (void *cls, /* Total up the fees and the value of the deposited coins! */ for (i=0;i<pc->coins_cnt;i++) { - struct MERCHANT_DepositConfirmation *dc = &pc->dc[i]; + struct DepositConfirmation *dc = &pc->dc[i]; const struct TALER_EXCHANGE_DenomPublicKey *denom_details; denom_details = TALER_EXCHANGE_get_denomination_key (keys, @@ -492,6 +506,7 @@ process_pay_with_exchange (void *cls, if (NULL == denom_details) { char *denom_enc; + GNUNET_break_op (0); resume_pay_with_response (pc, MHD_HTTP_BAD_REQUEST, @@ -500,7 +515,9 @@ process_pay_with_exchange (void *cls, "denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key), "exchange_keys", TALER_EXCHANGE_get_keys_raw (mh))); denom_enc = denomination_to_string_alloc (&dc->denom); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "unknown denom to exchange: %s\n", denom_enc); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "unknown denom to exchange: %s\n", + denom_enc); GNUNET_free (denom_enc); return; } @@ -510,6 +527,7 @@ process_pay_with_exchange (void *cls, exchange_trusted)) { char *denom_enc; + GNUNET_break_op (0); resume_pay_with_response (pc, MHD_HTTP_BAD_REQUEST, @@ -517,14 +535,17 @@ process_pay_with_exchange (void *cls, "error", "invalid denomination", "denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key))); denom_enc = denomination_to_string_alloc (&dc->denom); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "client offered invalid denomination: %s\n", denom_enc); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Client offered invalid denomination: %s\n", + denom_enc); GNUNET_free (denom_enc); return; } + dc->deposit_fee = denom_details->fee_deposit; if (0 == i) { acc_fee = denom_details->fee_deposit; - acc_amount = dc->percoin_amount; + acc_amount = dc->amount_with_fee; } else { @@ -534,7 +555,7 @@ process_pay_with_exchange (void *cls, &acc_fee)) || (GNUNET_OK != TALER_amount_add (&acc_amount, - &dc->percoin_amount, + &dc->amount_with_fee, &acc_amount)) ) { GNUNET_break_op (0); @@ -545,10 +566,9 @@ process_pay_with_exchange (void *cls, return; } } - if (GNUNET_SYSERR == - TALER_amount_subtract (&dc->amount_without_fee, - &dc->percoin_amount, - &denom_details->fee_deposit)) + if (1 == + TALER_amount_cmp (&dc->deposit_fee, + &dc->amount_with_fee)) { GNUNET_break_op (0); /* fee higher than residual coin value, makes no sense. */ @@ -556,7 +576,7 @@ process_pay_with_exchange (void *cls, MHD_HTTP_BAD_REQUEST, TMH_RESPONSE_make_json_pack ("{s:s, s:o, s:o}", "hint", "fee higher than coin value", - "f", TALER_JSON_from_amount (&dc->percoin_amount), + "f" /* FIXME */, TALER_JSON_from_amount (&dc->amount_with_fee), "fee_deposit", TALER_JSON_from_amount (&denom_details->fee_deposit))); return; } @@ -617,12 +637,13 @@ process_pay_with_exchange (void *cls, /* Initiate /deposit operation for all coins */ for (i=0;i<pc->coins_cnt;i++) { - struct MERCHANT_DepositConfirmation *dc = &pc->dc[i]; + struct DepositConfirmation *dc = &pc->dc[i]; + if (GNUNET_YES == dc->found_in_db) + continue; GNUNET_assert (NULL != j_wire); - dc->dh = TALER_EXCHANGE_deposit (mh, - &dc->percoin_amount, + &dc->amount_with_fee, pc->wire_transfer_deadline, j_wire, &pc->h_contract, @@ -680,6 +701,92 @@ handle_pay_timeout (void *cls) /** + * Function called with information about a coin that was deposited. + * + * @param cls closure + * @param transaction_id of the contract + * @param coin_pub public key of the coin + * @param amount_with_fee amount the exchange will deposit for this coin + * @param deposit_fee fee the exchange will charge for this coin + * @param exchange_proof proof from exchange that coin was accepted + */ +static void +check_coin_paid (void *cls, + uint64_t transaction_id, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *deposit_fee, + const json_t *exchange_proof) +{ + struct PayContext *pc = cls; + unsigned int i; + + if (pc->transaction_id != transaction_id) + { + GNUNET_break (0); + return; + } + for (i=0;i<pc->coins_cnt;i++) + { + struct DepositConfirmation *dc = &pc->dc[i]; + + if ( (0 != memcmp (coin_pub, + &dc->coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP))) || + (0 != TALER_amount_cmp (amount_with_fee, + &dc->amount_with_fee)) ) + continue; + dc->found_in_db = GNUNET_YES; + pc->pending--; + } +} + + +/** + * Check if the existing transaction matches our transaction. + * Update `transaction_exits` accordingly. + * + * @param cls closure with the `struct PayContext` + * @param transaction_id of the contract + * @param h_contract hash of the contract + * @param h_xwire hash of our wire details + * @param timestamp time of the confirmation + * @param refund refund deadline + * @param total_amount total amount we receive for the contract after fees + */ +static void +check_transaction_exists (void *cls, + uint64_t transaction_id, + const struct GNUNET_HashCode *h_contract, + const struct GNUNET_HashCode *h_xwire, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund, + const struct TALER_Amount *total_amount) +{ + struct PayContext *pc = cls; + + if ( (0 == memcmp (h_contract, + &pc->h_contract, + sizeof (struct GNUNET_HashCode))) && + (0 == memcmp (h_xwire, + &h_wire, + sizeof (struct GNUNET_HashCode))) && + (timestamp.abs_value_us == pc->timestamp.abs_value_us) && + (refund.abs_value_us == pc->refund_deadline.abs_value_us) && + (0 == TALER_amount_cmp (total_amount, + &pc->amount) ) ) + { + pc->transaction_exits = GNUNET_YES; + } + else + { + GNUNET_break_op (0); + pc->transaction_exits = GNUNET_SYSERR; + } +} + + +/** * Accomplish this payment. * * @param rh context of the handler @@ -855,14 +962,14 @@ MH_handler_pay (struct TMH_RequestHandler *rh, } /* note: 1 coin = 1 deposit confirmation expected */ pc->dc = GNUNET_new_array (pc->coins_cnt, - struct MERCHANT_DepositConfirmation); + struct DepositConfirmation); json_array_foreach (coins, coins_index, coin) { - struct MERCHANT_DepositConfirmation *dc = &pc->dc[coins_index]; + struct DepositConfirmation *dc = &pc->dc[coins_index]; struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_denomination_public_key ("denom_pub", &dc->denom), - TALER_JSON_spec_amount ("f", &dc->percoin_amount), + TALER_JSON_spec_amount ("f" /* FIXME */, &dc->amount_with_fee), GNUNET_JSON_spec_fixed_auto ("coin_pub", &dc->coin_pub), TALER_JSON_spec_denomination_signature ("ub_sig", &dc->ub_sig), GNUNET_JSON_spec_fixed_auto ("coin_sig", &dc->coin_sig), @@ -881,7 +988,8 @@ MH_handler_pay (struct TMH_RequestHandler *rh, { char *s; - s = TALER_amount_to_string (&dc->percoin_amount); + + s = TALER_amount_to_string (&dc->amount_with_fee); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Coin #%i has f %s\n", coins_index, @@ -893,11 +1001,20 @@ MH_handler_pay (struct TMH_RequestHandler *rh, dc->pc = pc; } } + pc->pending = pc->coins_cnt; - /* Check if this payment attempt has already taken place */ - if (GNUNET_OK == - db->check_payment (db->cls, - pc->transaction_id)) + /* Check if this payment attempt has already succeeded */ + if (GNUNET_SYSERR == + db->find_payments_by_id (db->cls, + pc->transaction_id, + &check_coin_paid, + pc)) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_error (connection, + "Merchant database error"); + } + if (0 == pc->pending) { struct MHD_Response *resp; int ret; @@ -914,8 +1031,42 @@ MH_handler_pay (struct TMH_RequestHandler *rh, return ret; } + /* Check if transaction is already known, if not store it. */ + if (GNUNET_SYSERR == + db->find_transaction_by_id (db->cls, + pc->transaction_id, + &check_transaction_exists, + pc)) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_error (connection, + "Merchant database error"); + } + if (GNUNET_SYSERR == pc->transaction_exits) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_error (connection, + "Transaction ID reused with different transaction details"); + } + if (GNUNET_NO == pc->transaction_exits) + { + if (GNUNET_OK != + db->store_transaction (db->cls, + pc->transaction_id, + &pc->h_contract, + &h_wire, + pc->timestamp, + pc->refund_deadline, + &pc->amount)) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_error (connection, + "Merchant database error"); + } + } + + MHD_suspend_connection (connection); /* Find the responsible exchange, this may take a while... */ - pc->pending = pc->coins_cnt; pc->fo = TMH_EXCHANGES_find_exchange (pc->chosen_exchange, &process_pay_with_exchange, pc); @@ -926,8 +1077,9 @@ MH_handler_pay (struct TMH_RequestHandler *rh, #resume_pay_with_response(). */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Suspending /pay handling while working with the exchange\n"); - MHD_suspend_connection (connection); - pc->timeout_task = GNUNET_SCHEDULER_add_delayed (PAY_TIMEOUT, handle_pay_timeout, pc); + pc->timeout_task = GNUNET_SCHEDULER_add_delayed (PAY_TIMEOUT, + &handle_pay_timeout, + pc); json_decref (root); return MHD_YES; } diff --git a/src/backend/taler-merchant-httpd_track.c b/src/backend/taler-merchant-httpd_track.c index 58743231..49e4056e 100644 --- a/src/backend/taler-merchant-httpd_track.c +++ b/src/backend/taler-merchant-httpd_track.c @@ -107,7 +107,7 @@ wire_deposit_cb (void *cls, const struct TALER_WireDepositDetails *details) { struct DepositTrackContext *rctx = cls; - unsigned int i; + // unsigned int i; rctx->wdh = NULL; if (NULL == total_amount) diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index 4dadb4fd..46435790 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -17,6 +17,7 @@ * @file merchant/plugin_merchantdb_postgres.c * @brief database helper functions for postgres used by the merchant * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff */ #include "platform.h" #include <gnunet/gnunet_util_lib.h> @@ -41,13 +42,87 @@ struct PostgresClosure }; -#define PQSQL_strerror(kind, cmd, res) \ - GNUNET_log_from (kind, "merchantdb-postgres", \ - "SQL %s failed at %s:%u with error: %s", \ +/** + * Log error from PostGres. + * + * @param kind log level to use + * @param cmd command that failed + * @param res postgres result object with error details + */ +#define PQSQL_strerror(kind, cmd, res) \ + GNUNET_log_from (kind, "merchantdb-postgres", \ + "SQL %s failed at %s:%u with error: %s", \ cmd, __FILE__, __LINE__, PQresultErrorMessage (res)); /** + * Macro to run @a s SQL statement using #GNUNET_POSTGRES_exec() + * and return with #GNUNET_SYSERR if the operation fails. + * + * @param pg context for running the statement + * @param s SQL statement to run + */ +#define PG_EXEC(pg,s) do { \ + if (GNUNET_OK != GNUNET_POSTGRES_exec (pg->conn, s)) \ + { \ + GNUNET_break (0); \ + return GNUNET_SYSERR; \ + } \ + } while (0) + + +/** + * Macro to run @a s SQL statement using #GNUNET_POSTGRES_exec(). + * Ignore errors, they happen. + * + * @param pg context for running the statement + * @param s SQL statement to run + */ +#define PG_EXEC_INDEX(pg,s) do { \ + PGresult *result = PQexec (pg->conn, s); \ + PQclear (result); \ + } while (0) + + +/** + * Prepare an SQL statement and log errors on failure. + * + * @param pg context for running the preparation + * @param n name of the prepared statement + * @param s SQL statement to run + * @param c number of arguments @a s expects + */ +#define PG_PREPARE(pg,n,s,c) do { \ + ExecStatusType status; \ + PGresult *res = PQprepare (pg->conn, n, s, c, NULL); \ + if ( (NULL == res) || \ + (PGRES_COMMAND_OK != (status = PQresultStatus (res))) ) \ + { \ + if (NULL != res) \ + { \ + PQSQL_strerror (GNUNET_ERROR_TYPE_ERROR, "PQprepare", res); \ + PQclear (res); \ + } \ + return GNUNET_SYSERR; \ + } \ + PQclear (res); \ + } while (0) + + +/** + * Log a really unexpected PQ error. + * + * @param result PQ result object of the PQ operation that failed + */ +#define BREAK_DB_ERR(result) do { \ + GNUNET_break (0); \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \ + "Database failure: %s\n", \ + PQresultErrorMessage (result)); \ + } while (0) + + +/** * Drop merchant tables * * @param cls closure our `struct Plugin` @@ -57,12 +132,11 @@ static int postgres_drop_tables (void *cls) { struct PostgresClosure *pg = cls; - int ret; - ret = GNUNET_POSTGRES_exec (pg->conn, - "DROP TABLE payments;"); - if (GNUNET_OK != ret) - return ret; + PG_EXEC_INDEX (pg, "DROP TABLE merchant_transfers;"); + PG_EXEC_INDEX (pg, "DROP TABLE merchant_deposits;"); + PG_EXEC_INDEX (pg, "DROP TABLE merchant_transactions;"); + PG_EXEC_INDEX (pg, "DROP TABLE merchant_proofs;"); return GNUNET_OK; } @@ -77,200 +151,677 @@ static int postgres_initialize (void *cls) { struct PostgresClosure *pg = cls; - PGresult *res; - ExecStatusType status; + + /* Setup tables */ + PG_EXEC (pg, + "CREATE TABLE IF NOT EXISTS merchant_transactions (" + " transaction_id INT8 PRIMARY KEY" + ",h_contract BYTEA NOT NULL CHECK (LENGTH(h_contract)=64)" + ",h_wire BYTEA NOT NULL CHECK (LENGTH(h_wire)=64)" + ",timestamp INT8 NOT NULL" + ",refund_deadline INT8 NOT NULL" + ",total_amount_val INT8 NOT NULL" + ",total_amount_frac INT4 NOT NULL" + ",total_amount_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") NOT NULL" + ");"); + PG_EXEC (pg, + "CREATE TABLE IF NOT EXISTS merchant_deposits (" + " transaction_id INT8 REFERENCES merchant_transactions (transaction_id)" + ",coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)" + ",amount_with_fee_val INT8 NOT NULL" + ",amount_with_fee_frac INT4 NOT NULL" + ",amount_with_fee_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") NOT NULL" + ",deposit_fee_val INT8 NOT NULL" + ",deposit_fee_frac INT4 NOT NULL" + ",deposit_fee_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") NOT NULL" + ",exchange_proof BYTEA NOT NULL" + ",PRIMARY KEY (transaction_id, coin_pub)" + ");"); + PG_EXEC (pg, + "CREATE TABLE IF NOT EXISTS merchant_proofs (" + " wtid BYTEA PRIMARY KEY CHECK (LENGTH(wtid)=32)" + ",proof BYTEA NOT NULL);"); + /* Note that transaction_id + 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! */ + PG_EXEC (pg, + "CREATE TABLE IF NOT EXISTS merchant_transfers (" + " transaction_id INT8" + ",coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)" + ",wtid BYTEA REFERENCES merchant_proofs (wtid)" + ",PRIMARY KEY (transaction_id, coin_pub)" + ");"); + PG_EXEC_INDEX (pg, + "CREATE INDEX IF NOT EXISTS transfers_by_coin " + " ON transfers (transaction_id, coin_pub)"); + PG_EXEC_INDEX (pg, + "CREATE INDEX IF NOT EXISTS transfers_by_wtid " + " ON transfers (wtid)"); + + /* Setup prepared "INSERT" statements */ + PG_PREPARE (pg, + "insert_transaction", + "INSERT INTO merchant_transactions" + "(transaction_id" + ",h_contract" + ",h_wire" + ",timestamp" + ",refund_deadline" + ",total_amount_val" + ",total_amount_frac" + ",total_amount_curr" + ") VALUES " + "($1, $2, $3, $4, $5, $6, $7, $8)", + 8); + PG_PREPARE (pg, + "insert_deposit", + "INSERT INTO merchant_deposits" + "(transaction_id" + ",coin_pub" + ",amount_with_fee_val" + ",amount_with_fee_frac" + ",amount_with_fee_curr" + ",deposit_fee_val" + ",deposit_fee_frac" + ",deposit_fee_curr" + ",exchange_proof) VALUES " + "($1, $2, $3, $4, $5, $6, $7, $8, $9)", + 9); + PG_PREPARE (pg, + "insert_transfer", + "INSERT INTO merchant_transfers" + "(transaction_id" + ",coin_pub" + ",wtid) VALUES " + "($1, $2, $3)", + 3); + PG_PREPARE (pg, + "insert_proof", + "INSERT INTO merchant_proofs" + "(wtid" + ",proof) VALUES " + "($1, $2)", + 2); + + /* Setup prepared "SELECT" statements */ + PG_PREPARE (pg, + "find_transaction", + "SELECT" + " h_contract" + ",h_wire" + ",timestamp" + ",refund_deadline" + ",total_amount_val" + ",total_amount_frac" + ",total_amount_curr" + " FROM merchant_transactions" + " WHERE transaction_id=$1", + 1); + PG_PREPARE (pg, + "find_deposits", + "SELECT" + " coin_pub" + ",amount_with_fee_val" + ",amount_with_fee_frac" + ",amount_with_fee_curr" + ",deposit_fee_val" + ",deposit_fee_frac" + ",deposit_fee_curr" + ",exchange_proof" + " FROM merchant_deposits" + " WHERE transaction_id=$1", + 1); + PG_PREPARE (pg, + "find_transfers_by_transaction_id", + "SELECT" + " coin_pub" + ",wtid" + ",merchant_proofs.proof" + " FROM merchant_transfers" + " JOIN merchant_proofs USING (wtid)" + " WHERE transaction_id=$1", + 1); + PG_PREPARE (pg, + "find_deposits_by_wtid", + "SELECT" + " merchant_transfers.transaction_id" + ",merchant_transfers.coin_pub" + ",merchant_deposits.amount_with_fee_val" + ",merchant_deposits.amount_with_fee_frac" + ",merchant_deposits.amount_with_fee_curr" + ",merchant_deposits.deposit_fee_val" + ",merchant_deposits.deposit_fee_frac" + ",merchant_deposits.deposit_fee_curr" + ",merchant_deposits.exchange_proof" + " FROM merchant_transfers" + " JOIN merchant_deposits" + " ON (merchant_deposits.transaction_id = merchant_transfers.transaction_id" + " AND" + " merchant_deposits.coin_pub = merchant_transfers.coin_pub)" + " WHERE wtid=$1", + 1); + return GNUNET_OK; +} + + +/** + * Insert transaction data into the database. + * + * @param cls closure + * @param transaction_id of the contract + * @param h_contract hash of the contract + * @param h_wire hash of our wire details + * @param timestamp time of the confirmation + * @param refund refund deadline + * @param total_amount total amount we receive for the contract after fees + * @return #GNUNET_OK on success, #GNUNET_SYSERR upon error + */ +static int +postgres_store_transaction (void *cls, + uint64_t transaction_id, + const struct GNUNET_HashCode *h_contract, + const struct GNUNET_HashCode *h_wire, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund, + const struct TALER_Amount *total_amount) +{ + struct PostgresClosure *pg = cls; + PGresult *result; int ret; - ret = GNUNET_POSTGRES_exec (pg->conn, - "CREATE TABLE IF NOT EXISTS payments (" - "h_contract BYTEA NOT NULL," - "h_wire BYTEA NOT NULL," - "transaction_id INT8," /*WARNING: this column used to be primary key, but that wrong since multiple coins belong to the same id*/ - "timestamp INT8 NOT NULL," - "refund_deadline INT8 NOT NULL," - "amount_without_fee_val INT8 NOT NULL," - "amount_without_fee_frac INT4 NOT NULL," - "amount_without_fee_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") NOT NULL," - "coin_pub BYTEA NOT NULL," - "exchange_proof BYTEA NOT NULL);"); - if (GNUNET_OK != ret) - return ret; - if ( (NULL == (res = PQprepare (pg->conn, - "insert_payment", - "INSERT INTO payments" - "(h_contract" - ",h_wire" - ",transaction_id" - ",timestamp" - ",refund_deadline" - ",amount_without_fee_val" - ",amount_without_fee_frac" - ",amount_without_fee_curr" - ",coin_pub" - ",exchange_proof) VALUES " - "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", - 10, NULL))) || - (PGRES_COMMAND_OK != (status = PQresultStatus(res))) ) + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&transaction_id), + GNUNET_PQ_query_param_auto_from_type (h_contract), + GNUNET_PQ_query_param_auto_from_type (h_wire), + GNUNET_PQ_query_param_absolute_time (×tamp), + GNUNET_PQ_query_param_absolute_time (&refund), + TALER_PQ_query_param_amount (total_amount), + GNUNET_PQ_query_param_end + }; + + result = GNUNET_PQ_exec_prepared (pg->conn, + "insert_transaction", + params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) { - if (NULL != res) - { - PQSQL_strerror (GNUNET_ERROR_TYPE_ERROR, "PQprepare", res); - PQclear (res); - } - return GNUNET_SYSERR; + ret = GNUNET_SYSERR; + BREAK_DB_ERR (result); } - if ( (NULL == (res = PQprepare (pg->conn, - "check_payment", - "SELECT * " - "FROM payments " - "WHERE transaction_id=$1", - 1, NULL))) || - (PGRES_COMMAND_OK != (status = PQresultStatus(res))) ) + else { - if (NULL != res) - { - PQSQL_strerror (GNUNET_ERROR_TYPE_ERROR, "PQprepare", res); - PQclear (res); - } - return GNUNET_SYSERR; + ret = GNUNET_OK; } - - PQclear (res); - return GNUNET_OK; + PQclear (result); + return ret; } /** * Insert payment confirmation from the exchange into the database. * - * @param cls our plugin handle - * @param h_contract hash of the contract - * @param h_wire hash of our wire details + * @param cls closure * @param transaction_id of the contract - * @param timestamp time of the confirmation - * @param refund refund deadline - * @param amount_without_fee amount the exchange will deposit * @param coin_pub public key of the coin - * @param exchange_proof proof from the exchange that coin was accepted + * @param amount_with_fee amount the exchange will deposit for this coin + * @param deposit_fee fee the exchange will charge for this coin + * @param exchange_proof proof from exchange that coin was accepted * @return #GNUNET_OK on success, #GNUNET_SYSERR upon error */ static int -postgres_store_payment (void *cls, - const struct GNUNET_HashCode *h_contract, - const struct GNUNET_HashCode *h_wire, +postgres_store_deposit (void *cls, uint64_t transaction_id, - struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute refund, - const struct TALER_Amount *amount_without_fee, const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *deposit_fee, const json_t *exchange_proof) { struct PostgresClosure *pg = cls; - PGresult *res; - ExecStatusType status; + PGresult *result; + int ret; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (h_contract), - GNUNET_PQ_query_param_auto_from_type (h_wire), GNUNET_PQ_query_param_uint64 (&transaction_id), - GNUNET_PQ_query_param_absolute_time (×tamp), - GNUNET_PQ_query_param_absolute_time (&refund), - TALER_PQ_query_param_amount (amount_without_fee), GNUNET_PQ_query_param_auto_from_type (coin_pub), + TALER_PQ_query_param_amount (amount_with_fee), + TALER_PQ_query_param_amount (deposit_fee), TALER_PQ_query_param_json (exchange_proof), GNUNET_PQ_query_param_end }; - res = GNUNET_PQ_exec_prepared (pg->conn, - "insert_payment", - params); - status = PQresultStatus (res); + result = GNUNET_PQ_exec_prepared (pg->conn, + "insert_deposit", + params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + ret = GNUNET_SYSERR; + BREAK_DB_ERR (result); + } + else + { + ret = GNUNET_OK; + } + PQclear (result); + return ret; +} - if (PGRES_COMMAND_OK != status) + +/** + * Insert mapping of @a coin_pub and @a transaction_id to + * corresponding @a wtid. + * + * @param cls closure + * @param transaction_id ID of the contract + * @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 #GNUNET_OK on success, #GNUNET_SYSERR upon error + */ +static int +postgres_store_coin_to_transfer (void *cls, + uint64_t transaction_id, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_WireTransferIdentifierRawP *wtid) +{ + struct PostgresClosure *pg = cls; + PGresult *result; + int ret; + + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&transaction_id), + GNUNET_PQ_query_param_auto_from_type (coin_pub), + GNUNET_PQ_query_param_auto_from_type (wtid), + GNUNET_PQ_query_param_end + }; + + result = GNUNET_PQ_exec_prepared (pg->conn, + "insert_transfer", + params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + ret = GNUNET_SYSERR; + BREAK_DB_ERR (result); + } + else { - const char *sqlstate; + ret = GNUNET_OK; + } + PQclear (result); + return ret; +} + + +/** + * Insert wire transfer confirmation from the exchange into the database. + * + * @param cls closure + * @param wtid identifier of the wire transfer + * @param exchange_proof proof from exchange about what the deposit was for + * @return #GNUNET_OK on success, #GNUNET_SYSERR upon error + */ +static int +postgres_store_transfer_to_proof (void *cls, + const struct TALER_WireTransferIdentifierRawP *wtid, + const json_t *exchange_proof) +{ + struct PostgresClosure *pg = cls; + PGresult *result; + int ret; - sqlstate = PQresultErrorField (res, PG_DIAG_SQLSTATE); - if (NULL == sqlstate) + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (wtid), + TALER_PQ_query_param_json (exchange_proof), + GNUNET_PQ_query_param_end + }; + + result = GNUNET_PQ_exec_prepared (pg->conn, + "insert_proof", + params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + ret = GNUNET_SYSERR; + BREAK_DB_ERR (result); + } + else + { + ret = GNUNET_OK; + } + PQclear (result); + return ret; +} + + +/** + * Find information about a transaction. + * + * @param cls our plugin handle + * @param transaction_id the transaction id to search + * @param cb function to call with transaction data + * @param cb_cls closure for @a cb + * @return #GNUNET_OK if found, #GNUNET_NO if not, #GNUNET_SYSERR + * upon error + */ +static int +postgres_find_transaction_by_id (void *cls, + uint64_t transaction_id, + TALER_MERCHANTDB_TransactionCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + PGresult *result; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&transaction_id), + GNUNET_PQ_query_param_end + }; + + result = GNUNET_PQ_exec_prepared (pg->conn, + "find_transaction", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + if (1 != PQntuples (result)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + { + struct GNUNET_HashCode h_contract; + struct GNUNET_HashCode h_wire; + struct GNUNET_TIME_Absolute timestamp; + struct GNUNET_TIME_Absolute refund_deadline; + struct TALER_Amount total_amount; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("h_contract", + &h_contract), + GNUNET_PQ_result_spec_auto_from_type ("h_wire", + &h_wire), + GNUNET_PQ_result_spec_auto_from_type ("timestamp", + ×tamp), + GNUNET_PQ_result_spec_absolute_time ("refund_deadline", + &refund_deadline), + TALER_PQ_result_spec_amount ("total_amount", + &total_amount), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + 0)) { - /* very unexpected... */ GNUNET_break (0); - PQclear (res); + PQclear (result); return GNUNET_SYSERR; } - /* 40P01: deadlock, 40001: serialization failure */ - if ( (0 == strcmp (sqlstate, - "23505"))) - { - /* Primary key violation */ - PQclear (res); - return GNUNET_NO; - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Database commit failure: %s\n", - sqlstate); - PQclear (res); + cb (cb_cls, + transaction_id, + &h_contract, + &h_wire, + timestamp, + refund_deadline, + &total_amount); + } + PQclear (result); + return GNUNET_OK; +} + + +/** + * Lookup information about coin payments by transaction ID. + * + * @param cls closure + * @param transaction_id key for the search + * @param cb function to call with payment data + * @param cb_cls closure for @a cb + * @return #GNUNET_OK on success, #GNUNET_NO if transaction Id is unknown, + * #GNUNET_SYSERR on hard errors + */ +static int +postgres_find_payments_by_id (void *cls, + uint64_t transaction_id, + TALER_MERCHANTDB_CoinDepositCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + PGresult *result; + unsigned int i; + + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&transaction_id), + GNUNET_PQ_query_param_end + }; + result = GNUNET_PQ_exec_prepared (pg->conn, + "find_deposits", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); return GNUNET_SYSERR; } + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + for (i=0;i<PQntuples (result);i++) + { + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_Amount amount_with_fee; + struct TALER_Amount deposit_fee; + json_t *exchange_proof; + + 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), + TALER_PQ_result_spec_amount ("deposit_fee", + &deposit_fee), + TALER_PQ_result_spec_json ("exchange_proof", + &exchange_proof), + GNUNET_PQ_result_spec_end + }; - PQclear (res); + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + cb (cb_cls, + transaction_id, + &coin_pub, + &amount_with_fee, + &deposit_fee, + exchange_proof); + GNUNET_PQ_cleanup_result (rs); + } + PQclear (result); return GNUNET_OK; + + GNUNET_break (0); + return GNUNET_SYSERR; } + /** - * Check whether a payment has already been stored + * Lookup information about a transfer by @a transaction_id. Note + * that in theory there could be multiple wire transfers for a + * single @a transaction_id, as the transaction may have involved + * multiple coins and the coins may be spread over different wire + * transfers. * - * @param cls our plugin handle - * @param transaction_id the transaction id to search into - * the db - * @return #GNUNET_OK if found, #GNUNET_NO if not, #GNUNET_SYSERR - * upon error + * @param cls closure + * @param transaction_id key for the search + * @param cb function to call with transfer data + * @param cb_cls closure for @a cb + * @return #GNUNET_OK on success, #GNUNET_NO if transaction Id is unknown, + * #GNUNET_SYSERR on hard errors */ static int -postgres_check_payment(void *cls, - uint64_t transaction_id) +postgres_find_transfers_by_id (void *cls, + uint64_t transaction_id, + TALER_MERCHANTDB_TransferCallback cb, + void *cb_cls) { struct PostgresClosure *pg = cls; - PGresult *res; - ExecStatusType status; + PGresult *result; + unsigned int i; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&transaction_id), GNUNET_PQ_query_param_end }; - res = GNUNET_PQ_exec_prepared (pg->conn, - "check_payment", - params); + result = GNUNET_PQ_exec_prepared (pg->conn, + "find_transfers_by_transaction_id", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } - status = PQresultStatus (res); - if (PGRES_TUPLES_OK != status) + for (i=0;i<PQntuples (result);i++) { - const char *sqlstate; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_WireTransferIdentifierRawP wtid; + 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), + TALER_PQ_result_spec_json ("proof", + &proof), + GNUNET_PQ_result_spec_end + }; - sqlstate = PQresultErrorField (res, PG_DIAG_SQLSTATE); - if (NULL == sqlstate) + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) { - /* very unexpected... */ GNUNET_break (0); - PQclear (res); + PQclear (result); return GNUNET_SYSERR; } + cb (cb_cls, + transaction_id, + &coin_pub, + &wtid, + proof); + GNUNET_PQ_cleanup_result (rs); + } + PQclear (result); + return GNUNET_OK; +} - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not check if contract %llu is in DB: %s\n", - (unsigned long long) transaction_id, - sqlstate); - PQclear (res); + +/** + * 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 #GNUNET_OK on success, #GNUNET_NO if transaction Id is unknown, + * #GNUNET_SYSERR on hard errors + */ +static int +postgres_find_deposits_by_wtid (void *cls, + const struct TALER_WireTransferIdentifierRawP *wtid, + TALER_MERCHANTDB_CoinDepositCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + PGresult *result; + unsigned int i; + + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (wtid), + GNUNET_PQ_query_param_end + }; + result = GNUNET_PQ_exec_prepared (pg->conn, + "find_deposits_by_wtid", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); return GNUNET_SYSERR; } - /* count rows */ - if (PQntuples (res) > 0) - return GNUNET_OK; - return GNUNET_NO; - + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + for (i=0;i<PQntuples (result);i++) + { + uint64_t transaction_id; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_Amount amount_with_fee; + struct TALER_Amount deposit_fee; + json_t *exchange_proof; + + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("transaction_id", + &transaction_id), + 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_json ("exchange_proof", + &exchange_proof), + GNUNET_PQ_result_spec_end + }; + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + cb (cb_cls, + transaction_id, + &coin_pub, + &amount_with_fee, + &deposit_fee, + exchange_proof); + GNUNET_PQ_cleanup_result (rs); + } + PQclear (result); + return GNUNET_OK; } + + /** * Initialize Postgres database subsystem. * @@ -312,8 +863,14 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) plugin->cls = pg; plugin->drop_tables = &postgres_drop_tables; plugin->initialize = &postgres_initialize; - plugin->store_payment = &postgres_store_payment; - plugin->check_payment = &postgres_check_payment; + plugin->store_transaction = &postgres_store_transaction; + plugin->store_deposit = &postgres_store_deposit; + plugin->store_coin_to_transfer = &postgres_store_coin_to_transfer; + plugin->store_transfer_to_proof = &postgres_store_transfer_to_proof; + plugin->find_transaction_by_id = &postgres_find_transaction_by_id; + plugin->find_payments_by_id = &postgres_find_payments_by_id; + plugin->find_transfers_by_id = &postgres_find_transfers_by_id; + plugin->find_deposits_by_wtid = &postgres_find_deposits_by_wtid; return plugin; } @@ -336,3 +893,5 @@ libtaler_plugin_merchantdb_postgres_done (void *cls) GNUNET_free (plugin); return NULL; } + +/* end of plugin_merchantdb_postgres.c */ diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c index aeff0eaf..2d0ca0ca 100644 --- a/src/backenddb/test_merchantdb.c +++ b/src/backenddb/test_merchantdb.c @@ -59,6 +59,7 @@ run (void *cls) json_t *exchange_proof = NULL; FAILIF (NULL == (plugin = TALER_MERCHANTDB_plugin_load (cfg))); + (void) plugin->drop_tables (plugin->cls); FAILIF (GNUNET_OK != plugin->initialize (plugin->cls)); /* Prepare data for 'store_payment()' */ @@ -73,6 +74,7 @@ run (void *cls) RND_BLK (&coin_pub); exchange_proof = json_object (); GNUNET_assert (0 == json_object_set (exchange_proof, "test", json_string ("backenddb test"))); +#if 0 FAILIF (GNUNET_OK != plugin->store_payment (plugin->cls, &h_contract, &h_wire, @@ -82,10 +84,12 @@ run (void *cls) &amount_without_fee, &coin_pub, exchange_proof)); - FAILIF (GNUNET_OK != plugin->check_payment (plugin->cls, transaction_id)); - result = 0; + FAILIF (GNUNET_OK != plugin->check_payment (plugin->cls, transaction_id)); +#endif + result = 0; drop: + GNUNET_break (GNUNET_OK == plugin->drop_tables (plugin->cls)); TALER_MERCHANTDB_plugin_unload (plugin); plugin = NULL; if (NULL != exchange_proof) diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h index 7a22b47c..fef668e3 100644 --- a/src/include/taler_merchant_service.h +++ b/src/include/taler_merchant_service.h @@ -55,7 +55,7 @@ typedef void struct TALER_MERCHANT_TrackDepositOperation * TALER_MERCHANT_track_deposit (struct GNUNET_CURL_Context *ctx, const char *backend_uri, - const char *wtid, + const struct TALER_WireTransferIdentifierRawP *wtid, const char *exchange_uri, TALER_MERCHANT_TrackDepositCallback trackdeposit_cb, void *trackdeposit_cb_cls); diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index ca7870d7..16064448 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -31,9 +31,68 @@ struct TALER_MERCHANTDB_Plugin; +/** + * Function called with information about a transaction. + * + * @param cls closure + * @param transaction_id of the contract + * @param h_contract hash of the contract + * @param h_wire hash of our wire details + * @param timestamp time of the confirmation + * @param refund refund deadline + * @param total_amount total amount we receive for the contract after fees + */ +typedef void +(*TALER_MERCHANTDB_TransactionCallback)(void *cls, + uint64_t transaction_id, + const struct GNUNET_HashCode *h_contract, + const struct GNUNET_HashCode *h_wire, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund, + const struct TALER_Amount *total_amount); + + +/** + * Function called with information about a coin that was deposited. + * + * @param cls closure + * @param transaction_id of the contract + * @param coin_pub public key of the coin + * @param amount_with_fee amount the exchange will deposit for this coin + * @param deposit_fee fee the exchange will charge for this coin + * @param exchange_proof proof from exchange that coin was accepted + */ +typedef void +(*TALER_MERCHANTDB_CoinDepositCallback)(void *cls, + uint64_t transaction_id, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *deposit_fee, + const json_t *exchange_proof); + + +/** + * Information about the wire transfer corresponding to + * a deposit operation. Note that it is in theory possible + * that we have a @a transaction_id and @a coin_pub in the + * result that do not match a deposit that we know about, + * for example because someone else deposited funds into + * our account. + * + * @param cls closure + * @param transaction_id ID of the contract + * @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 + * @param exchange_proof proof from exchange about what the deposit was for + * NULL if we have not asked for this signature + */ typedef void -(*TALER_MERCHANTDB_PaymentCallback)(void *cls, - ...); +(*TALER_MERCHANTDB_TransferCallback)(void *cls, + uint64_t transaction_id, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_WireTransferIdentifierRawP *wtid, + const json_t *exchange_proof); /** @@ -71,62 +130,153 @@ struct TALER_MERCHANTDB_Plugin int (*initialize) (void *cls); + /** - * Insert payment confirmation from the exchange into the database. + * Insert transaction data into the database. * * @param cls closure + * @param transaction_id of the contract * @param h_contract hash of the contract * @param h_wire hash of our wire details - * @param transaction_id of the contract * @param timestamp time of the confirmation * @param refund refund deadline - * @param amount_without_fee amount the exchange will deposit + * @param total_amount total amount we receive for the contract after fees + * @return #GNUNET_OK on success, #GNUNET_SYSERR upon error + */ + int + (*store_transaction) (void *cls, + uint64_t transaction_id, + const struct GNUNET_HashCode *h_contract, + const struct GNUNET_HashCode *h_wire, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund, + const struct TALER_Amount *total_amount); + + + /** + * Insert payment confirmation from the exchange into the database. + * + * @param cls closure + * @param transaction_id of the contract * @param coin_pub public key of the coin + * @param amount_with_fee amount the exchange will deposit for this coin + * @param deposit_fee fee the exchange will charge for this coin * @param exchange_proof proof from exchange that coin was accepted * @return #GNUNET_OK on success, #GNUNET_SYSERR upon error */ int - (*store_payment) (void *cls, - const struct GNUNET_HashCode *h_contract, - const struct GNUNET_HashCode *h_wire, + (*store_deposit) (void *cls, uint64_t transaction_id, - struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute refund, - const struct TALER_Amount *amount_without_fee, const struct TALER_CoinSpendPublicKeyP *coin_pub, - const json_t *exchange_proof); + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *deposit_fee, + const json_t *exchange_proof); /** - * Check whether a payment has already been stored + * Insert mapping of @a coin_pub and @a transaction_id to + * corresponding @a wtid. * - * @param cls our plugin handle - * @param transaction_id the transaction id to search into - * the db + * @param cls closure + * @param transaction_id ID of the contract + * @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 #GNUNET_OK on success, #GNUNET_SYSERR upon error + */ + int + (*store_coin_to_transfer) (void *cls, + uint64_t transaction_id, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_WireTransferIdentifierRawP *wtid); + + + /** + * Insert wire transfer confirmation from the exchange into the database. * + * @param cls closure + * @param wtid identifier of the wire transfer + * @param exchange_proof proof from exchange about what the deposit was for + * @return #GNUNET_OK on success, #GNUNET_SYSERR upon error + */ + int + (*store_transfer_to_proof) (void *cls, + const struct TALER_WireTransferIdentifierRawP *wtid, + const json_t *exchange_proof); + + + /** + * Find information about a transaction. + * + * @param cls our plugin handle + * @param transaction_id the transaction id to search + * @param cb function to call with transaction data + * @param cb_cls closure for @a cb * @return #GNUNET_OK if found, #GNUNET_NO if not, #GNUNET_SYSERR - * upon error + * upon error + */ + int + (*find_transaction_by_id) (void *cls, + uint64_t transaction_id, + TALER_MERCHANTDB_TransactionCallback cb, + void *cb_cls); + + + /** + * Lookup information about coin payments by transaction ID. + * + * @param cls closure + * @param transaction_id key for the search + * @param cb function to call with payment data + * @param cb_cls closure for @a cb + * @return #GNUNET_OK on success, #GNUNET_NO if transaction Id is unknown, + * #GNUNET_SYSERR on hard errors */ int - (*check_payment) (void *cls, - uint64_t transaction_id); + (*find_payments_by_id) (void *cls, + uint64_t transaction_id, + TALER_MERCHANTDB_CoinDepositCallback cb, + void *cb_cls); /** - * Lookup information about a payment by transaction ID. + * Lookup information about a transfer by @a transaction_id. Note + * that in theory there could be multiple wire transfers for a + * single @a transaction_id, as the transaction may have involved + * multiple coins and the coins may be spread over different wire + * transfers. * * @param cls closure * @param transaction_id key for the search + * @param cb function to call with transfer data + * @param cb_cls closure for @a cb + * @return #GNUNET_OK on success, #GNUNET_NO if transaction Id is unknown, + * #GNUNET_SYSERR on hard errors + */ + int + (*find_transfers_by_id) (void *cls, + uint64_t transaction_id, + TALER_MERCHANTDB_TransferCallback cb, + void *cb_cls); + + + /** + * 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 #GNUNET_OK on success, #GNUNET_NO if transaction Id is unknown, * #GNUNET_SYSERR on hard errors */ int - (*find_payment) (void *cls, - uint64_t transaction_id, - TALER_MERCHANTDB_PaymentCallback cb, - void *cb_cls); + (*find_deposits_by_wtid) (void *cls, + const struct TALER_WireTransferIdentifierRawP *wtid, + TALER_MERCHANTDB_CoinDepositCallback cb, + void *cb_cls); }; + + #endif diff --git a/src/lib/merchant_api_track.c b/src/lib/merchant_api_track.c index b0fd5745..c49832f2 100644 --- a/src/lib/merchant_api_track.c +++ b/src/lib/merchant_api_track.c @@ -137,14 +137,17 @@ handle_trackdeposit_finished (void *cls, struct TALER_MERCHANT_TrackDepositOperation * TALER_MERCHANT_track_deposit (struct GNUNET_CURL_Context *ctx, const char *backend_uri, - const char *wtid, + const struct TALER_WireTransferIdentifierRawP *wtid, const char *exchange_uri, TALER_MERCHANT_TrackDepositCallback trackdeposit_cb, void *trackdeposit_cb_cls) { struct TALER_MERCHANT_TrackDepositOperation *tdo; CURL *eh; + char *wtid_str; + wtid_str = GNUNET_STRINGS_data_to_string_alloc (wtid, + sizeof (struct TALER_WireTransferIdentifierRawP)); tdo = GNUNET_new (struct TALER_MERCHANT_TrackDepositOperation); tdo->ctx = ctx; tdo->cb = trackdeposit_cb; @@ -153,8 +156,9 @@ TALER_MERCHANT_track_deposit (struct GNUNET_CURL_Context *ctx, GNUNET_asprintf (&tdo->url, "%s?wtid=%s&exchange=%s", backend_uri, - wtid, + wtid_str, exchange_uri); + GNUNET_free (wtid_str); eh = curl_easy_init (); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, |