summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/taler-merchant-httpd_pay.c236
-rw-r--r--src/backend/taler-merchant-httpd_track.c2
-rw-r--r--src/backenddb/plugin_merchantdb_postgres.c833
-rw-r--r--src/backenddb/test_merchantdb.c8
-rw-r--r--src/include/taler_merchant_service.h2
-rw-r--r--src/include/taler_merchantdb_plugin.h198
-rw-r--r--src/lib/merchant_api_track.c8
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 (&timestamp),
+ 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 (&timestamp),
- 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",
+ &timestamp),
+ 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,