/*
This file is part of TALER
(C) 2014--2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see
*/
/**
* @file plugin_merchantdb_postgres.c
* @brief database helper functions for postgres used by the merchant
* @author Sree Harsha Totakura
* @author Christian Grothoff
* @author Marcello Stanisci
* @author Priscilla Huang
*/
#include "platform.h"
#include
#include
#include
#include
#include
#include
#include "taler_merchantdb_plugin.h"
/**
* How often do we re-try if we run into a DB serialization error?
*/
#define MAX_RETRIES 3
/**
* Wrapper macro to add the currency from the plugin's state
* when fetching amounts from the database.
*
* @param field name of the database field to fetch amount from
* @param[out] amountp pointer to amount to set
*/
#define TALER_PQ_RESULT_SPEC_AMOUNT(field,amountp) \
TALER_PQ_result_spec_amount ( \
field,pg->currency,amountp)
/**
* Wrapper macro to add the currency from the plugin's state
* when fetching amounts from the database. NBO variant.
*
* @param field name of the database field to fetch amount from
* @param[out] amountp pointer to amount to set
*/
#define TALER_PQ_RESULT_SPEC_AMOUNT_NBO(field, amountp) \
TALER_PQ_result_spec_amount_nbo ( \
field,pg->currency,amountp)
/**
* Wrapper macro to add the currency from the plugin's state
* when fetching amounts from the database.
*
* @param field name of the database field to fetch amount from
* @param[out] amountp pointer to amount to set
*/
#define TALER_PQ_RESULT_SPEC_AMOUNT(field,amountp) \
TALER_PQ_result_spec_amount ( \
field,pg->currency,amountp)
/**
* Type of the "cls" argument given to each of the functions in
* our API.
*/
struct PostgresClosure
{
/**
* Postgres connection handle.
*/
struct GNUNET_PQ_Context *conn;
/**
* Which currency do we deal in?
*/
char *currency;
/**
* Directory with SQL statements to run to create tables.
*/
char *sql_dir;
/**
* Underlying configuration.
*/
const struct GNUNET_CONFIGURATION_Handle *cfg;
/**
* Name of the currently active transaction, NULL if none is active.
*/
const char *transaction_name;
};
/**
* Drop all Taler tables. This should only be used by testcases.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
static enum GNUNET_GenericReturnValue
postgres_drop_tables (void *cls)
{
struct PostgresClosure *pc = cls;
struct GNUNET_PQ_Context *conn;
enum GNUNET_GenericReturnValue ret;
conn = GNUNET_PQ_connect_with_cfg (pc->cfg,
"merchantdb-postgres",
NULL,
NULL,
NULL);
if (NULL == conn)
return GNUNET_SYSERR;
ret = GNUNET_PQ_exec_sql (conn,
"drop");
GNUNET_PQ_disconnect (conn);
return ret;
}
/**
* Initialize tables.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
static enum GNUNET_GenericReturnValue
postgres_create_tables (void *cls)
{
struct PostgresClosure *pc = cls;
struct GNUNET_PQ_Context *conn;
struct GNUNET_PQ_ExecuteStatement es[] = {
GNUNET_PQ_make_try_execute ("SET search_path TO merchant;"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
conn = GNUNET_PQ_connect_with_cfg (pc->cfg,
"merchantdb-postgres",
"merchant-",
es,
NULL);
if (NULL == conn)
return GNUNET_SYSERR;
GNUNET_PQ_disconnect (conn);
return GNUNET_OK;
}
/**
* Register callback to be invoked on events of type @a es.
*
* @param cls database context to use
* @param es specification of the event to listen for
* @param timeout how long to wait for the event
* @param cb function to call when the event happens, possibly
* multiple times (until cancel is invoked)
* @param cb_cls closure for @a cb
* @return handle useful to cancel the listener
*/
static struct GNUNET_DB_EventHandler *
postgres_event_listen (void *cls,
const struct GNUNET_DB_EventHeaderP *es,
struct GNUNET_TIME_Relative timeout,
GNUNET_DB_EventCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
return GNUNET_PQ_event_listen (pg->conn,
es,
timeout,
cb,
cb_cls);
}
/**
* Stop notifications.
*
* @param eh handle to unregister.
*/
static void
postgres_event_listen_cancel (struct GNUNET_DB_EventHandler *eh)
{
GNUNET_PQ_event_listen_cancel (eh);
}
/**
* Notify all that listen on @a es of an event.
*
* @param cls database context to use
* @param es specification of the event to generate
* @param extra additional event data provided
* @param extra_size number of bytes in @a extra
*/
static void
postgres_event_notify (void *cls,
const struct GNUNET_DB_EventHeaderP *es,
const void *extra,
size_t extra_size)
{
struct PostgresClosure *pg = cls;
return GNUNET_PQ_event_notify (pg->conn,
es,
extra,
extra_size);
}
/**
* Do a pre-flight check that we are not in an uncommitted transaction.
* If we are, die.
* Does not return anything, as we will continue regardless of the outcome.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
*/
static void
postgres_preflight (void *cls)
{
struct PostgresClosure *pg = cls;
if (NULL == pg->transaction_name)
return; /* all good */
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"BUG: Preflight check detected running transaction `%s'!\n",
pg->transaction_name);
GNUNET_assert (0);
}
/**
* Check that the database connection is still up
* and automatically reconnects unless we are
* already inside of a transaction.
*
* @param pg connection to check
*/
static void
check_connection (struct PostgresClosure *pg)
{
if (NULL != pg->transaction_name)
return;
GNUNET_PQ_reconnect_if_down (pg->conn);
}
/**
* Start a transaction.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
* @param name unique name identifying the transaction (for debugging),
* must point to a constant
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
postgres_start (void *cls,
const char *name)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_ExecuteStatement es[] = {
GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
check_connection (pg);
postgres_preflight (pg);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Starting merchant DB transaction `%s'\n",
name);
if (GNUNET_OK !=
GNUNET_PQ_exec_statements (pg->conn,
es))
{
TALER_LOG_ERROR ("Failed to start transaction\n");
GNUNET_break (0);
return GNUNET_SYSERR;
}
pg->transaction_name = name;
return GNUNET_OK;
}
/**
* Start a transaction in 'read committed' mode.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
* @param name unique name identifying the transaction (for debugging),
* must point to a constant
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
postgres_start_read_committed (void *cls,
const char *name)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_ExecuteStatement es[] = {
GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL READ COMMITTED"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
check_connection (pg);
postgres_preflight (pg);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Starting merchant DB transaction %s (READ COMMITTED)\n",
name);
if (GNUNET_OK !=
GNUNET_PQ_exec_statements (pg->conn,
es))
{
TALER_LOG_ERROR ("Failed to start transaction\n");
GNUNET_break (0);
return GNUNET_SYSERR;
}
pg->transaction_name = name;
return GNUNET_OK;
}
/**
* Roll back the current transaction of a database connection.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
*/
static void
postgres_rollback (void *cls)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_ExecuteStatement es[] = {
GNUNET_PQ_make_execute ("ROLLBACK"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Rolling back merchant DB transaction `%s'\n",
pg->transaction_name);
GNUNET_break (GNUNET_OK ==
GNUNET_PQ_exec_statements (pg->conn,
es));
pg->transaction_name = NULL;
}
/**
* Commit the current transaction of a database connection.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
* @return transaction status code
*/
static enum GNUNET_DB_QueryStatus
postgres_commit (void *cls)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_end
};
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Committing merchant DB transaction %s\n",
pg->transaction_name);
pg->transaction_name = NULL;
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"end_transaction",
params);
}
/**
* Context for lookup_instances().
*/
struct LookupInstancesContext
{
/**
* Function to call with the results.
*/
TALER_MERCHANTDB_InstanceCallback cb;
/**
* Closure for @e cb.
*/
void *cb_cls;
/**
* Database context.
*/
struct PostgresClosure *pg;
/**
* Instance settings, valid only during find_instances_cb().
*/
struct TALER_MERCHANTDB_InstanceSettings is;
/**
* Instance authentication settings, valid only during find_instances_cb().
*/
struct TALER_MERCHANTDB_InstanceAuthSettings ias;
/**
* Instance serial number, valid only during find_instances_cb().
*/
uint64_t instance_serial;
/**
* Public key of the current instance, valid only during find_instances_cb().
*/
struct TALER_MerchantPublicKeyP merchant_pub;
/**
* Set to the return value on errors.
*/
enum GNUNET_DB_QueryStatus qs;
/**
* true if we only are interested in instances for which we have the private key.
*/
bool active_only;
};
/**
* We are processing an instances lookup and have the @a accounts.
* Find the private key if possible, and invoke the callback.
*
* @param lic context we are handling
* @param num_accounts length of @a accounts array
* @param accounts information about accounts of the instance in @a lic
*/
static void
call_with_accounts (struct LookupInstancesContext *lic,
unsigned int num_accounts,
const struct TALER_MERCHANTDB_AccountDetails accounts[])
{
struct PostgresClosure *pg = lic->pg;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&lic->instance_serial),
GNUNET_PQ_query_param_end
};
struct TALER_MerchantPrivateKeyP merchant_priv;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("merchant_priv",
&merchant_priv),
GNUNET_PQ_result_spec_end
};
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_instance_private_key",
params,
rs);
if (qs < 0)
{
GNUNET_break (0);
lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
if ( (0 == qs) &&
(lic->active_only) )
return; /* skip, not interesting */
lic->cb (lic->cb_cls,
&lic->merchant_pub,
(0 == qs) ? NULL : &merchant_priv,
&lic->is,
&lic->ias,
num_accounts,
accounts);
}
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about accounts.
*
* @param cls of type `struct FindInstancesContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_accounts_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupInstancesContext *lic = cls;
char *paytos[num_results];
struct TALER_MERCHANTDB_AccountDetails accounts[num_results];
for (unsigned int i = 0; i < num_results; i++)
{
uint8_t active;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("h_wire",
&accounts[i].h_wire),
GNUNET_PQ_result_spec_auto_from_type ("salt",
&accounts[i].salt),
GNUNET_PQ_result_spec_string ("payto_uri",
&paytos[i]),
GNUNET_PQ_result_spec_auto_from_type ("active",
&active),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
for (unsigned int j = 0; j < i; j++)
GNUNET_free (paytos[j]);
return;
}
accounts[i].active = (0 != active);
accounts[i].payto_uri = paytos[i];
}
call_with_accounts (lic,
num_results,
accounts);
for (unsigned int i = 0; i < num_results; i++)
GNUNET_free (paytos[i]);
}
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about instances.
*
* @param cls of type `struct FindInstancesContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_instances_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupInstancesContext *lic = cls;
struct PostgresClosure *pg = lic->pg;
for (unsigned int i = 0; i < num_results; i++)
{
bool no_auth;
bool no_salt;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("merchant_serial",
&lic->instance_serial),
GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
&lic->merchant_pub),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_auto_from_type ("auth_hash",
&lic->ias.auth_hash),
&no_auth),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_auto_from_type ("auth_salt",
&lic->ias.auth_salt),
&no_salt),
GNUNET_PQ_result_spec_string ("merchant_id",
&lic->is.id),
GNUNET_PQ_result_spec_string ("merchant_name",
&lic->is.name),
TALER_PQ_result_spec_json ("address",
&lic->is.address),
TALER_PQ_result_spec_json ("jurisdiction",
&lic->is.jurisdiction),
TALER_PQ_RESULT_SPEC_AMOUNT ("default_max_deposit_fee",
&lic->is.default_max_deposit_fee),
TALER_PQ_RESULT_SPEC_AMOUNT ("default_max_wire_fee",
&lic->is.default_max_wire_fee),
GNUNET_PQ_result_spec_uint32 ("default_wire_fee_amortization",
&lic->is.default_wire_fee_amortization),
GNUNET_PQ_result_spec_relative_time ("default_wire_transfer_delay",
&lic->is.default_wire_transfer_delay),
GNUNET_PQ_result_spec_relative_time ("default_pay_delay",
&lic->is.default_pay_delay),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_string ("website",
&lic->is.website),
NULL),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_string ("email",
&lic->is.email),
NULL),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_string ("logo",
&lic->is.logo),
NULL),
GNUNET_PQ_result_spec_end
};
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&lic->instance_serial),
GNUNET_PQ_query_param_end
};
memset (&lic->ias.auth_salt,
0,
sizeof (lic->ias.auth_salt));
memset (&lic->ias.auth_hash,
0,
sizeof (lic->ias.auth_hash));
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
lic->qs = GNUNET_PQ_eval_prepared_multi_select (lic->pg->conn,
"lookup_accounts",
params,
&lookup_accounts_cb,
lic);
if (0 > lic->qs)
{
/* lookup_accounts_cb() did not run, still notify about the
account-less instance! */
call_with_accounts (lic,
0,
NULL);
}
GNUNET_PQ_cleanup_result (rs);
if (0 > lic->qs)
break;
}
}
/**
* Lookup all of the instances this backend has configured.
*
* @param cls closure
* @param active_only only find 'active' instances
* @param cb function to call on all instances found
* @param cb_cls closure for @a cb
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_instances (void *cls,
bool active_only,
TALER_MERCHANTDB_InstanceCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupInstancesContext lic = {
.cb = cb,
.cb_cls = cb_cls,
.active_only = active_only,
.pg = pg
};
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_instances",
params,
&lookup_instances_cb,
&lic);
if (0 > lic.qs)
return lic.qs;
return qs;
}
/**
* Lookup all one of the instances this backend has configured.
*
* @param cls closure
* @param id instance ID to resolve
* @param active_only only find 'active' instances
* @param cb function to call on all instances found
* @param cb_cls closure for @a cb
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_instance (void *cls,
const char *id,
bool active_only,
TALER_MERCHANTDB_InstanceCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupInstancesContext lic = {
.cb = cb,
.cb_cls = cb_cls,
.active_only = active_only,
.pg = pg
};
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_instance",
params,
&lookup_instances_cb,
&lic);
if (0 > lic.qs)
return lic.qs;
return qs;
}
/**
* Lookup authentication data of an instance.
*
* @param cls closure
* @param instance_id instance to query
* @param[out] ias where to store the auth data
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_instance_auth (
void *cls,
const char *instance_id,
struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("auth_hash",
&ias->auth_hash),
GNUNET_PQ_result_spec_auto_from_type ("auth_salt",
&ias->auth_salt),
GNUNET_PQ_result_spec_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_instance_auth",
params,
rs);
}
/**
* Insert information about an instance into our database.
*
* @param cls closure
* @param merchant_pub public key of the instance
* @param merchant_priv private key of the instance
* @param is details about the instance
* @param ias authentication settings for the instance
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_instance (
void *cls,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_MerchantPrivateKeyP *merchant_priv,
const struct TALER_MERCHANTDB_InstanceSettings *is,
const struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_auto_from_type (&ias->auth_hash),
GNUNET_PQ_query_param_auto_from_type (&ias->auth_salt),
GNUNET_PQ_query_param_string (is->id),
GNUNET_PQ_query_param_string (is->name),
TALER_PQ_query_param_json (is->address),
TALER_PQ_query_param_json (is->jurisdiction),
TALER_PQ_query_param_amount (&is->default_max_deposit_fee),
TALER_PQ_query_param_amount (&is->default_max_wire_fee),
GNUNET_PQ_query_param_uint32 (&is->default_wire_fee_amortization),
GNUNET_PQ_query_param_relative_time (
&is->default_wire_transfer_delay),
GNUNET_PQ_query_param_relative_time (&is->default_pay_delay),
(NULL == is->website)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (is->website),
(NULL == is->email)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (is->email),
(NULL == is->logo)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (is->logo),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_QueryParam params_priv[] = {
GNUNET_PQ_query_param_auto_from_type (merchant_priv),
GNUNET_PQ_query_param_string (is->id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_instance",
params);
if (qs <= 0)
return qs;
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_keys",
params_priv);
}
/**
* Insert information about an instance's account into our database.
*
* @param cls closure
* @param id identifier of the instance
* @param account_details details about the account
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_account (
void *cls,
const char *id,
const struct TALER_MERCHANTDB_AccountDetails *account_details)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (id),
GNUNET_PQ_query_param_auto_from_type (&account_details->h_wire),
GNUNET_PQ_query_param_auto_from_type (&account_details->salt),
GNUNET_PQ_query_param_string (account_details->payto_uri),
GNUNET_PQ_query_param_bool (account_details->active),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_account",
params);
}
/**
* Closure for kyc_status_cb().
*/
struct KycStatusContext
{
/**
* Function to call with results.
*/
TALER_MERCHANTDB_KycCallback kyc_cb;
/**
* Closure for @e kyc_cb.
*/
void *kyc_cb_cls;
/**
* Filter, NULL to not filter.
*/
const struct TALER_MerchantWireHashP *h_wire;
/**
* Filter, NULL to not filter.
*/
const char *exchange_url;
/**
* Number of results found.
*/
unsigned int count;
/**
* Set to true on failure(s).
*/
bool failure;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about accounts.
*
* @param[in,out] cls of type `struct KycStatusContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
kyc_status_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct KycStatusContext *ksc = cls;
for (unsigned int i = 0; i < num_results; i++)
{
struct TALER_MerchantWireHashP h_wire;
uint64_t kyc_serial;
char *exchange_url;
char *payto_uri;
struct GNUNET_TIME_Timestamp last_check;
uint8_t kyc_ok;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("h_wire",
&h_wire),
GNUNET_PQ_result_spec_uint64 ("exchange_kyc_serial",
&kyc_serial),
GNUNET_PQ_result_spec_string ("payto_uri",
&payto_uri),
GNUNET_PQ_result_spec_string ("exchange_url",
&exchange_url),
GNUNET_PQ_result_spec_timestamp ("kyc_timestamp",
&last_check),
GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
&kyc_ok),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
ksc->failure = true;
return;
}
if ( (NULL != ksc->exchange_url) &&
(0 != strcmp (ksc->exchange_url,
exchange_url)) )
{
GNUNET_PQ_cleanup_result (rs);
continue;
}
if ( (NULL != ksc->h_wire) &&
(0 != GNUNET_memcmp (ksc->h_wire,
&h_wire)) )
{
GNUNET_PQ_cleanup_result (rs);
continue;
}
ksc->count++;
ksc->kyc_cb (ksc->kyc_cb_cls,
&h_wire,
kyc_serial,
payto_uri,
exchange_url,
last_check,
0 != kyc_ok);
GNUNET_PQ_cleanup_result (rs);
}
}
/**
* Check an instance's account's KYC status.
*
* @param cls closure
* @param merchant_id merchant backend instance ID
* @param h_wire hash of the wire account to check,
* NULL to check all accounts of the merchant
* @param exchange_url base URL of the exchange to check,
* NULL to check all exchanges
* @param kyc_cb KYC status callback to invoke
* @param kyc_cb_cls closure for @a kyc_cb
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_account_kyc_get_status (void *cls,
const char *merchant_id,
const struct TALER_MerchantWireHashP *h_wire,
const char *exchange_url,
TALER_MERCHANTDB_KycCallback kyc_cb,
void *kyc_cb_cls)
{
struct PostgresClosure *pg = cls;
struct KycStatusContext ksc = {
.kyc_cb = kyc_cb,
.kyc_cb_cls = kyc_cb_cls,
.exchange_url = exchange_url,
.h_wire = h_wire
};
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (merchant_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_kyc_status",
params,
&kyc_status_cb,
&ksc);
if (ksc.failure)
{
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
if (0 > qs)
return qs;
return ksc.count;
}
/**
* Update an instance's account's KYC status.
*
* @param cls closure
* @param merchant_id merchant backend instance ID
* @param h_wire hash of the wire account to check
* @param exchange_url base URL of the exchange to check
* @param exchange_kyc_serial serial number for our account at the exchange (0 if unknown)
* @param exchange_sig signature of the exchange, or NULL for none
* @param exchange_pub public key of the exchange, or NULL for none
* @param timestamp timestamp to store
* @param kyc_ok current KYC status (true for satisfied)
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_account_kyc_set_status (
void *cls,
const char *merchant_id,
const struct TALER_MerchantWireHashP *h_wire,
const char *exchange_url,
uint64_t exchange_kyc_serial,
const struct TALER_ExchangeSignatureP *exchange_sig,
const struct TALER_ExchangePublicKeyP *exchange_pub,
struct GNUNET_TIME_Timestamp timestamp,
bool kyc_ok)
{
struct PostgresClosure *pg = cls;
uint8_t ok = kyc_ok;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (merchant_id),
GNUNET_PQ_query_param_auto_from_type (h_wire),
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_uint64 (&exchange_kyc_serial),
GNUNET_PQ_query_param_timestamp (×tamp),
GNUNET_PQ_query_param_auto_from_type (&ok),
exchange_pub
? GNUNET_PQ_query_param_auto_from_type (exchange_pub)
: GNUNET_PQ_query_param_null (),
exchange_sig
? GNUNET_PQ_query_param_auto_from_type (exchange_sig)
: GNUNET_PQ_query_param_null (),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"upsert_account_kyc",
params);
}
/**
* Delete private key of an instance from our database.
*
* @param cls closure
* @param merchant_id identifier of the instance
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_delete_instance_private_key (
void *cls,
const char *merchant_id)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (merchant_id),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"delete_key",
params);
}
/**
* Purge an instance and all associated information from our database.
* Highly likely to cause undesired data loss. Use with caution.
*
* @param cls closure
* @param merchant_id identifier of the instance
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_purge_instance (void *cls,
const char *merchant_id)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (merchant_id),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"purge_instance",
params);
}
/**
* Update information about an instance into our database.
*
* @param cls closure
* @param is details about the instance
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_update_instance (void *cls,
const struct TALER_MERCHANTDB_InstanceSettings *is)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (is->id),
GNUNET_PQ_query_param_string (is->name),
TALER_PQ_query_param_json (is->address),
TALER_PQ_query_param_json (is->jurisdiction),
TALER_PQ_query_param_amount (&is->default_max_deposit_fee),
TALER_PQ_query_param_amount (&is->default_max_wire_fee),
GNUNET_PQ_query_param_uint32 (&is->default_wire_fee_amortization),
GNUNET_PQ_query_param_relative_time (
&is->default_wire_transfer_delay),
GNUNET_PQ_query_param_relative_time (&is->default_pay_delay),
(NULL == is->website)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (is->website),
(NULL == is->email)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (is->email),
(NULL == is->logo)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (is->logo),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_instance",
params);
}
/**
* Update information about an instance's authentication settings
* into our database.
*
* @param cls closure
* @param merchant_id identity of the instance
* @param is authentication details about the instance
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_update_instance_auth (
void *cls,
const char *merchant_id,
const struct TALER_MERCHANTDB_InstanceAuthSettings *is)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (merchant_id),
GNUNET_PQ_query_param_auto_from_type (&is->auth_hash),
GNUNET_PQ_query_param_auto_from_type (&is->auth_salt),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_instance_auth",
params);
}
/**
* Set an instance's account in our database to "inactive".
*
* @param cls closure
* @param merchant_id merchant backend instance ID
* @param h_wire hash of the wire account to set to inactive
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_inactivate_account (void *cls,
const char *merchant_id,
const struct TALER_MerchantWireHashP *h_wire)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (merchant_id),
GNUNET_PQ_query_param_auto_from_type (h_wire),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"inactivate_account",
params);
}
/**
* Set an instance's account in our database to "active".
*
* @param cls closure
* @param merchant_id merchant backend instance ID
* @param h_wire hash of the wire account to set to active
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_activate_account (void *cls,
const char *merchant_id,
const struct TALER_MerchantWireHashP *h_wire)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (merchant_id),
GNUNET_PQ_query_param_auto_from_type (h_wire),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"activate_account",
params);
}
/**
* Context used for postgres_lookup_products().
*/
struct LookupProductsContext
{
/**
* Function to call with the results.
*/
TALER_MERCHANTDB_ProductsCallback cb;
/**
* Closure for @a cb.
*/
void *cb_cls;
/**
* Did database result extraction fail?
*/
bool extract_failed;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about products.
*
* @param[in,out] cls of type `struct LookupProductsContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_products_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupProductsContext *plc = cls;
for (unsigned int i = 0; i < num_results; i++)
{
char *product_id;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("product_id",
&product_id),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
plc->extract_failed = true;
return;
}
plc->cb (plc->cb_cls,
product_id);
GNUNET_PQ_cleanup_result (rs);
}
}
/**
* Lookup all of the products the given instance has configured.
*
* @param cls closure
* @param instance_id instance to lookup products for
* @param cb function to call on all products found
* @param cb_cls closure for @a cb
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_products (void *cls,
const char *instance_id,
TALER_MERCHANTDB_ProductsCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupProductsContext plc = {
.cb = cb,
.cb_cls = cb_cls,
/* Can be overwritten by the lookup_products_cb */
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_products",
params,
&lookup_products_cb,
&plc);
/* If there was an error inside lookup_products_cb, return a hard error. */
if (plc.extract_failed)
return GNUNET_DB_STATUS_HARD_ERROR;
return qs;
}
/**
* Lookup details about a particular product.
*
* @param cls closure
* @param instance_id instance to lookup products for
* @param product_id product to lookup
* @param[out] pd set to the product details on success, can be NULL
* (in that case we only want to check if the product exists)
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_product (void *cls,
const char *instance_id,
const char *product_id,
struct TALER_MERCHANTDB_ProductDetails *pd)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (product_id),
GNUNET_PQ_query_param_end
};
if (NULL == pd)
{
struct GNUNET_PQ_ResultSpec rs_null[] = {
GNUNET_PQ_result_spec_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_product",
params,
rs_null);
}
else
{
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("description",
&pd->description),
TALER_PQ_result_spec_json ("description_i18n",
&pd->description_i18n),
GNUNET_PQ_result_spec_string ("unit",
&pd->unit),
TALER_PQ_RESULT_SPEC_AMOUNT ("price",
&pd->price),
TALER_PQ_result_spec_json ("taxes",
&pd->taxes),
GNUNET_PQ_result_spec_uint64 ("total_stock",
&pd->total_stock),
GNUNET_PQ_result_spec_uint64 ("total_sold",
&pd->total_sold),
GNUNET_PQ_result_spec_uint64 ("total_lost",
&pd->total_lost),
GNUNET_PQ_result_spec_string ("image",
&pd->image),
TALER_PQ_result_spec_json ("address",
&pd->address),
GNUNET_PQ_result_spec_timestamp ("next_restock",
&pd->next_restock),
GNUNET_PQ_result_spec_uint32 ("minimum_age",
&pd->minimum_age),
GNUNET_PQ_result_spec_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_product",
params,
rs);
}
}
/**
* Delete information about a product. Note that the transaction must
* enforce that no stocks are currently locked.
*
* @param cls closure
* @param instance_id instance to delete product of
* @param product_id product to delete
* @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
* if locks prevent deletion OR product unknown
*/
static enum GNUNET_DB_QueryStatus
postgres_delete_product (void *cls,
const char *instance_id,
const char *product_id)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (product_id),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"delete_product",
params);
}
/**
* Insert details about a particular product.
*
* @param cls closure
* @param instance_id instance to insert product for
* @param product_id product identifier of product to insert
* @param pd the product details to insert
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_product (void *cls,
const char *instance_id,
const char *product_id,
const struct TALER_MERCHANTDB_ProductDetails *pd)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (product_id),
GNUNET_PQ_query_param_string (pd->description),
TALER_PQ_query_param_json (pd->description_i18n),
GNUNET_PQ_query_param_string (pd->unit),
GNUNET_PQ_query_param_string (pd->image),
TALER_PQ_query_param_json (pd->taxes),
TALER_PQ_query_param_amount (&pd->price),
GNUNET_PQ_query_param_uint64 (&pd->total_stock),
TALER_PQ_query_param_json (pd->address),
GNUNET_PQ_query_param_timestamp (&pd->next_restock),
GNUNET_PQ_query_param_uint32 (&pd->minimum_age),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_product",
params);
}
/**
* Update details about a particular product. Note that the
* transaction must enforce that the sold/stocked/lost counters
* are not reduced (i.e. by expanding the WHERE clause on the existing
* values).
*
* @param cls closure
* @param instance_id instance to lookup products for
* @param product_id product to lookup
* @param[out] pd set to the product details on success, can be NULL
* (in that case we only want to check if the product exists)
* total_sold in @a pd is ignored, total_lost must not
* exceed total_stock minus the existing total_sold;
* total_sold and total_stock must be larger or equal to
* the existing value;
* @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the
* non-decreasing constraints are not met *or* if the product
* does not yet exist.
*/
static enum GNUNET_DB_QueryStatus
postgres_update_product (void *cls,
const char *instance_id,
const char *product_id,
const struct TALER_MERCHANTDB_ProductDetails *pd)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id), /* $1 */
GNUNET_PQ_query_param_string (product_id),
GNUNET_PQ_query_param_string (pd->description),
TALER_PQ_query_param_json (pd->description_i18n),
GNUNET_PQ_query_param_string (pd->unit),
GNUNET_PQ_query_param_string (pd->image), /* $6 */
TALER_PQ_query_param_json (pd->taxes),
TALER_PQ_query_param_amount (&pd->price), /* $8+$9 */
GNUNET_PQ_query_param_uint64 (&pd->total_stock), /* $10 */
GNUNET_PQ_query_param_uint64 (&pd->total_lost),
TALER_PQ_query_param_json (pd->address),
GNUNET_PQ_query_param_timestamp (&pd->next_restock),
GNUNET_PQ_query_param_uint32 (&pd->minimum_age),
GNUNET_PQ_query_param_end
};
if ( (pd->total_stock < pd->total_lost + pd->total_sold) ||
(pd->total_lost < pd->total_lost
+ pd->total_sold) /* integer overflow */)
{
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_product",
params);
}
/**
* Lock stocks of a particular product. Note that the transaction must
* enforce that the "stocked-sold-lost >= locked" constraint holds.
*
* @param cls closure
* @param instance_id instance to lookup products for
* @param product_id product to lookup
* @param uuid the UUID that holds the lock
* @param quantity how many units should be locked
* @param expiration_time when should the lock expire
* @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the
* product is unknown OR if there insufficient stocks remaining
*/
static enum GNUNET_DB_QueryStatus
postgres_lock_product (void *cls,
const char *instance_id,
const char *product_id,
const struct GNUNET_Uuid *uuid,
uint64_t quantity,
struct GNUNET_TIME_Timestamp expiration_time)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (product_id),
GNUNET_PQ_query_param_auto_from_type (uuid),
GNUNET_PQ_query_param_uint64 (&quantity),
GNUNET_PQ_query_param_timestamp (&expiration_time),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"lock_product",
params);
}
/**
* Release all expired product locks, including
* those from expired offers -- across all
* instances.
*
* @param cls closure
*/
static void
postgres_expire_locks (void *cls)
{
struct PostgresClosure *pg = cls;
struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs1;
enum GNUNET_DB_QueryStatus qs2;
enum GNUNET_DB_QueryStatus qs3;
check_connection (pg);
qs1 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"unlock_products",
params);
if (qs1 < 0)
{
GNUNET_break (0);
return;
}
qs2 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"unlock_orders",
params);
if (qs2 < 0)
{
GNUNET_break (0);
return;
}
qs3 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"unlock_contracts",
params);
if (qs3 < 0)
{
GNUNET_break (0);
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Released %d+%d+%d locks\n",
qs1,
qs2,
qs3);
}
/**
* Delete information about an order. Note that the transaction must
* enforce that the order is not awaiting payment anymore.
*
* @param cls closure
* @param instance_id instance to delete order of
* @param order_id order to delete
* @param force delete claimed but unpaid orders as well
* @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
* if pending payment prevents deletion OR order unknown
*/
static enum GNUNET_DB_QueryStatus
postgres_delete_order (void *cls,
const char *instance_id,
const char *order_id,
bool force)
{
struct PostgresClosure *pg = cls;
struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_bool (force),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_QueryParam params2[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"delete_order",
params);
if ( (qs <= 0) || (! force))
return qs;
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"delete_contract",
params2);
}
/**
* Retrieve order given its @a order_id and the @a instance_id.
*
* @param cls closure
* @param instance_id instance to obtain order of
* @param order_id order id used to perform the lookup
* @param[out] claim_token the claim token generated for the order,
* NULL to only test if the order exists
* @param[out] h_post_data set to the hash of the POST data that created the order
* @param[out] contract_terms where to store the retrieved contract terms,
* NULL to only test if the order exists
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_order (void *cls,
const char *instance_id,
const char *order_id,
struct TALER_ClaimTokenP *claim_token,
struct TALER_MerchantPostDataHashP *h_post_data,
json_t **contract_terms)
{
struct PostgresClosure *pg = cls;
json_t *j;
struct TALER_ClaimTokenP ct;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
TALER_PQ_result_spec_json ("contract_terms",
&j),
GNUNET_PQ_result_spec_auto_from_type ("claim_token",
&ct),
GNUNET_PQ_result_spec_auto_from_type ("h_post_data",
h_post_data),
GNUNET_PQ_result_spec_end
};
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Finding contract term, order_id: '%s', instance_id: '%s'.\n",
order_id,
instance_id);
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_order",
params,
rs);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
if (NULL != contract_terms)
*contract_terms = j;
else
json_decref (j);
if (NULL != claim_token)
*claim_token = ct;
}
else
{
/* just to be safe: NULL it */
if (NULL != contract_terms)
*contract_terms = NULL;
if (NULL != claim_token)
*claim_token = (struct TALER_ClaimTokenP) { 0 }
;
}
return qs;
}
/**
* Retrieve order summary given its @a order_id and the @a instance_id.
*
* @param cls closure
* @param instance_id instance to obtain order of
* @param order_id order id used to perform the lookup
* @param[out] timestamp when was the order created
* @param[out] order_serial under which serial do we keep this order
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_order_summary (void *cls,
const char *instance_id,
const char *order_id,
struct GNUNET_TIME_Timestamp *timestamp,
uint64_t *order_serial)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("order_serial",
order_serial),
GNUNET_PQ_result_spec_timestamp ("creation_time",
timestamp),
GNUNET_PQ_result_spec_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_order_summary",
params,
rs);
}
/**
* Context used for postgres_lookup_orders().
*/
struct LookupOrdersContext
{
/**
* Function to call with the results.
*/
TALER_MERCHANTDB_OrdersCallback cb;
/**
* Closure for @a cb.
*/
void *cb_cls;
/**
* Did database result extraction fail?
*/
bool extract_failed;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about orders.
*
* @param[in,out] cls of type `struct LookupOrdersContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_orders_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupOrdersContext *plc = cls;
for (unsigned int i = 0; i < num_results; i++)
{
char *order_id;
uint64_t order_serial;
struct GNUNET_TIME_Timestamp ts;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("order_id",
&order_id),
GNUNET_PQ_result_spec_uint64 ("order_serial",
&order_serial),
GNUNET_PQ_result_spec_timestamp ("creation_time",
&ts),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
plc->extract_failed = true;
return;
}
plc->cb (plc->cb_cls,
order_id,
order_serial,
ts);
GNUNET_PQ_cleanup_result (rs);
}
}
/**
* Retrieve orders given the @a instance_id.
*
* @param cls closure
* @param instance_id instance to obtain order of
* @param of filter to apply when looking up orders
* @param cb callback to pass all the orders that are found
* @param cb_cls closure for @a cb
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_orders (void *cls,
const char *instance_id,
const struct TALER_MERCHANTDB_OrderFilter *of,
TALER_MERCHANTDB_OrdersCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupOrdersContext plc = {
.cb = cb,
.cb_cls = cb_cls
};
uint64_t limit = (of->delta > 0) ? of->delta : -of->delta;
uint8_t paid;
uint8_t refunded;
uint8_t wired;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&limit),
GNUNET_PQ_query_param_uint64 (&of->start_row),
GNUNET_PQ_query_param_timestamp (&of->date),
GNUNET_PQ_query_param_auto_from_type (&paid),
GNUNET_PQ_query_param_auto_from_type (&refunded),
GNUNET_PQ_query_param_auto_from_type (&wired),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
char stmt[128];
paid = (TALER_EXCHANGE_YNA_YES == of->paid);
refunded = (TALER_EXCHANGE_YNA_YES == of->refunded);
wired = (TALER_EXCHANGE_YNA_YES == of->wired);
/* painfully many cases..., note that "_xxx" being present in 'stmt' merely
means that we filter by that variable, the value we filter for is
computed above */
GNUNET_snprintf (stmt,
sizeof (stmt),
"lookup_orders_%s%s%s%s",
(of->delta > 0) ? "inc" : "dec",
(TALER_EXCHANGE_YNA_ALL == of->paid) ? "" : "_paid",
(TALER_EXCHANGE_YNA_ALL == of->refunded) ? "" :
"_refunded",
(TALER_EXCHANGE_YNA_ALL == of->wired) ? "" : "_wired");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
stmt,
params,
&lookup_orders_cb,
&plc);
if (plc.extract_failed)
return GNUNET_DB_STATUS_HARD_ERROR;
return qs;
}
/**
* Insert order into the DB.
*
* @param cls closure
* @param instance_id identifies the instance responsible for the order
* @param order_id alphanumeric string that uniquely identifies the proposal
* @param h_post_data hash of the POST data for idempotency checks
* @param pay_deadline how long does the customer have to pay for the order
* @param claim_token token to use for access control
* @param contract_terms proposal data to store
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_order (void *cls,
const char *instance_id,
const char *order_id,
const struct TALER_MerchantPostDataHashP *h_post_data,
struct GNUNET_TIME_Timestamp pay_deadline,
const struct TALER_ClaimTokenP *claim_token,
const json_t *contract_terms)
{
struct PostgresClosure *pg = cls;
struct GNUNET_TIME_Timestamp now;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_timestamp (&pay_deadline),
GNUNET_PQ_query_param_auto_from_type (claim_token),
GNUNET_PQ_query_param_auto_from_type (h_post_data),
GNUNET_PQ_query_param_timestamp (&now),
TALER_PQ_query_param_json (contract_terms),
GNUNET_PQ_query_param_end
};
now = GNUNET_TIME_timestamp_get ();
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"inserting order: order_id: %s, instance_id: %s.\n",
order_id,
instance_id);
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_order",
params);
}
/**
* Release an inventory lock by UUID. Releases ALL stocks locked under
* the given UUID.
*
* @param cls closure
* @param uuid the UUID to release locks for
* @return transaction status,
* #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are no locks under @a uuid
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
*/
static enum GNUNET_DB_QueryStatus
postgres_unlock_inventory (void *cls,
const struct GNUNET_Uuid *uuid)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (uuid),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"unlock_inventory",
params);
}
/**
* Lock inventory stock to a particular order.
*
* @param cls closure
* @param instance_id identifies the instance responsible for the order
* @param order_id alphanumeric string that uniquely identifies the order
* @param product_id uniquely identifies the product to be locked
* @param quantity how many units should be locked to the @a order_id
* @return transaction status,
* #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are insufficient stocks
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_order_lock (void *cls,
const char *instance_id,
const char *order_id,
const char *product_id,
uint64_t quantity)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_string (product_id),
GNUNET_PQ_query_param_uint64 (&quantity),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_order_lock",
params);
}
/**
* Retrieve contract terms given its @a order_id
*
* @param cls closure
* @param instance_id instance's identifier
* @param order_id order_id used to lookup.
* @param[out] contract_terms where to store the result, NULL to only check for existence
* @param[out] order_serial set to the order's serial number
* @param[out] paid set to true if the order is fully paid
* @param[out] claim_token set to the claim token, NULL to only check for existence
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_contract_terms (void *cls,
const char *instance_id,
const char *order_id,
json_t **contract_terms,
uint64_t *order_serial,
bool *paid,
struct TALER_ClaimTokenP *claim_token)
{
struct PostgresClosure *pg = cls;
enum GNUNET_DB_QueryStatus qs;
struct TALER_ClaimTokenP ct;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
/* contract_terms must be first! */
TALER_PQ_result_spec_json ("contract_terms",
contract_terms),
GNUNET_PQ_result_spec_uint64 ("order_serial",
order_serial),
GNUNET_PQ_result_spec_bool ("paid",
paid),
GNUNET_PQ_result_spec_auto_from_type ("claim_token",
&ct),
GNUNET_PQ_result_spec_end
};
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_contract_terms",
params,
(NULL != contract_terms)
? rs
: &rs[1]);
if (NULL != claim_token)
*claim_token = ct;
return qs;
}
/**
* Store contract terms given its @a order_id. Note that some attributes are
* expected to be calculated inside of the function, like the hash of the
* contract terms (to be hashed), the creation_time and pay_deadline (to be
* obtained from the merchant_orders table). The "session_id" should be
* initially set to the empty string. The "fulfillment_url" and "refund_deadline"
* must be extracted from @a contract_terms.
*
* @param cls closure
* @param instance_id instance's identifier
* @param order_id order_id used to store
* @param contract_terms contract terms to store
* @param[out] order_serial set to the serial of the order
* @return transaction status, #GNUNET_DB_STATUS_HARD_ERROR if @a contract_terms
* is malformed
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_contract_terms (void *cls,
const char *instance_id,
const char *order_id,
json_t *contract_terms,
uint64_t *order_serial)
{
struct PostgresClosure *pg = cls;
struct GNUNET_TIME_Timestamp pay_deadline;
struct GNUNET_TIME_Timestamp refund_deadline;
const char *fulfillment_url;
struct TALER_PrivateContractHashP h_contract_terms;
if (GNUNET_OK !=
TALER_JSON_contract_hash (contract_terms,
&h_contract_terms))
{
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_timestamp ("pay_deadline",
&pay_deadline),
GNUNET_JSON_spec_timestamp ("refund_deadline",
&refund_deadline),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_data (NULL,
contract_terms,
spec);
if (GNUNET_OK != res)
{
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
}
fulfillment_url =
json_string_value (json_object_get (contract_terms,
"fulfillment_url"));
check_connection (pg);
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
TALER_PQ_query_param_json (contract_terms),
GNUNET_PQ_query_param_auto_from_type (&h_contract_terms),
GNUNET_PQ_query_param_timestamp (&pay_deadline),
GNUNET_PQ_query_param_timestamp (&refund_deadline),
(NULL == fulfillment_url)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (fulfillment_url),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("order_serial",
order_serial),
GNUNET_PQ_result_spec_end
};
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"insert_contract_terms",
params,
rs);
}
}
/**
* Update the contract terms stored for @a order_id. Note that some attributes are
* expected to be calculated inside of the function, like the hash of the
* contract terms (to be hashed), the creation_time and pay_deadline (to be
* obtained from the merchant_orders table). The "session_id" should be
* initially set to the empty string. The "fulfillment_url" and "refund_deadline"
* must be extracted from @a contract_terms.
*
* @param cls closure
* @param instance_id instance's identifier
* @param order_id order_id used to store
* @param contract_terms contract to store
* @return transaction status, #GNUNET_DB_STATUS_HARD_ERROR if @a contract_terms
* is malformed
*/
static enum GNUNET_DB_QueryStatus
postgres_update_contract_terms (void *cls,
const char *instance_id,
const char *order_id,
json_t *contract_terms)
{
struct PostgresClosure *pg = cls;
struct GNUNET_TIME_Timestamp pay_deadline;
struct GNUNET_TIME_Timestamp refund_deadline;
const char *fulfillment_url = NULL;
struct TALER_PrivateContractHashP h_contract_terms;
if (GNUNET_OK !=
TALER_JSON_contract_hash (contract_terms,
&h_contract_terms))
{
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_timestamp ("pay_deadline",
&pay_deadline),
GNUNET_JSON_spec_timestamp ("refund_deadline",
&refund_deadline),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("fulfillment_url",
&fulfillment_url),
NULL),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_data (NULL,
contract_terms,
spec);
if (GNUNET_OK != res)
{
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
}
check_connection (pg);
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
TALER_PQ_query_param_json (contract_terms),
GNUNET_PQ_query_param_auto_from_type (&h_contract_terms),
GNUNET_PQ_query_param_timestamp (&pay_deadline),
GNUNET_PQ_query_param_timestamp (&refund_deadline),
(NULL == fulfillment_url)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (fulfillment_url),
GNUNET_PQ_query_param_end
};
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_contract_terms",
params);
}
}
/**
* Delete information about a contract. Note that the transaction must
* enforce that the contract is not awaiting payment anymore AND was not
* paid, or is past the legal expiration.
*
* @param cls closure
* @param instance_id instance to delete order of
* @param order_id order to delete
* @param legal_expiration how long do we need to keep (paid) contracts on
* file for legal reasons (i.e. taxation)
* @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
* if locks prevent deletion OR order unknown
*/
static enum GNUNET_DB_QueryStatus
postgres_delete_contract_terms (void *cls,
const char *instance_id,
const char *order_id,
struct GNUNET_TIME_Relative legal_expiration)
{
struct PostgresClosure *pg = cls;
struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_relative_time (&legal_expiration),
GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"delete_contract_terms",
params);
}
/**
* Closure for #lookup_deposits_cb().
*/
struct LookupDepositsContext
{
/**
* Function to call with results.
*/
TALER_MERCHANTDB_DepositsCallback cb;
/**
* Closure for @e cls.
*/
void *cb_cls;
/**
* Plugin context.
*/
struct PostgresClosure *pg;
/**
* Transaction status (set).
*/
enum GNUNET_DB_QueryStatus qs;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param[in,out] cls of type `struct LookupDepositsContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_deposits_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupDepositsContext *ldc = cls;
struct PostgresClosure *pg = ldc->pg;
for (unsigned int i = 0; iqs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
ldc->cb (ldc->cb_cls,
exchange_url,
&coin_pub,
&amount_with_fee,
&deposit_fee,
&refund_fee,
&wire_fee);
GNUNET_PQ_cleanup_result (rs);
}
ldc->qs = num_results;
}
/**
* Lookup information about coins that were successfully deposited for a
* given contract.
*
* @param cls closure
* @param instance_id instance to lookup deposits for
* @param h_contract_terms proposal data's hashcode
* @param cb function to call with payment data
* @param cb_cls closure for @a cb
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_deposits (
void *cls,
const char *instance_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
TALER_MERCHANTDB_DepositsCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_end
};
struct LookupDepositsContext ldc = {
.cb = cb,
.cb_cls = cb_cls,
.pg = pg
};
enum GNUNET_DB_QueryStatus qs;
/* no preflight check here, run in its own transaction by the caller! */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Finding deposits for h_contract_terms '%s'\n",
GNUNET_h2s (&h_contract_terms->hash));
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_deposits",
params,
&lookup_deposits_cb,
&ldc);
if (qs <= 0)
return qs;
return ldc.qs;
}
/**
* Insert an exchange signing key into our database.
*
* @param cls closure
* @param master_pub exchange master public key used for @a master_sig
* @param exchange_pub exchange signing key to insert
* @param start_date when does the signing key become valid
* @param expire_date when does the signing key stop being used
* @param end_date when does the signing key become void as proof
* @param master_sig signature of @a master_pub over the @a exchange_pub and the dates
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_exchange_signkey (
void *cls,
const struct TALER_MasterPublicKeyP *master_pub,
const struct TALER_ExchangePublicKeyP *exchange_pub,
struct GNUNET_TIME_Timestamp start_date,
struct GNUNET_TIME_Timestamp expire_date,
struct GNUNET_TIME_Timestamp end_date,
const struct TALER_MasterSignatureP *master_sig)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (master_pub),
GNUNET_PQ_query_param_auto_from_type (exchange_pub),
GNUNET_PQ_query_param_timestamp (&start_date),
GNUNET_PQ_query_param_timestamp (&expire_date),
GNUNET_PQ_query_param_timestamp (&end_date),
GNUNET_PQ_query_param_auto_from_type (master_sig),
GNUNET_PQ_query_param_end
};
check_connection (pg);
postgres_preflight (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_exchange_signkey",
params);
}
/**
* Insert payment confirmation from the exchange into the database.
*
* @param cls closure
* @param instance_id instance to lookup deposits for
* @param deposit_timestamp time when the exchange generated the deposit confirmation
* @param h_contract_terms proposal data's hashcode
* @param coin_pub public key of the coin
* @param exchange_url URL of the exchange that issued @a coin_pub
* @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin
* @param wire_fee wire fee the exchange charges
* @param refund_fee fee the exchange charges to refund this coin
* @param h_wire hash of the wire details of the target account of the merchant
* @param exchange_sig signature from exchange that coin was accepted
* @param exchange_pub signgin key that was used for @a exchange_sig
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_deposit (
void *cls,
const char *instance_id,
struct GNUNET_TIME_Timestamp deposit_timestamp,
const struct TALER_PrivateContractHashP *h_contract_terms,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const char *exchange_url,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
const struct TALER_Amount *refund_fee,
const struct TALER_Amount *wire_fee,
const struct TALER_MerchantWireHashP *h_wire,
const struct TALER_ExchangeSignatureP *exchange_sig,
const struct TALER_ExchangePublicKeyP *exchange_pub)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_timestamp (&deposit_timestamp), /* $3 */
GNUNET_PQ_query_param_auto_from_type (coin_pub),
GNUNET_PQ_query_param_string (exchange_url),
TALER_PQ_query_param_amount (amount_with_fee), /* $6/$7 */
TALER_PQ_query_param_amount (deposit_fee), /* $8, $9 */
TALER_PQ_query_param_amount (refund_fee), /* $10, $11 */
TALER_PQ_query_param_amount (wire_fee), /* $12, $13 */
GNUNET_PQ_query_param_auto_from_type (h_wire), /* $14 */
GNUNET_PQ_query_param_auto_from_type (exchange_sig), /* $15 */
GNUNET_PQ_query_param_auto_from_type (exchange_pub), /* $16 */
GNUNET_PQ_query_param_end
};
/* no preflight check here, run in transaction by caller! */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Storing deposit for instance `%s' h_contract_terms `%s', coin_pub: `%s', amount_with_fee: %s\n",
instance_id,
GNUNET_h2s (&h_contract_terms->hash),
TALER_B2S (coin_pub),
TALER_amount2s (amount_with_fee));
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_deposit",
params);
}
/**
* Closure for #lookup_refunds_cb().
*/
struct LookupRefundsContext
{
/**
* Function to call for each refund.
*/
TALER_MERCHANTDB_RefundCallback rc;
/**
* Closure for @e rc.
*/
void *rc_cls;
/**
* Plugin context.
*/
struct PostgresClosure *pg;
/**
* Transaction result.
*/
enum GNUNET_DB_QueryStatus qs;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param cls of type `struct LookupRefundsContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_refunds_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupRefundsContext *lrc = cls;
struct PostgresClosure *pg = lrc->pg;
for (unsigned int i = 0; iqs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
lrc->rc (lrc->rc_cls,
&coin_pub,
&refund_amount);
GNUNET_PQ_cleanup_result (rs); /* technically useless here */
}
lrc->qs = num_results;
}
/**
* Obtain refunds associated with a contract.
*
* @param cls closure, typically a connection to the db
* @param instance_id instance to lookup refunds for
* @param h_contract_terms hash code of the contract
* @param rc function to call for each coin on which there is a refund
* @param rc_cls closure for @a rc
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_refunds (
void *cls,
const char *instance_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
TALER_MERCHANTDB_RefundCallback rc,
void *rc_cls)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_end
};
struct LookupRefundsContext lrc = {
.rc = rc,
.rc_cls = rc_cls,
.pg = pg
};
enum GNUNET_DB_QueryStatus qs;
/* no preflight check here, run in transaction by caller! */
TALER_LOG_DEBUG ("Looking for refund of h_contract_terms %s at `%s'\n",
GNUNET_h2s (&h_contract_terms->hash),
instance_id);
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_refunds",
params,
&lookup_refunds_cb,
&lrc);
if (0 >= qs)
return qs;
return lrc.qs;
}
/**
* Mark contract as paid and store the current @a session_id
* for which the contract was paid. Deletes the underlying order
* and marks the locked stocks of the order as sold.
*
* @param cls closure
* @param instance_id instance to mark contract as paid for
* @param h_contract_terms hash of the contract that is now paid
* @param session_id the session that paid the contract
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_mark_contract_paid (
void *cls,
const char *instance_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
const char *session_id)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_string (session_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_QueryParam uparams[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
/* Session ID must always be given by the caller. */
GNUNET_assert (NULL != session_id);
/* no preflight check here, run in transaction by caller! */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Marking h_contract_terms '%s' of %s as paid for session `%s'\n",
GNUNET_h2s (&h_contract_terms->hash),
instance_id,
session_id);
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"mark_contract_paid",
params);
if (qs <= 0)
return qs;
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"mark_inventory_sold",
uparams);
if (qs < 0)
return qs; /* 0: no inventory management, that's OK! */
/* ON DELETE CASCADE deletes from merchant_order_locks */
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"delete_completed_order",
uparams);
}
/**
* Function called during aborts to refund a coin. Marks the
* respective coin as refunded.
*
* @param cls closure
* @param instance_id instance to refund payment for
* @param h_contract_terms hash of the contract to refund coin for
* @param refund_timestamp timestamp of the refund
* @param coin_pub public key of the coin to refund (fully)
* @param reason text justifying the refund
* @return transaction status
* #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a coin_pub is unknown to us;
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid,
* regardless of whether it actually increased the refund
*/
static enum GNUNET_DB_QueryStatus
postgres_refund_coin (void *cls,
const char *instance_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
struct GNUNET_TIME_Timestamp refund_timestamp,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const char *reason)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_timestamp (&refund_timestamp),
GNUNET_PQ_query_param_auto_from_type (coin_pub),
GNUNET_PQ_query_param_string (reason),
GNUNET_PQ_query_param_end
};
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"refund_coin",
params);
}
/**
* Retrieve contract terms given its @a order_id
*
* @param cls closure
* @param instance_id instance's identifier
* @param order_id order to lookup contract for
* @param[out] h_contract_terms set to the hash of the contract.
* @param[out] paid set to the payment status of the contract
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_order_status (void *cls,
const char *instance_id,
const char *order_id,
struct TALER_PrivateContractHashP *
h_contract_terms,
bool *paid)
{
struct PostgresClosure *pg = cls;
uint8_t paid8;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
h_contract_terms),
GNUNET_PQ_result_spec_auto_from_type ("paid",
&paid8),
GNUNET_PQ_result_spec_end
};
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_order_status",
params,
rs);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
*paid = (0 != paid8);
else
*paid = false; /* just to be safe(r) */
return qs;
}
/**
* Retrieve contract terms given its @a order_serial
*
* @param cls closure
* @param instance_id instance's identifier
* @param order_serial serial ID of the order to look up
* @param[out] order_id set to ID of the order
* @param[out] h_contract_terms set to the hash of the contract.
* @param[out] paid set to the payment status of the contract
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_order_status_by_serial (void *cls,
const char *instance_id,
uint64_t order_serial,
char **order_id,
struct TALER_PrivateContractHashP *
h_contract_terms,
bool *paid)
{
struct PostgresClosure *pg = cls;
uint8_t paid8;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&order_serial),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
h_contract_terms),
GNUNET_PQ_result_spec_auto_from_type ("paid",
&paid8),
GNUNET_PQ_result_spec_string ("order_id",
order_id),
GNUNET_PQ_result_spec_end
};
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_order_status_by_serial",
params,
rs);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
*paid = (0 != paid8);
else
*paid = false; /* just to be safe(r) */
return qs;
}
/**
* Retrieve payment and wire status for a given @a order_serial and session ID.
*
* @param cls closure
* @param order_serial identifies the order
* @param session_id session for which to check the payment status, NULL for any
* @param[out] paid set to the payment status of the contract
* @param[out] wired set to the wire transfer status of the exchange payment
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_payment_status (void *cls,
uint64_t order_serial,
const char *session_id,
bool *paid,
bool *wired)
{
struct PostgresClosure *pg = cls;
uint8_t paid8;
uint8_t wired8;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("paid",
&paid8),
GNUNET_PQ_result_spec_auto_from_type ("wired",
&wired8),
GNUNET_PQ_result_spec_end
};
check_connection (pg);
if (NULL == session_id)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&order_serial),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_payment_status",
params,
rs);
}
else
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&order_serial),
GNUNET_PQ_query_param_string (session_id),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_payment_status_session_id",
params,
rs);
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
*paid = (0 != paid8);
*wired = (0 != wired8);
}
else
{
*paid = false; /* just to be safe(r) */
*wired = false; /* just to be safe(r) */
}
return qs;
}
/**
* Closure for lookup_deposits_by_order_cb().
*/
struct LookupDepositsByOrderContext
{
/**
* Plugin context.
*/
struct PostgresClosure *pg;
/**
* Function to call with all results.
*/
TALER_MERCHANTDB_DepositedCoinsCallback cb;
/**
* Closure for @e cb.
*/
void *cb_cls;
/**
* Set to the query result.
*/
enum GNUNET_DB_QueryStatus qs;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param cls of type `struct LookupDepositsByOrderContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_deposits_by_order_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupDepositsByOrderContext *ldoc = cls;
struct PostgresClosure *pg = ldoc->pg;
for (unsigned int i = 0; iqs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
ldoc->cb (ldoc->cb_cls,
deposit_serial,
exchange_url,
&h_wire,
&amount_with_fee,
&deposit_fee,
&coin_pub);
GNUNET_PQ_cleanup_result (rs); /* technically useless here */
}
ldoc->qs = num_results;
}
/**
* Retrieve details about coins that were deposited for an order.
*
* @param cls closure
* @param order_serial identifies the order
* @param cb function to call for each deposited coin
* @param cb_cls closure for @a cb
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_deposits_by_order (void *cls,
uint64_t order_serial,
TALER_MERCHANTDB_DepositedCoinsCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupDepositsByOrderContext ldoc = {
.pg = pg,
.cb = cb,
.cb_cls = cb_cls
};
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&order_serial),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_deposits_by_order",
params,
&lookup_deposits_by_order_cb,
&ldoc);
if (qs < 0)
return qs;
return ldoc.qs;
}
/**
* Closure for lookup_deposits_by_order_cb().
*/
struct LookupTransferDetailsByOrderContext
{
/**
* Plugin context.
*/
struct PostgresClosure *pg;
/**
* Function to call with all results.
*/
TALER_MERCHANTDB_OrderTransferDetailsCallback cb;
/**
* Closure for @e cb.
*/
void *cb_cls;
/**
* Set to the query result.
*/
enum GNUNET_DB_QueryStatus qs;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param cls of type `struct LookupTransferDetailsByOrderContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_transfer_details_by_order_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupTransferDetailsByOrderContext *ltdo = cls;
struct PostgresClosure *pg = ltdo->pg;
for (unsigned int i = 0; iqs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
ltdo->cb (ltdo->cb_cls,
&wtid,
exchange_url,
execution_time,
&deposit_value,
&deposit_fee,
(0 != transfer_confirmed));
GNUNET_PQ_cleanup_result (rs); /* technically useless here */
}
ltdo->qs = num_results;
}
/**
* Retrieve wire transfer details for all deposits associated with
* a given @a order_serial.
*
* @param cls closure
* @param order_serial identifies the order
* @param cb function called with the wire transfer details
* @param cb_cls closure for @a cb
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_transfer_details_by_order (
void *cls,
uint64_t order_serial,
TALER_MERCHANTDB_OrderTransferDetailsCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupTransferDetailsByOrderContext ltdo = {
.pg = pg,
.cb = cb,
.cb_cls = cb_cls
};
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&order_serial),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
"lookup_transfer_details_by_order",
params,
&lookup_transfer_details_by_order_cb,
<do);
if (qs < 0)
return qs;
return ltdo.qs;
}
/**
* Insert wire transfer details for a deposit.
*
* @param cls closure
* @param deposit_serial serial number of the deposit
* @param dd deposit transfer data from the exchange to store
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_deposit_to_transfer (
void *cls,
uint64_t deposit_serial,
const struct TALER_EXCHANGE_DepositData *dd)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&deposit_serial),
TALER_PQ_query_param_amount (&dd->coin_contribution),
GNUNET_PQ_query_param_timestamp (&dd->execution_time),
GNUNET_PQ_query_param_auto_from_type (&dd->exchange_sig),
GNUNET_PQ_query_param_auto_from_type (&dd->exchange_pub),
GNUNET_PQ_query_param_auto_from_type (&dd->wtid),
GNUNET_PQ_query_param_end
};
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_deposit_to_transfer",
params);
}
/**
* Set 'wired' status for an order to 'true'.
*
* @param cls closure
* @param order_serial serial number of the order
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_mark_order_wired (void *cls,
uint64_t order_serial)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&order_serial),
GNUNET_PQ_query_param_end
};
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"mark_order_wired",
params);
}
/**
* Closure for #process_refund_cb().
*/
struct FindRefundContext
{
/**
* Plugin context.
*/
struct PostgresClosure *pg;
/**
* Updated to reflect total amount refunded so far.
*/
struct TALER_Amount refunded_amount;
/**
* Set to the largest refund transaction ID encountered.
*/
uint64_t max_rtransaction_id;
/**
* Set to true on hard errors.
*/
bool err;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param cls closure, our `struct FindRefundContext`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
process_refund_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct FindRefundContext *ictx = cls;
struct PostgresClosure *pg = ictx->pg;
for (unsigned int i = 0; ierr = true;
return;
}
if (0 >
TALER_amount_add (&ictx->refunded_amount,
&ictx->refunded_amount,
&acc))
{
GNUNET_break (0);
ictx->err = true;
return;
}
ictx->max_rtransaction_id = GNUNET_MAX (ictx->max_rtransaction_id,
rtransaction_id);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Found refund of %s\n",
TALER_amount2s (&acc));
}
}
/**
* Closure for #process_deposits_for_refund_cb().
*/
struct InsertRefundContext
{
/**
* Used to provide a connection to the db
*/
struct PostgresClosure *pg;
/**
* Amount to which increase the refund for this contract
*/
const struct TALER_Amount *refund;
/**
* Human-readable reason behind this refund
*/
const char *reason;
/**
* Transaction status code.
*/
enum TALER_MERCHANTDB_RefundStatus rs;
};
/**
* Data extracted per coin.
*/
struct RefundCoinData
{
/**
* Public key of a coin.
*/
struct TALER_CoinSpendPublicKeyP coin_pub;
/**
* Amount deposited for this coin.
*/
struct TALER_Amount deposited_with_fee;
/**
* Amount refunded already for this coin.
*/
struct TALER_Amount refund_amount;
/**
* Order serial (actually not really per-coin).
*/
uint64_t order_serial;
/**
* Maximum rtransaction_id for this coin so far.
*/
uint64_t max_rtransaction_id;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param cls closure, our `struct InsertRefundContext`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
process_deposits_for_refund_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct InsertRefundContext *ctx = cls;
struct PostgresClosure *pg = ctx->pg;
struct TALER_Amount current_refund;
struct RefundCoinData rcd[GNUNET_NZL (num_results)];
struct GNUNET_TIME_Timestamp now;
now = GNUNET_TIME_timestamp_get ();
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (ctx->refund->currency,
¤t_refund));
memset (rcd, 0, sizeof (rcd));
/* Pass 1: Collect amount of existing refunds into current_refund.
* Also store existing refunded amount for each deposit in deposit_refund. */
for (unsigned int i = 0; irs = TALER_MERCHANTDB_RS_HARD_ERROR;
return;
}
{
enum GNUNET_DB_QueryStatus ires;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub),
GNUNET_PQ_query_param_uint64 (&rcd[i].order_serial),
GNUNET_PQ_query_param_end
};
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (ctx->refund->currency,
&ictx.refunded_amount));
ires = GNUNET_PQ_eval_prepared_multi_select (ctx->pg->conn,
"find_refunds_by_coin",
params,
&process_refund_cb,
&ictx);
if ( (ictx.err) ||
(GNUNET_DB_STATUS_HARD_ERROR == ires) )
{
GNUNET_break (0);
ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
return;
}
if (GNUNET_DB_STATUS_SOFT_ERROR == ires)
{
ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
return;
}
}
if (0 >
TALER_amount_add (¤t_refund,
¤t_refund,
&ictx.refunded_amount))
{
GNUNET_break (0);
ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
return;
}
rcd[i].refund_amount = ictx.refunded_amount;
rcd[i].max_rtransaction_id = ictx.max_rtransaction_id;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Existing refund for coin %s is %s\n",
TALER_B2S (&rcd[i].coin_pub),
TALER_amount2s (&ictx.refunded_amount));
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Total existing refund is %s\n",
TALER_amount2s (¤t_refund));
/* stop immediately if we are 'done' === amount already
* refunded. */
if (0 >= TALER_amount_cmp (ctx->refund,
¤t_refund))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Existing refund of %s at or above requested refund. Finished early.\n",
TALER_amount2s (¤t_refund));
ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
return;
}
/* Phase 2: Try to increase current refund until it matches desired refund */
for (unsigned int i = 0; i
TALER_amount_subtract (&left,
&rcd[i].deposited_with_fee,
&rcd[i].refund_amount))
{
GNUNET_break (0);
ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
return;
}
if ( (0 == left.value) &&
(0 == left.fraction) )
{
/* coin was fully refunded, move to next coin */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Coin %s fully refunded, moving to next coin\n",
TALER_B2S (&rcd[i].coin_pub));
continue;
}
rcd[i].max_rtransaction_id++;
/* How much of the refund is still to be paid back? */
if (0 >
TALER_amount_subtract (&remaining_refund,
ctx->refund,
¤t_refund))
{
GNUNET_break (0);
ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
return;
}
/* By how much will we increase the refund for this coin? */
if (0 >= TALER_amount_cmp (&remaining_refund,
&left))
{
/* remaining_refund <= left */
increment = &remaining_refund;
}
else
{
increment = &left;
}
if (0 >
TALER_amount_add (¤t_refund,
¤t_refund,
increment))
{
GNUNET_break (0);
ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
return;
}
/* actually run the refund */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Coin %s deposit amount is %s\n",
TALER_B2S (&rcd[i].coin_pub),
TALER_amount2s (&rcd[i].deposited_with_fee));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Coin %s refund will be incremented by %s\n",
TALER_B2S (&rcd[i].coin_pub),
TALER_amount2s (increment));
{
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&rcd[i].order_serial),
GNUNET_PQ_query_param_uint64 (&rcd[i].max_rtransaction_id), /* already inc'ed */
GNUNET_PQ_query_param_timestamp (&now),
GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub),
GNUNET_PQ_query_param_string (ctx->reason),
TALER_PQ_query_param_amount (increment),
GNUNET_PQ_query_param_end
};
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_refund",
params);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
return;
default:
ctx->rs = (enum TALER_MERCHANTDB_RefundStatus) qs;
break;
}
}
/* stop immediately if we are done */
if (0 == TALER_amount_cmp (ctx->refund,
¤t_refund))
{
ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
return;
}
}
/**
* We end up here if not all of the refund has been covered.
* Although this should be checked as the business should never
* issue a refund bigger than the contract's actual price, we cannot
* rely upon the frontend being correct.
*///
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"The refund of %s is bigger than the order's value\n",
TALER_amount2s (ctx->refund));
ctx->rs = TALER_MERCHANTDB_RS_TOO_HIGH;
}
/**
* Function called when some backoffice staff decides to award or
* increase the refund on an existing contract. This function
* MUST be called from within a transaction scope setup by the
* caller as it executes multiple SQL statements.
*
* @param cls closure
* @param instance_id instance identifier
* @param order_id the order to increase the refund for
* @param refund maximum refund to return to the customer for this contract
* @param reason 0-terminated UTF-8 string giving the reason why the customer
* got a refund (free form, business-specific)
* @return transaction status
* #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a refund is ABOVE the amount we
* were originally paid and thus the transaction failed;
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid,
* regardless of whether it actually increased the refund beyond
* what was already refunded (idempotency!)
*/
static enum TALER_MERCHANTDB_RefundStatus
postgres_increase_refund (void *cls,
const char *instance_id,
const char *order_id,
const struct TALER_Amount *refund,
const char *reason)
{
struct PostgresClosure *pg = cls;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_end
};
struct InsertRefundContext ctx = {
.pg = pg,
.refund = refund,
.reason = reason
};
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Asked to refund %s on order %s\n",
TALER_amount2s (refund),
order_id);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"find_deposits_for_refund",
params,
&process_deposits_for_refund_cb,
&ctx);
switch (qs)
{
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
/* never paid, means we clearly cannot refund anything */
return TALER_MERCHANTDB_RS_NO_SUCH_ORDER;
case GNUNET_DB_STATUS_SOFT_ERROR:
return TALER_MERCHANTDB_RS_SOFT_ERROR;
case GNUNET_DB_STATUS_HARD_ERROR:
return TALER_MERCHANTDB_RS_HARD_ERROR;
default:
/* Got one or more deposits */
return ctx.rs;
}
}
/**
* Closure for #lookup_refunds_detailed_cb().
*/
struct LookupRefundsDetailedContext
{
/**
* Function to call for each refund.
*/
TALER_MERCHANTDB_RefundDetailCallback rc;
/**
* Closure for @e rc.
*/
void *rc_cls;
/**
* Plugin context.
*/
struct PostgresClosure *pg;
/**
* Transaction result.
*/
enum GNUNET_DB_QueryStatus qs;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param cls of type `struct GetRefundsContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_refunds_detailed_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupRefundsDetailedContext *lrdc = cls;
struct PostgresClosure *pg = lrdc->pg;
for (unsigned int i = 0; iqs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
lrdc->rc (lrdc->rc_cls,
refund_serial,
timestamp,
&coin_pub,
exchange_url,
rtransaction_id,
reason,
&refund_amount,
0 != pending8);
GNUNET_PQ_cleanup_result (rs);
}
lrdc->qs = num_results;
}
/**
* Obtain detailed refund data associated with a contract.
*
* @param cls closure, typically a connection to the db
* @param instance_id instance to lookup refunds for
* @param h_contract_terms hash code of the contract
* @param rc function to call for each coin on which there is a refund
* @param rc_cls closure for @a rc
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_refunds_detailed (
void *cls,
const char *instance_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
TALER_MERCHANTDB_RefundDetailCallback rc,
void *rc_cls)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_end
};
struct LookupRefundsDetailedContext lrdc = {
.rc = rc,
.rc_cls = rc_cls,
.pg = pg
};
enum GNUNET_DB_QueryStatus qs;
/* no preflight check here, run in transaction by caller! */
TALER_LOG_DEBUG ("Looking for refund %s + %s\n",
GNUNET_h2s (&h_contract_terms->hash),
instance_id);
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_refunds_detailed",
params,
&lookup_refunds_detailed_cb,
&lrdc);
if (0 >= qs)
return qs;
return lrdc.qs;
}
/**
* Insert refund proof data from the exchange into the database.
*
* @param cls closure
* @param refund_serial serial number of the refund
* @param exchange_sig signature from exchange that coin was refunded
* @param exchange_pub signing key that was used for @a exchange_sig
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_refund_proof (
void *cls,
uint64_t refund_serial,
const struct TALER_ExchangeSignatureP *exchange_sig,
const struct TALER_ExchangePublicKeyP *exchange_pub)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&refund_serial),
GNUNET_PQ_query_param_auto_from_type (exchange_sig),
GNUNET_PQ_query_param_auto_from_type (exchange_pub),
GNUNET_PQ_query_param_end
};
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_refund_proof",
params);
}
/**
* Lookup refund proof data.
*
* @param cls closure
* @param refund_serial serial number of the refund
* @param[out] exchange_sig set to signature from exchange
* @param[out] exchange_pub signing key that was used for @a exchange_sig
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_refund_proof (void *cls,
uint64_t refund_serial,
struct TALER_ExchangeSignatureP *exchange_sig,
struct TALER_ExchangePublicKeyP *exchange_pub)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&refund_serial),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("exchange_sig",
exchange_sig),
GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
exchange_pub),
GNUNET_PQ_result_spec_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_refund_proof",
params,
rs);
}
/**
* Retrieve the order ID that was used to pay for a resource within a session.
*
* @param cls closure
* @param instance_id identifying the instance
* @param fulfillment_url URL that canonically identifies the resource
* being paid for
* @param session_id session id
* @param[out] order_id where to store the order ID that was used when
* paying for the resource URL
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
postgres_lookup_order_by_fulfillment (void *cls,
const char *instance_id,
const char *fulfillment_url,
const char *session_id,
char **order_id)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (fulfillment_url),
GNUNET_PQ_query_param_string (session_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("order_id",
order_id),
GNUNET_PQ_result_spec_end
};
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_order_by_fulfillment",
params,
rs);
}
/**
* Insert information about a wire transfer the merchant has received.
*
* @param cls closure
* @param instance_id the instance that received the transfer
* @param exchange_url which exchange made the transfer
* @param wtid identifier of the wire transfer
* @param credit_amount how much did we receive
* @param payto_uri what is the merchant's bank account that received the transfer
* @param confirmed whether the transfer was confirmed by the merchant or
* was merely claimed by the exchange at this point
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_transfer (
void *cls,
const char *instance_id,
const char *exchange_url,
const struct TALER_WireTransferIdentifierRawP *wtid,
const struct TALER_Amount *credit_amount,
const char *payto_uri,
bool confirmed)
{
struct PostgresClosure *pg = cls;
uint8_t confirmed8 = confirmed;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_auto_from_type (wtid),
TALER_PQ_query_param_amount (credit_amount),
GNUNET_PQ_query_param_string (payto_uri),
GNUNET_PQ_query_param_auto_from_type (&confirmed8),
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_transfer",
params);
}
/**
* Delete information about a transfer. Note that transfers
* confirmed by the exchange cannot be deleted anymore.
*
* @param cls closure
* @param instance_id instance to delete transfer of
* @param transfer_serial_id transfer to delete
* @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
* if deletion is prohibited OR transfer is unknown
*/
static enum GNUNET_DB_QueryStatus
postgres_delete_transfer (void *cls,
const char *instance_id,
uint64_t transfer_serial_id)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&transfer_serial_id),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"delete_transfer",
params);
}
/**
* Check if information about a transfer exists with the
* backend. Returns no data, only the query status.
*
* @param cls closure
* @param instance_id instance to delete transfer of
* @param transfer_serial_id transfer to delete
* @return DB status code, #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
* if the transfer record exists
*/
static enum GNUNET_DB_QueryStatus
postgres_check_transfer_exists (void *cls,
const char *instance_id,
uint64_t transfer_serial_id)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&transfer_serial_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"check_transfer_exists",
params,
rs);
}
/**
* Lookup account serial by payto URI.
*
* @param cls closure
* @param instance_id instance to lookup the account from
* @param payto_uri what is the merchant's bank account to lookup
* @param[out] account_serial serial number of the account
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_account (void *cls,
const char *instance_id,
const char *payto_uri,
uint64_t *account_serial)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (payto_uri),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("account_serial",
account_serial),
GNUNET_PQ_result_spec_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_account",
params,
rs);
}
/**
* Insert information about a wire transfer the merchant has received.
*
* @param cls closure
* @param instance_id instance to provide transfer details for
* @param exchange_url which exchange made the transfer
* @param payto_uri what is the merchant's bank account that received the transfer
* @param wtid identifier of the wire transfer
* @param td transfer details to store
* @return transaction status,
* #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the @a wtid and @a exchange_uri are not known for this @a instance_id
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT on success
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_transfer_details (
void *cls,
const char *instance_id,
const char *exchange_url,
const char *payto_uri,
const struct TALER_WireTransferIdentifierRawP *wtid,
const struct TALER_EXCHANGE_TransferData *td)
{
struct PostgresClosure *pg = cls;
enum GNUNET_DB_QueryStatus qs;
uint64_t credit_serial;
unsigned int retries;
retries = 0;
check_connection (pg);
RETRY:
if (MAX_RETRIES < ++retries)
return GNUNET_DB_STATUS_SOFT_ERROR;
if (GNUNET_OK !=
postgres_start_read_committed (pg,
"insert transfer details"))
{
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
/* lookup credit serial */
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_string (payto_uri),
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (wtid),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("credit_serial",
&credit_serial),
GNUNET_PQ_result_spec_end
};
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_credit_serial",
params,
rs);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
postgres_rollback (pg);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
goto RETRY;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"'lookup_credit_serial' for account %s and amount %s failed with status %d\n",
payto_uri,
TALER_amount2s (&td->total_amount),
qs);
return qs;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
postgres_rollback (pg);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"'lookup_credit_serial' for account %s failed with transfer unknown\n",
payto_uri);
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
}
}
/* update merchant_transfer_signatures table */
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&credit_serial),
TALER_PQ_query_param_amount (&td->total_amount),
TALER_PQ_query_param_amount (&td->wire_fee),
GNUNET_PQ_query_param_timestamp (&td->execution_time),
GNUNET_PQ_query_param_auto_from_type (&td->exchange_sig),
GNUNET_PQ_query_param_auto_from_type (&td->exchange_pub),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_transfer_signature",
params);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
postgres_rollback (pg);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
goto RETRY;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"'insert_transfer_signature' failed with status %d\n",
qs);
return qs;
}
if (0 == qs)
{
postgres_rollback (pg);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"'insert_transfer_signature' failed with status %d\n",
qs);
return GNUNET_DB_STATUS_HARD_ERROR;
}
}
/* Update transfer-coin association table */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Updating transfer-coin association table\n");
for (unsigned int i = 0; idetails_length; i++)
{
const struct TALER_TrackTransferDetails *d = &td->details[i];
uint64_t i64 = (uint64_t) i;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&credit_serial),
GNUNET_PQ_query_param_uint64 (&i64),
TALER_PQ_query_param_amount (&d->coin_value),
TALER_PQ_query_param_amount (&d->coin_fee), /* deposit fee */
GNUNET_PQ_query_param_auto_from_type (&d->coin_pub),
GNUNET_PQ_query_param_auto_from_type (&d->h_contract_terms),
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_transfer_to_coin_mapping",
params);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
postgres_rollback (pg);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
goto RETRY;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"'insert_transfer_to_coin_mapping' failed with status %d\n",
qs);
return qs;
}
if (0 == qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"'insert_transfer_to_coin_mapping' failed at %u: deposit unknown\n",
i);
}
}
/* Update merchant_contract_terms 'wired' status: for all coins
that were wired, set the respective order's "wired" status to
true, *if* all other deposited coins associated with that order
have also been wired (this time or earlier) */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Updating contract terms 'wired' status\n");
for (unsigned int i = 0; idetails_length; i++)
{
const struct TALER_TrackTransferDetails *d = &td->details[i];
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (&d->coin_pub),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_wired_by_coin_pub",
params);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
postgres_rollback (pg);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
goto RETRY;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"'update_wired_by_coin_pub' failed with status %d\n",
qs);
return qs;
}
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Committing transaction...\n");
qs = postgres_commit (pg);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
goto RETRY;
return qs;
}
/**
* Obtain information about wire fees charged by an exchange,
* including signature (so we have proof).
*
* @param cls closure
* @param master_pub public key of the exchange
* @param wire_method the wire method
* @param contract_date date of the contract to use for the lookup
* @param[out] fees wire fees charged
* @param[out] start_date start of fee being used
* @param[out] end_date end of fee being used
* @param[out] master_sig signature of exchange over fee structure
* @return transaction status code
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_wire_fee (void *cls,
const struct TALER_MasterPublicKeyP *master_pub,
const char *wire_method,
struct GNUNET_TIME_Timestamp contract_date,
struct TALER_WireFeeSet *fees,
struct GNUNET_TIME_Timestamp *start_date,
struct GNUNET_TIME_Timestamp *end_date,
struct TALER_MasterSignatureP *master_sig)
{
struct PostgresClosure *pg = cls;
struct GNUNET_HashCode h_wire_method;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (master_pub),
GNUNET_PQ_query_param_auto_from_type (&h_wire_method),
GNUNET_PQ_query_param_timestamp (&contract_date),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
&fees->wire),
TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
&fees->closing),
GNUNET_PQ_result_spec_timestamp ("start_date",
start_date),
GNUNET_PQ_result_spec_timestamp ("end_date",
end_date),
GNUNET_PQ_result_spec_auto_from_type ("master_sig",
master_sig),
GNUNET_PQ_result_spec_end
};
check_connection (pg);
GNUNET_CRYPTO_hash (wire_method,
strlen (wire_method) + 1,
&h_wire_method);
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_wire_fee",
params,
rs);
}
/**
* Closure for #lookup_deposits_by_contract_and_coin_cb().
*/
struct LookupDepositsByCnCContext
{
/**
* Function to call for each deposit.
*/
TALER_MERCHANTDB_CoinDepositCallback cb;
/**
* Closure for @e cb.
*/
void *cb_cls;
/**
* Plugin context.
*/
struct PostgresClosure *pg;
/**
* Transaction result.
*/
enum GNUNET_DB_QueryStatus qs;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param cls of type `struct LookupDepositsByCnCContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_deposits_by_contract_and_coin_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupDepositsByCnCContext *ldcc = cls;
struct PostgresClosure *pg = ldcc->pg;
for (unsigned int i = 0; iqs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
ldcc->cb (ldcc->cb_cls,
exchange_url,
&amount_with_fee,
&deposit_fee,
&refund_fee,
&wire_fee,
&h_wire,
deposit_timestamp,
refund_deadline,
&exchange_sig,
&exchange_pub);
GNUNET_PQ_cleanup_result (rs);
}
ldcc->qs = num_results;
}
/**
* Lookup information about coin payments by @a h_contract_terms and
* @a coin_pub.
*
* @param cls closure
* @param instance_id instance to lookup payments for
* @param h_contract_terms proposal data's hashcode
* @param coin_pub public key to use for the search
* @param cb function to call with payment data
* @param cb_cls closure for @a cb
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_deposits_by_contract_and_coin (
void *cls,
const char *instance_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
TALER_MERCHANTDB_CoinDepositCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_auto_from_type (coin_pub),
GNUNET_PQ_query_param_end
};
struct LookupDepositsByCnCContext ldcc = {
.cb = cb,
.cb_cls = cb_cls,
.pg = pg
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
"lookup_deposits_by_contract_and_coin",
params,
&lookup_deposits_by_contract_and_coin_cb,
&ldcc);
if (0 >= qs)
return qs;
return ldcc.qs;
}
/**
* Lookup transfer status.
*
* @param cls closure
* @param instance_id at which instance should we resolve the transfer
* @param exchange_url the exchange that made the transfer
* @param wtid wire transfer subject
* @param[out] total_amount amount that was debited from our
* aggregate balance at the exchange (in total, sum of
* the wire transfer amount and the @a wire_fee)
* @param[out] wire_fee the wire fee the exchange charged (only set if @a have_exchange_sig is true)
* @param[out] exchange_amount the amount the exchange claims was transferred (only set if @a have_exchange_sig is true)
* @param[out] execution_time when the transfer was executed by the exchange (only set if @a have_exchange_sig is true)
* @param[out] have_exchange_sig do we have a response from the exchange about this transfer
* @param[out] verified did we confirm the transfer was OK
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_transfer (
void *cls,
const char *instance_id,
const char *exchange_url,
const struct TALER_WireTransferIdentifierRawP *wtid,
struct TALER_Amount *total_amount,
struct TALER_Amount *wire_fee,
struct TALER_Amount *exchange_amount,
struct GNUNET_TIME_Timestamp *execution_time,
bool *have_exchange_sig,
bool *verified)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_auto_from_type (wtid),
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
uint8_t verified8;
/** Amount we got actually credited, _excludes_ the wire fee */
bool no_sig;
struct TALER_Amount credit_amount;
struct GNUNET_PQ_ResultSpec rs[] = {
TALER_PQ_RESULT_SPEC_AMOUNT ("credit_amount",
&credit_amount),
GNUNET_PQ_result_spec_allow_null (
TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
wire_fee),
&no_sig),
GNUNET_PQ_result_spec_allow_null (
TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_amount",
exchange_amount),
NULL),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_timestamp ("execution_time",
execution_time),
NULL),
GNUNET_PQ_result_spec_auto_from_type ("verified",
&verified8),
GNUNET_PQ_result_spec_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
*execution_time = GNUNET_TIME_UNIT_ZERO_TS;
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_transfer",
params,
rs);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Lookup transfer returned %d\n",
qs);
if (qs > 0)
{
*have_exchange_sig = ! no_sig;
*verified = (0 != verified8);
if ( (! no_sig) &&
(0 >
TALER_amount_add (total_amount,
&credit_amount,
wire_fee)) )
{
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
}
else
{
*verified = false;
*have_exchange_sig = false;
}
return qs;
}
/**
* Set transfer status to verified.
*
* @param cls closure
* @param exchange_url the exchange that made the transfer
* @param wtid wire transfer subject
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_set_transfer_status_to_verified (
void *cls,
const char *exchange_url,
const struct TALER_WireTransferIdentifierRawP *wtid)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (wtid),
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (
pg->conn,
"set_transfer_status_to_verified",
params);
}
/**
* Closure for #lookup_transfer_summary_cb().
*/
struct LookupTransferSummaryContext
{
/**
* Function to call for each order that was aggregated.
*/
TALER_MERCHANTDB_TransferSummaryCallback cb;
/**
* Closure for @e cb.
*/
void *cb_cls;
/**
* Plugin context.
*/
struct PostgresClosure *pg;
/**
* Transaction result.
*/
enum GNUNET_DB_QueryStatus qs;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param cls of type `struct LookupTransferSummaryContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_transfer_summary_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupTransferSummaryContext *ltdc = cls;
struct PostgresClosure *pg = ltdc->pg;
for (unsigned int i = 0; iqs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
ltdc->cb (ltdc->cb_cls,
order_id,
&deposit_value,
&deposit_fee);
GNUNET_PQ_cleanup_result (rs);
}
ltdc->qs = num_results;
}
/**
* Lookup transfer summary.
*
* @param cls closure
* @param exchange_url the exchange that made the transfer
* @param wtid wire transfer subject
* @param cb function to call with detailed transfer data
* @param cb_cls closure for @a cb
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_transfer_summary (
void *cls,
const char *exchange_url,
const struct TALER_WireTransferIdentifierRawP *wtid,
TALER_MERCHANTDB_TransferSummaryCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_auto_from_type (wtid),
GNUNET_PQ_query_param_end
};
struct LookupTransferSummaryContext ltdc = {
.cb = cb,
.cb_cls = cb_cls,
.pg = pg
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
"lookup_transfer_summary",
params,
&lookup_transfer_summary_cb,
<dc);
if (0 >= qs)
return qs;
return ltdc.qs;
}
/**
* Closure for #lookup_transfer_details_cb().
*/
struct LookupTransferDetailsContext
{
/**
* Function to call for each order that was aggregated.
*/
TALER_MERCHANTDB_TransferDetailsCallback cb;
/**
* Closure for @e cb.
*/
void *cb_cls;
/**
* Plugin context.
*/
struct PostgresClosure *pg;
/**
* Transaction result.
*/
enum GNUNET_DB_QueryStatus qs;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param cls of type `struct LookupTransferDetailsContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_transfer_details_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupTransferDetailsContext *ltdc = cls;
struct PostgresClosure *pg = ltdc->pg;
for (unsigned int i = 0; iqs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
ltdc->cb (ltdc->cb_cls,
(unsigned int) current_offset,
&ttd);
GNUNET_PQ_cleanup_result (rs);
}
ltdc->qs = num_results;
}
/**
* Lookup transfer details.
*
* @param cls closure
* @param exchange_url the exchange that made the transfer
* @param wtid wire transfer subject
* @param cb function to call with detailed transfer data
* @param cb_cls closure for @a cb
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_transfer_details (
void *cls,
const char *exchange_url,
const struct TALER_WireTransferIdentifierRawP *wtid,
TALER_MERCHANTDB_TransferDetailsCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_auto_from_type (wtid),
GNUNET_PQ_query_param_end
};
struct LookupTransferDetailsContext ltdc = {
.cb = cb,
.cb_cls = cb_cls,
.pg = pg
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
"lookup_transfer_details",
params,
&lookup_transfer_details_cb,
<dc);
if (0 >= qs)
return qs;
return ltdc.qs;
}
/**
* Closure for #lookup_transfers_cb().
*/
struct LookupTransfersContext
{
/**
* Function to call on results.
*/
TALER_MERCHANTDB_TransferCallback cb;
/**
* Closure for @e cb.
*/
void *cb_cls;
/**
* Postgres context.
*/
struct PostgresClosure *pg;
/**
* Transaction status (set).
*/
enum GNUNET_DB_QueryStatus qs;
/**
* Filter to apply by verification status.
*/
enum TALER_EXCHANGE_YesNoAll verified;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param cls of type `struct LookupTransfersContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_transfers_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupTransfersContext *ltc = cls;
struct PostgresClosure *pg = ltc->pg;
for (unsigned int i = 0; iqs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
if (0 == verified8)
verified = TALER_EXCHANGE_YNA_NO;
else
verified = TALER_EXCHANGE_YNA_YES;
if ( (ltc->verified == TALER_EXCHANGE_YNA_ALL) ||
(ltc->verified == verified) )
{
ltc->cb (ltc->cb_cls,
&credit_amount,
&wtid,
payto_uri,
exchange_url,
transfer_serial_id,
execution_time,
TALER_EXCHANGE_YNA_YES == verified,
0 != confirmed8);
}
GNUNET_PQ_cleanup_result (rs);
}
ltc->qs = num_results;
}
/**
* Lookup transfers. Note that filtering by @a verified status is done
* outside of SQL, as we already have 8 prepared statements and adding
* a filter on verified would further double the number of statements for
* a likely rather ineffective filter. So we apply that filter in
* #lookup_transfers_cb().
*
* @param cls closure
* @param instance_id instance to lookup payments for
* @param payto_uri account that we are interested in transfers to
* @param before timestamp for the earliest transfer we care about
* @param after timestamp for the last transfer we care about
* @param limit number of entries to return, negative for descending in execution time,
* positive for ascending in execution time
* @param offset transfer_serial number of the transfer we want to offset from
* @param verified filter transfers by verification status
* @param cb function to call with detailed transfer data
* @param cb_cls closure for @a cb
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_transfers (void *cls,
const char *instance_id,
const char *payto_uri,
struct GNUNET_TIME_Timestamp before,
struct GNUNET_TIME_Timestamp after,
int64_t limit,
uint64_t offset,
enum TALER_EXCHANGE_YesNoAll verified,
TALER_MERCHANTDB_TransferCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
struct LookupTransfersContext ltc = {
.cb = cb,
.cb_cls = cb_cls,
.pg = pg,
.verified = verified
};
enum GNUNET_DB_QueryStatus qs;
bool by_time;
by_time = ( (! GNUNET_TIME_absolute_is_never (before.abs_time)) ||
(! GNUNET_TIME_absolute_is_zero (after.abs_time)) );
check_connection (pg);
if (by_time)
{
if (NULL != payto_uri)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_timestamp (&before),
GNUNET_PQ_query_param_timestamp (&after),
GNUNET_PQ_query_param_uint64 (&offset),
GNUNET_PQ_query_param_uint64 (&plimit),
GNUNET_PQ_query_param_string (payto_uri),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
(limit > 0)
? "lookup_transfers_time_payto_asc"
: "lookup_transfers_time_payto_desc",
params,
&lookup_transfers_cb,
<c);
}
else
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_timestamp (&before),
GNUNET_PQ_query_param_timestamp (&after),
GNUNET_PQ_query_param_uint64 (&offset),
GNUNET_PQ_query_param_uint64 (&plimit),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
(limit > 0)
? "lookup_transfers_time_asc"
: "lookup_transfers_time_desc",
params,
&lookup_transfers_cb,
<c);
}
}
else
{
if (NULL != payto_uri)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&offset),
GNUNET_PQ_query_param_uint64 (&plimit),
GNUNET_PQ_query_param_string (payto_uri),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
(limit > 0)
? "lookup_transfers_payto_asc"
: "lookup_transfers_payto_desc",
params,
&lookup_transfers_cb,
<c);
}
else
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&offset),
GNUNET_PQ_query_param_uint64 (&plimit),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_multi_select (
pg->conn,
(limit > 0)
? "lookup_transfers_asc"
: "lookup_transfers_desc",
params,
&lookup_transfers_cb,
<c);
}
}
if (0 >= qs)
return qs;
return ltc.qs;
}
/**
* Store information about wire fees charged by an exchange,
* including signature (so we have proof).
*
* @param cls closure
* @param master_pub public key of the exchange
* @param h_wire_method hash of wire method
* @param fees the fee charged
* @param start_date start of fee being used
* @param end_date end of fee being used
* @param master_sig signature of exchange over fee structure
* @return transaction status code
*/
static enum GNUNET_DB_QueryStatus
postgres_store_wire_fee_by_exchange (
void *cls,
const struct TALER_MasterPublicKeyP *master_pub,
const struct GNUNET_HashCode *h_wire_method,
const struct TALER_WireFeeSet *fees,
struct GNUNET_TIME_Timestamp start_date,
struct GNUNET_TIME_Timestamp end_date,
const struct TALER_MasterSignatureP *master_sig)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (master_pub),
GNUNET_PQ_query_param_auto_from_type (h_wire_method),
TALER_PQ_query_param_amount (&fees->wire),
TALER_PQ_query_param_amount (&fees->closing),
GNUNET_PQ_query_param_timestamp (&start_date),
GNUNET_PQ_query_param_timestamp (&end_date),
GNUNET_PQ_query_param_auto_from_type (master_sig),
GNUNET_PQ_query_param_end
};
/* no preflight check here, run in its own transaction by the caller */
check_connection (pg);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Storing wire fee for %s starting at %s of %s\n",
TALER_B2S (master_pub),
GNUNET_TIME_timestamp2s (start_date),
TALER_amount2s (&fees->wire));
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_wire_fee",
params);
}
/**
* Add @a credit to a reserve to be used for tipping. Note that
* this function does not actually perform any wire transfers to
* credit the reserve, it merely tells the merchant backend that
* a reserve now exists. This has to happen before tips can be
* authorized.
*
* @param cls closure, typically a connection to the db
* @param instance_id which instance is the reserve tied to
* @param reserve_priv which reserve is topped up or created
* @param reserve_pub which reserve is topped up or created
* @param exchange_url what URL is the exchange reachable at where the reserve is located
* @param payto_uri URI to use to fund the reserve
* @param initial_balance how much money will be added to the reserve
* @param expiration when does the reserve expire?
* @return transaction status, usually
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
*/
static enum TALER_ErrorCode
postgres_insert_reserve (void *cls,
const char *instance_id,
const struct TALER_ReservePrivateKeyP *reserve_priv,
const struct TALER_ReservePublicKeyP *reserve_pub,
const char *exchange_url,
const char *payto_uri,
const struct TALER_Amount *initial_balance,
struct GNUNET_TIME_Timestamp expiration)
{
struct PostgresClosure *pg = cls;
unsigned int retries;
enum GNUNET_DB_QueryStatus qs;
retries = 0;
check_connection (pg);
RETRY:
if (MAX_RETRIES < ++retries)
return TALER_EC_GENERIC_DB_SOFT_FAILURE;
if (GNUNET_OK !=
postgres_start (pg,
"insert reserve"))
{
GNUNET_break (0);
return TALER_EC_GENERIC_DB_START_FAILED;
}
/* Setup reserve */
{
struct GNUNET_TIME_Timestamp now;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (reserve_pub),
GNUNET_PQ_query_param_timestamp (&now),
GNUNET_PQ_query_param_timestamp (&expiration),
TALER_PQ_query_param_amount (initial_balance),
GNUNET_PQ_query_param_end
};
now = GNUNET_TIME_timestamp_get ();
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_reserve",
params);
if (0 > qs)
{
postgres_rollback (pg);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
goto RETRY;
return qs;
}
}
/* Store private key */
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (reserve_pub),
GNUNET_PQ_query_param_auto_from_type (reserve_priv),
GNUNET_PQ_query_param_string (exchange_url),
GNUNET_PQ_query_param_string (payto_uri),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_reserve_key",
params);
if (0 > qs)
{
postgres_rollback (pg);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
goto RETRY;
return qs;
}
}
qs = postgres_commit (pg);
if (0 <= qs)
return TALER_EC_NONE; /* success */
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
goto RETRY;
return qs;
}
/**
* Confirms @a credit as the amount the exchange claims to have received and
* thus really 'activates' the reserve. This has to happen before tips can
* be authorized.
*
* @param cls closure, typically a connection to the db
* @param instance_id which instance is the reserve tied to
* @param reserve_pub which reserve is topped up or created
* @param initial_exchange_balance how much money was be added to the reserve
* according to the exchange
* @return transaction status, usually
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
*/
static enum GNUNET_DB_QueryStatus
postgres_activate_reserve (void *cls,
const char *instance_id,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_Amount *initial_exchange_balance)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (reserve_pub),
TALER_PQ_query_param_amount (initial_exchange_balance),
GNUNET_PQ_query_param_end
};
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"activate_reserve",
params);
}
/**
* Closure for #lookup_reserves_cb.
*/
struct LookupReservesContext
{
/**
* Postgres context.
*/
struct PostgresClosure *pg;
/**
* Function to call with the results
*/
TALER_MERCHANTDB_ReservesCallback cb;
/**
* Closure for @e cb
*/
void *cb_cls;
/**
* Filter by active reserves.
*/
enum TALER_EXCHANGE_YesNoAll active;
/**
* Filter by failures (mismatch in exchange claimed and
* merchant claimed initial amounts).
*/
enum TALER_EXCHANGE_YesNoAll failures;
/**
* Set in case of errors.
*/
enum GNUNET_DB_QueryStatus qs;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about accounts.
*
* @param[in,out] cls of type `struct LookupReservesContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_reserves_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupReservesContext *lrc = cls;
struct PostgresClosure *pg = lrc->pg;
for (unsigned int i = 0; i < num_results; i++)
{
struct TALER_ReservePublicKeyP reserve_pub;
struct GNUNET_TIME_Timestamp creation_time;
struct GNUNET_TIME_Timestamp expiration_time;
struct TALER_Amount merchant_initial_balance;
struct TALER_Amount exchange_initial_balance;
struct TALER_Amount pickup_amount;
struct TALER_Amount committed_amount;
uint8_t active;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
&reserve_pub),
GNUNET_PQ_result_spec_timestamp ("creation_time",
&creation_time),
GNUNET_PQ_result_spec_timestamp ("expiration",
&expiration_time),
TALER_PQ_RESULT_SPEC_AMOUNT ("merchant_initial_balance",
&merchant_initial_balance),
TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance",
&exchange_initial_balance),
TALER_PQ_RESULT_SPEC_AMOUNT ("tips_committed",
&committed_amount),
TALER_PQ_RESULT_SPEC_AMOUNT ("tips_picked_up",
&pickup_amount),
GNUNET_PQ_result_spec_auto_from_type ("active",
&active),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
lrc->qs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
switch (lrc->active)
{
case TALER_EXCHANGE_YNA_YES:
if (0 == active)
continue;
break;
case TALER_EXCHANGE_YNA_NO:
if (0 != active)
continue;
break;
case TALER_EXCHANGE_YNA_ALL:
break;
}
switch (lrc->failures)
{
case TALER_EXCHANGE_YNA_YES:
if (0 ==
TALER_amount_cmp (&merchant_initial_balance,
&exchange_initial_balance))
continue;
break;
case TALER_EXCHANGE_YNA_NO:
if (0 !=
TALER_amount_cmp (&merchant_initial_balance,
&exchange_initial_balance))
continue;
break;
case TALER_EXCHANGE_YNA_ALL:
break;
}
lrc->cb (lrc->cb_cls,
&reserve_pub,
creation_time,
expiration_time,
&merchant_initial_balance,
&exchange_initial_balance,
&pickup_amount,
&committed_amount,
(0 != active));
}
}
/**
* Lookup reserves.
*
* @param cls closure
* @param instance_id instance to lookup payments for
* @param created_after filter by reserves created after this date
* @param active filter by active reserves
* @param failures filter by reserves with a disagreement on the initial balance
* @param cb function to call with reserve summary data
* @param cb_cls closure for @a cb
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_reserves (void *cls,
const char *instance_id,
struct GNUNET_TIME_Timestamp created_after,
enum TALER_EXCHANGE_YesNoAll active,
enum TALER_EXCHANGE_YesNoAll failures,
TALER_MERCHANTDB_ReservesCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupReservesContext lrc = {
.pg = pg,
.active = active,
.failures = failures,
.cb = cb,
.cb_cls = cb_cls
};
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_timestamp (&created_after),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_reserves",
params,
&lookup_reserves_cb,
&lrc);
if (lrc.qs < 0)
return lrc.qs;
return qs;
}
/**
* Closure for #lookup_pending_reserves_cb.
*/
struct LookupPendingReservesContext
{
/**
* Postgres context.
*/
struct PostgresClosure *pg;
/**
* Function to call with the results
*/
TALER_MERCHANTDB_PendingReservesCallback cb;
/**
* Closure for @e cb
*/
void *cb_cls;
/**
* Set in case of errors.
*/
enum GNUNET_DB_QueryStatus qs;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about accounts.
*
* @param[in,out] cls of type `struct LookupReservesContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_pending_reserves_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupPendingReservesContext *lrc = cls;
struct PostgresClosure *pg = lrc->pg;
for (unsigned int i = 0; i < num_results; i++)
{
struct TALER_ReservePublicKeyP reserve_pub;
struct TALER_Amount merchant_initial_balance;
char *exchange_url;
char *instance_id;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
&reserve_pub),
GNUNET_PQ_result_spec_string ("merchant_id",
&instance_id),
GNUNET_PQ_result_spec_string ("exchange_url",
&exchange_url),
TALER_PQ_RESULT_SPEC_AMOUNT ("merchant_initial_balance",
&merchant_initial_balance),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
lrc->qs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
lrc->cb (lrc->cb_cls,
instance_id,
exchange_url,
&reserve_pub,
&merchant_initial_balance);
GNUNET_PQ_cleanup_result (rs);
}
}
/**
* Lookup reserves pending activation across all instances.
*
* @param cls closure
* @param cb function to call with reserve summary data
* @param cb_cls closure for @a cb
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_pending_reserves (void *cls,
TALER_MERCHANTDB_PendingReservesCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupPendingReservesContext lrc = {
.pg = pg,
.cb = cb,
.cb_cls = cb_cls
};
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_pending_reserves",
params,
&lookup_pending_reserves_cb,
&lrc);
if (lrc.qs < 0)
return lrc.qs;
return qs;
}
/**
* Closure for #lookup_reserve_tips_cb().
*/
struct LookupTipsContext
{
/**
* Postgres context.
*/
struct PostgresClosure *pg;
/**
* Array with information about tips generated from this reserve.
*/
struct TALER_MERCHANTDB_TipDetails *tips;
/**
* Length of the @e tips array.
*/
unsigned int tips_length;
/**
* Set in case of errors.
*/
enum GNUNET_DB_QueryStatus qs;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about accounts.
*
* @param[in,out] cls of type `struct LookupTipsContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_reserve_tips_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupTipsContext *ltc = cls;
struct PostgresClosure *pg = ltc->pg;
GNUNET_array_grow (ltc->tips,
ltc->tips_length,
num_results);
for (unsigned int i = 0; i < num_results; i++)
{
struct TALER_MERCHANTDB_TipDetails *td = <c->tips[i];
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("justification",
&td->reason),
GNUNET_PQ_result_spec_auto_from_type ("tip_id",
&td->tip_id),
TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
&td->total_amount),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
ltc->qs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
}
}
/**
* Lookup reserve details.
*
* @param cls closure
* @param instance_id instance to lookup payments for
* @param reserve_pub public key of the reserve to inspect
* @param fetch_tips if true, also return information about tips
* @param cb function to call with reserve summary data
* @param cb_cls closure for @a cb
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_reserve (void *cls,
const char *instance_id,
const struct TALER_ReservePublicKeyP *reserve_pub,
bool fetch_tips,
TALER_MERCHANTDB_ReserveDetailsCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupTipsContext ltc = {
.pg = pg,
.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
};
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (reserve_pub),
GNUNET_PQ_query_param_end
};
struct GNUNET_TIME_Timestamp creation_time;
struct GNUNET_TIME_Timestamp expiration_time;
struct TALER_Amount merchant_initial_balance;
struct TALER_Amount exchange_initial_balance;
struct TALER_Amount pickup_amount;
struct TALER_Amount committed_amount;
uint8_t active;
char *exchange_url = NULL;
char *payto_uri = NULL;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_timestamp ("creation_time",
&creation_time),
GNUNET_PQ_result_spec_timestamp ("expiration",
&expiration_time),
TALER_PQ_RESULT_SPEC_AMOUNT ("merchant_initial_balance",
&merchant_initial_balance),
TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance",
&exchange_initial_balance),
TALER_PQ_RESULT_SPEC_AMOUNT ("tips_picked_up",
&pickup_amount),
TALER_PQ_RESULT_SPEC_AMOUNT ("tips_committed",
&committed_amount),
GNUNET_PQ_result_spec_auto_from_type ("active",
&active),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_string ("exchange_url",
&exchange_url),
NULL),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_string ("payto_uri",
&payto_uri),
NULL),
GNUNET_PQ_result_spec_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_reserve",
params,
rs);
if (qs < 0)
return qs;
if (! fetch_tips)
{
cb (cb_cls,
creation_time,
expiration_time,
&merchant_initial_balance,
&exchange_initial_balance,
&pickup_amount,
&committed_amount,
(0 != active),
exchange_url,
payto_uri,
0,
NULL);
GNUNET_PQ_cleanup_result (rs);
return qs;
}
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_reserve_tips",
params,
&lookup_reserve_tips_cb,
<c);
if (qs < 0)
return qs;
if (ltc.qs >= 0)
{
cb (cb_cls,
creation_time,
expiration_time,
&merchant_initial_balance,
&exchange_initial_balance,
&pickup_amount,
&committed_amount,
0 != active,
exchange_url,
payto_uri,
ltc.tips_length,
ltc.tips);
}
for (unsigned int i = 0; iconn,
"delete_reserve",
params);
}
/**
* Purge all of the information about a reserve, including tips.
*
* @param cls closure, typically a connection to the db
* @param instance_id which instance is the reserve tied to
* @param reserve_pub which reserve is to be purged
* @return transaction status, usually
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
*/
static enum GNUNET_DB_QueryStatus
postgres_purge_reserve (void *cls,
const char *instance_id,
const struct TALER_ReservePublicKeyP *reserve_pub)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (reserve_pub),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"purge_reserve",
params);
}
/**
* Closure for #lookup_reserve_for_tip_cb().
*/
struct LookupReserveForTipContext
{
/**
* Postgres context.
*/
struct PostgresClosure *pg;
/**
* Public key of the reserve we found.
*/
struct TALER_ReservePublicKeyP reserve_pub;
/**
* How much money must be left in the reserve.
*/
struct TALER_Amount required_amount;
/**
* Set to the expiration time of the reserve we found.
* #GNUNET_TIME_UNIT_FOREVER_ABS if we found none.
*/
struct GNUNET_TIME_Timestamp expiration;
/**
* Error status.
*/
enum TALER_ErrorCode ec;
/**
* Did we find a good reserve?
*/
bool ok;
};
/**
* How long must a reserve be at least still valid before we use
* it for a tip?
*/
#define MIN_EXPIRATION GNUNET_TIME_UNIT_HOURS
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about accounts.
*
* @param[in,out] cls of type `struct LookupReserveForTipContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_reserve_for_tip_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupReserveForTipContext *lac = cls;
struct PostgresClosure *pg = lac->pg;
for (unsigned int i = 0; i < num_results; i++)
{
struct TALER_ReservePublicKeyP reserve_pub;
struct TALER_Amount committed_amount;
struct TALER_Amount remaining;
struct TALER_Amount initial_balance;
struct GNUNET_TIME_Timestamp expiration;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
&reserve_pub),
TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance",
&initial_balance),
TALER_PQ_RESULT_SPEC_AMOUNT ("tips_committed",
&committed_amount),
GNUNET_PQ_result_spec_timestamp ("expiration",
&expiration),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
lac->ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
return;
}
if (0 >
TALER_amount_subtract (&remaining,
&initial_balance,
&committed_amount))
{
GNUNET_break (0);
continue;
}
if (0 >
TALER_amount_cmp (&remaining,
&lac->required_amount))
{
/* insufficient balance */
if (lac->ok)
continue; /* got another reserve */
lac->ec = TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_INSUFFICIENT_FUNDS;
continue;
}
if ( (! GNUNET_TIME_absolute_is_never (lac->expiration.abs_time)) &&
GNUNET_TIME_timestamp_cmp (expiration, >, lac->expiration) &&
GNUNET_TIME_relative_cmp (
GNUNET_TIME_absolute_get_remaining (lac->expiration.abs_time),
>,
MIN_EXPIRATION) )
{
/* reserve expired */
if (lac->ok)
continue; /* got another reserve */
lac->ec = TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_EXPIRED;
continue;
}
lac->ok = true;
lac->ec = TALER_EC_NONE;
lac->expiration = expiration;
lac->reserve_pub = reserve_pub;
}
}
/**
* Authorize a tip over @a amount from reserve @a reserve_pub. Remember
* the authorization under @a tip_id for later, together with the
* @a justification.
*
* @param cls closure, typically a connection to the db
* @param instance_id which instance should generate the tip
* @param reserve_pub which reserve is debited, NULL to pick one in the DB
* @param amount how high is the tip (with fees)
* @param justification why was the tip approved
* @param next_url where to send the URL post tip pickup
* @param[out] tip_id set to the unique ID for the tip
* @param[out] expiration set to when the tip expires
* @return transaction status,
* #TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but has expired
* #TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_NOT_FOUND if the reserve is not known
* #TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has insufficient funds left
* #TALER_EC_GENERIC_DB_START_FAILED on hard DB errors
* #TALER_EC_GENERIC_DB_FETCH_FAILED on hard DB errors
* #TALER_EC_GENERIC_DB_STORE_FAILED on hard DB errors
* #TALER_EC_GENERIC_DB_INVARIANT_FAILURE on hard DB errors
* #TALER_EC_GENERIC_DB_SOFT_FAILURE soft DB errors (client should retry)
* #TALER_EC_NONE upon success
*/
static enum TALER_ErrorCode
postgres_authorize_tip (void *cls,
const char *instance_id,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_Amount *amount,
const char *justification,
const char *next_url,
struct TALER_TipIdentifierP *tip_id,
struct GNUNET_TIME_Timestamp *expiration)
{
struct PostgresClosure *pg = cls;
unsigned int retries = 0;
enum GNUNET_DB_QueryStatus qs;
struct TALER_Amount tips_committed;
struct TALER_Amount exchange_initial_balance;
const struct TALER_ReservePublicKeyP *reserve_pubp;
struct LookupReserveForTipContext lac = {
.pg = pg,
.required_amount = *amount,
.expiration = GNUNET_TIME_UNIT_FOREVER_TS
};
check_connection (pg);
RETRY:
reserve_pubp = reserve_pub;
if (MAX_RETRIES < ++retries)
{
GNUNET_break (0);
return
TALER_EC_GENERIC_DB_SOFT_FAILURE;
}
if (GNUNET_OK !=
postgres_start (pg,
"authorize tip"))
{
GNUNET_break (0);
return TALER_EC_GENERIC_DB_START_FAILED;
}
if (NULL == reserve_pubp)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_reserve_for_tip",
params,
&lookup_reserve_for_tip_cb,
&lac);
switch (qs)
{
case GNUNET_DB_STATUS_SOFT_ERROR:
postgres_rollback (pg);
goto RETRY;
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
postgres_rollback (pg);
return TALER_EC_GENERIC_DB_FETCH_FAILED;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
postgres_rollback (pg);
return TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_NOT_FOUND;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
default:
break;
}
if (TALER_EC_NONE != lac.ec)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Enabling tip reserved failed with status %d\n",
lac.ec);
postgres_rollback (pg);
return lac.ec;
}
GNUNET_assert (lac.ok);
reserve_pubp = &lac.reserve_pub;
}
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (reserve_pubp),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_timestamp ("expiration",
expiration),
TALER_PQ_RESULT_SPEC_AMOUNT ("tips_committed",
&tips_committed),
TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance",
&exchange_initial_balance),
GNUNET_PQ_result_spec_end
};
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_reserve_status",
params,
rs);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
postgres_rollback (pg);
goto RETRY;
}
if (qs < 0)
{
GNUNET_break (0);
postgres_rollback (pg);
return TALER_EC_GENERIC_DB_FETCH_FAILED;
}
if (0 == qs)
{
postgres_rollback (pg);
return TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_NOT_FOUND;
}
}
{
struct TALER_Amount remaining;
if (0 >
TALER_amount_subtract (&remaining,
&exchange_initial_balance,
&tips_committed))
{
GNUNET_break (0);
postgres_rollback (pg);
return TALER_EC_GENERIC_DB_INVARIANT_FAILURE;
}
if (0 >
TALER_amount_cmp (&remaining,
amount))
{
postgres_rollback (pg);
return TALER_EC_MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_INSUFFICIENT_FUNDS;
}
}
GNUNET_assert (0 <=
TALER_amount_add (&tips_committed,
&tips_committed,
amount));
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (reserve_pubp),
TALER_PQ_query_param_amount (&tips_committed),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_reserve_tips_committed",
params);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
postgres_rollback (pg);
goto RETRY;
}
if (qs < 0)
{
GNUNET_break (0);
postgres_rollback (pg);
return TALER_EC_GENERIC_DB_STORE_FAILED;
}
}
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
tip_id,
sizeof (*tip_id));
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (reserve_pubp),
GNUNET_PQ_query_param_auto_from_type (tip_id),
GNUNET_PQ_query_param_string (justification),
GNUNET_PQ_query_param_string (next_url),
GNUNET_PQ_query_param_timestamp (expiration),
TALER_PQ_query_param_amount (amount),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_tip",
params);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
postgres_rollback (pg);
goto RETRY;
}
if (qs < 0)
{
GNUNET_break (0);
postgres_rollback (pg);
return TALER_EC_GENERIC_DB_STORE_FAILED;
}
}
qs = postgres_commit (pg);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
goto RETRY;
if (qs < 0)
{
GNUNET_break (0);
postgres_rollback (pg);
return TALER_EC_GENERIC_DB_COMMIT_FAILED;
}
return TALER_EC_NONE;
}
/**
* Closure for #lookup_signatures_cb().
*/
struct LookupSignaturesContext
{
/**
* Length of the @e sigs array
*/
unsigned int sigs_length;
/**
* Where to store the signatures.
*/
struct TALER_BlindedDenominationSignature *sigs;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about accounts.
*
* @param[in,out] cls of type `struct LookupSignaturesContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_signatures_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupSignaturesContext *lsc = cls;
for (unsigned int i = 0; i < num_results; i++)
{
uint32_t offset;
struct TALER_BlindedDenominationSignature bsig;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint32 ("coin_offset",
&offset),
TALER_PQ_result_spec_blinded_denom_sig ("blind_sig",
&bsig),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
return;
}
if (offset >= lsc->sigs_length)
{
GNUNET_break_op (0);
GNUNET_PQ_cleanup_result (rs);
continue;
}
/* Must be NULL due to UNIQUE constraint on offset and
requirement that client launched us with 'sigs'
pre-initialized to NULL. */
lsc->sigs[offset] = bsig;
}
}
/**
* Lookup pickup details for pickup @a pickup_id.
*
* @param cls closure, typically a connection to the db
* @param instance_id which instance should we lookup tip details for
* @param tip_id which tip should we lookup details on
* @param pickup_id which pickup should we lookup details on
* @param[out] exchange_url which exchange is the tip withdrawn from
* @param[out] reserve_priv private key the tip is withdrawn from (set if still available!)
* @param sigs_length length of the @a sigs array
* @param[out] sigs set to the (blind) signatures we have for this @a pickup_id,
* those that are unavailable are left at NULL
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_pickup (void *cls,
const char *instance_id,
const struct TALER_TipIdentifierP *tip_id,
const struct TALER_PickupIdentifierP *pickup_id,
char **exchange_url,
struct TALER_ReservePrivateKeyP *reserve_priv,
unsigned int sigs_length,
struct TALER_BlindedDenominationSignature sigs[])
{
struct PostgresClosure *pg = cls;
uint64_t pickup_serial;
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (tip_id),
GNUNET_PQ_query_param_auto_from_type (pickup_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("exchange_url",
exchange_url),
GNUNET_PQ_result_spec_auto_from_type ("reserve_priv",
reserve_priv),
GNUNET_PQ_result_spec_uint64 ("pickup_serial",
&pickup_serial),
GNUNET_PQ_result_spec_end
};
enum GNUNET_DB_QueryStatus qs;
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_pickup",
params,
rs);
if (qs <= 0)
return qs;
}
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&pickup_serial),
GNUNET_PQ_query_param_end
};
struct LookupSignaturesContext lsc = {
.sigs_length = sigs_length,
.sigs = sigs
};
return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_pickup_signatures",
params,
&lookup_signatures_cb,
&lsc);
}
}
/**
* Lookup tip details for tip @a tip_id.
*
* @param cls closure, typically a connection to the db
* @param instance_id which instance should we lookup tip details for
* @param tip_id which tip should we lookup details on
* @param[out] total_authorized amount how high is the tip (with fees)
* @param[out] total_picked_up how much of the tip was so far picked up (with fees)
* @param[out] expiration set to when the tip expires
* @param[out] exchange_url set to the exchange URL where the reserve is
* @param[out] reserve_priv set to private key of reserve to be debited
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_tip (void *cls,
const char *instance_id,
const struct TALER_TipIdentifierP *tip_id,
struct TALER_Amount *total_authorized,
struct TALER_Amount *total_picked_up,
struct GNUNET_TIME_Timestamp *expiration,
char **exchange_url,
struct TALER_ReservePrivateKeyP *reserve_priv)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (tip_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
total_authorized),
TALER_PQ_RESULT_SPEC_AMOUNT ("picked_up",
total_picked_up),
GNUNET_PQ_result_spec_timestamp ("expiration",
expiration),
GNUNET_PQ_result_spec_string ("exchange_url",
exchange_url),
GNUNET_PQ_result_spec_auto_from_type ("reserve_priv",
reserve_priv),
GNUNET_PQ_result_spec_end
};
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_tip",
params,
rs);
}
/**
* Context used for postgres_lookup_tips().
*/
struct LookupMerchantTipsContext
{
/**
* Postgres context.
*/
struct PostgresClosure *pg;
/**
* Function to call with the results.
*/
TALER_MERCHANTDB_TipsCallback cb;
/**
* Closure for @a cb.
*/
void *cb_cls;
/**
* Internal result.
*/
enum GNUNET_DB_QueryStatus qs;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about tips.
*
* @param[in,out] cls of type `struct LookupTipsContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_tips_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupMerchantTipsContext *plc = cls;
struct PostgresClosure *pg = plc->pg;
for (unsigned int i = 0; i < num_results; i++)
{
uint64_t row_id;
struct TALER_TipIdentifierP tip_id;
struct TALER_Amount tip_amount;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("tip_serial",
&row_id),
GNUNET_PQ_result_spec_auto_from_type ("tip_id",
&tip_id),
TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
&tip_amount),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
plc->qs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
plc->cb (plc->cb_cls,
row_id,
tip_id,
tip_amount);
GNUNET_PQ_cleanup_result (rs);
}
}
/**
* Lookup tips
*
* @param cls closure, typically a connection to the db
* @param instance_id which instance should we lookup tips for
* @param expired should we include expired tips?
* @param limit maximum number of results to return, positive for
* ascending row id, negative for descending
* @param offset row id to start returning results from
* @param cb function to call with tip data
* @param cb_cls closure for @a cb
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_tips (void *cls,
const char *instance_id,
enum TALER_EXCHANGE_YesNoAll expired,
int64_t limit,
uint64_t offset,
TALER_MERCHANTDB_TipsCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupMerchantTipsContext plc = {
.pg = pg,
.cb = cb,
.cb_cls = cb_cls
};
uint64_t ulimit = (limit > 0) ? limit : -limit;
uint8_t bexpired;
struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&ulimit),
GNUNET_PQ_query_param_uint64 (&offset),
GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_auto_from_type (&bexpired),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
char stmt[128];
bexpired = (TALER_EXCHANGE_YNA_YES == expired);
GNUNET_snprintf (stmt,
sizeof (stmt),
"lookup_tips_%s%s",
(limit > 0) ? "inc" : "dec",
(TALER_EXCHANGE_YNA_ALL == expired) ? "" : "_expired");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
stmt,
params,
&lookup_tips_cb,
&plc);
if (0 != plc.qs)
return plc.qs;
return qs;
}
/**
* Closure for #lookup_pickup_details_cb().
*/
struct LookupTipDetailsContext
{
/**
* Length of the @e sigs array
*/
unsigned int *pickups_length;
/**
* Where to store the signatures.
*/
struct TALER_MERCHANTDB_PickupDetails **pickups;
/**
* Database handle.
*/
struct PostgresClosure *pg;
/**
* Transaction status.
*/
enum GNUNET_DB_QueryStatus qs;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about pickups.
*
* @param[in,out] cls of type `struct LookupTipDetailsContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_pickup_details_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupTipDetailsContext *ltdc = cls;
struct PostgresClosure *pg = ltdc->pg;
*ltdc->pickups_length = num_results;
*ltdc->pickups = GNUNET_new_array (num_results,
struct TALER_MERCHANTDB_PickupDetails);
for (unsigned int i = 0; i < num_results; i++)
{
struct TALER_MERCHANTDB_PickupDetails *pd = &((*ltdc->pickups)[i]);
uint64_t num_planchets = 0;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("pickup_id",
&pd->pickup_id),
TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
&pd->requested_amount),
GNUNET_PQ_result_spec_uint64 ("num_planchets",
&num_planchets),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
ltdc->qs = GNUNET_DB_STATUS_HARD_ERROR;
GNUNET_array_grow (*ltdc->pickups,
*ltdc->pickups_length,
0);
return;
}
pd->num_planchets = num_planchets;
}
}
/**
* Lookup tip details for tip @a tip_id.
*
* @param cls closure, typically a connection to the db
* @param instance_id which instance should we lookup tip details for
* @param tip_id which tip should we lookup details on
* @param fpu should we fetch details about individual pickups
* @param[out] total_authorized amount how high is the tip (with fees)
* @param[out] total_picked_up how much of the tip was so far picked up (with fees)
* @param[out] justification why was the tip approved
* @param[out] expiration set to when the tip expires
* @param[out] reserve_pub set to which reserve is debited
* @param[out] pickups_length set to the length of @e pickups
* @param[out] pickups if @a fpu is true, set to details about the pickup operations
* @return transaction status,
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_tip_details (void *cls,
const char *instance_id,
const struct TALER_TipIdentifierP *tip_id,
bool fpu,
struct TALER_Amount *total_authorized,
struct TALER_Amount *total_picked_up,
char **justification,
struct GNUNET_TIME_Timestamp *expiration,
struct TALER_ReservePublicKeyP *reserve_pub,
unsigned int *pickups_length,
struct TALER_MERCHANTDB_PickupDetails **pickups)
{
struct PostgresClosure *pg = cls;
uint64_t tip_serial;
enum GNUNET_DB_QueryStatus qs;
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (tip_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("tip_serial",
&tip_serial),
TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
total_authorized),
TALER_PQ_RESULT_SPEC_AMOUNT ("picked_up",
total_picked_up),
GNUNET_PQ_result_spec_string ("justification",
justification),
GNUNET_PQ_result_spec_timestamp ("expiration",
expiration),
GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
reserve_pub),
GNUNET_PQ_result_spec_end
};
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_tip_details",
params,
rs);
if (qs <= 0)
return qs;
if (! fpu)
{
*pickups_length = 0;
*pickups = NULL;
return qs;
}
}
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&tip_serial),
GNUNET_PQ_query_param_end
};
struct LookupTipDetailsContext ltdc = {
.pickups_length = pickups_length,
.pickups = pickups,
.pg = pg,
.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
};
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_pickup_details",
params,
&lookup_pickup_details_cb,
<dc);
if (qs < 0)
return qs;
return ltdc.qs;
}
}
/**
* Insert details about a tip pickup operation. The @a total_picked_up
* UPDATES the total amount under the @a tip_id, while the @a
* total_requested is the amount to be associated with this @a pickup_id.
* While there is usually only one pickup event that picks up the entire
* amount, our schema allows for wallets to pick up the amount incrementally
* over multiple pick up operations.
*
* @param cls closure, typically a connection to the db
* @param instance_id which instance gave the tip
* @param tip_id the unique ID for the tip
* @param total_picked_up how much was picked up overall at this
* point (includes @a total_requested)
* @param pickup_id unique ID for the operation
* @param total_requested how much is being picked up in this operation
* @return transaction status, usually
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
* #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a credit_uuid already known
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_pickup (void *cls,
const char *instance_id,
const struct TALER_TipIdentifierP *tip_id,
const struct TALER_Amount *total_picked_up,
const struct TALER_PickupIdentifierP *pickup_id,
const struct TALER_Amount *total_requested)
{
struct PostgresClosure *pg = cls;
enum GNUNET_DB_QueryStatus qs;
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (tip_id),
GNUNET_PQ_query_param_auto_from_type (pickup_id),
TALER_PQ_query_param_amount (total_requested),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_pickup",
params);
if (0 > qs)
return qs;
}
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (tip_id),
TALER_PQ_query_param_amount (total_picked_up),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_picked_up_tip",
params);
if (0 > qs)
return qs;
}
{
uint64_t reserve_serial;
struct TALER_Amount reserve_picked_up;
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_auto_from_type (tip_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("reserve_serial",
&reserve_serial),
TALER_PQ_RESULT_SPEC_AMOUNT ("tips_picked_up",
&reserve_picked_up),
GNUNET_PQ_result_spec_end
};
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_picked_up_reserve",
params,
rs);
if (0 > qs)
return qs;
}
if (0 >=
TALER_amount_add (&reserve_picked_up,
&reserve_picked_up,
total_requested))
{
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&reserve_serial),
TALER_PQ_query_param_amount (&reserve_picked_up),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_picked_up_reserve",
params);
if (0 > qs)
return qs;
}
}
return qs;
}
/**
* Insert blind signature obtained from the exchange during a
* tip pickup operation.
*
* @param cls closure, typically a connection to the db
* @param pickup_id unique ID for the operation
* @param offset offset of the blind signature for the pickup
* @param blind_sig the blind signature
* @return transaction status, usually
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
* #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a credit_uuid already known
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_pickup_blind_signature (
void *cls,
const struct TALER_PickupIdentifierP *pickup_id,
uint32_t offset,
const struct TALER_BlindedDenominationSignature *blind_sig)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (pickup_id),
GNUNET_PQ_query_param_uint32 (&offset),
TALER_PQ_query_param_blinded_denom_sig (blind_sig),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_pickup_blind_signature",
params);
}
/**
* Delete information about a template.
*
* @param cls closure
* @param instance_id instance to delete template of
* @param template_id template to delete
* @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
* if template unknown.
*/
static enum GNUNET_DB_QueryStatus
postgres_delete_template (void *cls,
const char *instance_id,
const char *template_id)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (template_id),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"delete_template",
params);
}
/**
* Insert details about a particular template.
*
* @param cls closure
* @param instance_id instance to insert template for
* @param template_id template identifier of template to insert
* @param td the template details to insert
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_template (void *cls,
const char *instance_id,
const char *template_id,
const struct TALER_MERCHANTDB_TemplateDetails *td)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (template_id),
GNUNET_PQ_query_param_string (td->template_description),
(NULL == td->image)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (td->image),
TALER_PQ_query_param_json (td->template_contract),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_template",
params);
}
/**
* Update details about a particular template.
*
* @param cls closure
* @param instance_id instance to update template for
* @param template_id template to update
* @param td update to the template details on success, can be NULL
* (in that case we only want to check if the template exists)
* @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the template
* does not yet exist.
*/
static enum GNUNET_DB_QueryStatus
postgres_update_template (void *cls,
const char *instance_id,
const char *template_id,
const struct TALER_MERCHANTDB_TemplateDetails *td)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (template_id),
GNUNET_PQ_query_param_string (td->template_description),
(NULL == td->image)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (td->image),
TALER_PQ_query_param_json (td->template_contract),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_template",
params);
}
/**
* Context used for postgres_lookup_template().
*/
struct LookupTemplateContext
{
/**
* Function to call with the results.
*/
TALER_MERCHANTDB_TemplatesCallback cb;
/**
* Closure for @a cb.
*/
void *cb_cls;
/**
* Did database result extraction fail?
*/
bool extract_failed;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about template.
*
* @param[in,out] cls of type `struct LookupTemplateContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_templates_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupTemplateContext *tlc = cls;
for (unsigned int i = 0; i < num_results; i++)
{
char *template_id;
char *template_description;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("template_id",
&template_id),
GNUNET_PQ_result_spec_string ("template_description",
&template_description),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
tlc->extract_failed = true;
return;
}
tlc->cb (tlc->cb_cls,
template_id,
template_description);
GNUNET_PQ_cleanup_result (rs);
}
}
/**
* Lookup all of the templates the given instance has configured.
*
* @param cls closure
* @param instance_id instance to lookup template for
* @param cb function to call on all template found
* @param cb_cls closure for @a cb
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_templates (void *cls,
const char *instance_id,
TALER_MERCHANTDB_TemplatesCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupTemplateContext tlc = {
.cb = cb,
.cb_cls = cb_cls,
/* Can be overwritten by the lookup_template_cb */
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_templates",
params,
&lookup_templates_cb,
&tlc);
/* If there was an error inside lookup_template_cb, return a hard error. */
if (tlc.extract_failed)
return GNUNET_DB_STATUS_HARD_ERROR;
return qs;
}
/**
* Lookup details about a particular template.
*
* @param cls closure
* @param instance_id instance to lookup template for
* @param template_id template to lookup
* @param[out] td set to the template details on success, can be NULL
* (in that case we only want to check if the template exists)
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_template (void *cls,
const char *instance_id,
const char *template_id,
struct TALER_MERCHANTDB_TemplateDetails *td)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (template_id),
GNUNET_PQ_query_param_end
};
if (NULL == td)
{
struct GNUNET_PQ_ResultSpec rs_null[] = {
GNUNET_PQ_result_spec_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_template",
params,
rs_null);
}
else
{
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("template_description",
&td->template_description),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_string ("image",
&td->image),
NULL),
TALER_PQ_result_spec_json ("template_contract",
&td->template_contract),
GNUNET_PQ_result_spec_end
};
check_connection (pg);
td->image = NULL;
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_template",
params,
rs);
}
}
/**
* Delete information about a webhook.
*
* @param cls closure
* @param instance_id instance to delete webhook of
* @param webhook_id webhook to delete
* @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
* if webhook unknown.
*/
static enum GNUNET_DB_QueryStatus
postgres_delete_webhook (void *cls,
const char *instance_id,
const char *webhook_id)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (webhook_id),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"delete_webhook",
params);
}
/**
* Insert details about a particular webhook.
*
* @param cls closure
* @param instance_id instance to insert webhook for
* @param webhook_id webhook identifier of webhook to insert
* @param wb the webhook details to insert
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_webhook (void *cls,
const char *instance_id,
const char *webhook_id,
const struct TALER_MERCHANTDB_WebhookDetails *wb)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (webhook_id),
GNUNET_PQ_query_param_string (wb->event_type),
GNUNET_PQ_query_param_string (wb->url),
GNUNET_PQ_query_param_string (wb->http_method),
GNUNET_PQ_query_param_string (wb->header_template),
GNUNET_PQ_query_param_string (wb->body_template),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_webhook",
params);
}
/**
* Update details about a particular webhook.
*
* @param cls closure
* @param instance_id instance to update template for
* @param webhook_id webhook to update
* @param wb update to the webhook details on success, can be NULL
* (in that case we only want to check if the webhook exists)
* @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the webhook
* does not yet exist.
*/
static enum GNUNET_DB_QueryStatus
postgres_update_webhook (void *cls,
const char *instance_id,
const char *webhook_id,
const struct TALER_MERCHANTDB_WebhookDetails *wb)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (webhook_id),
GNUNET_PQ_query_param_string (wb->event_type),
GNUNET_PQ_query_param_string (wb->url),
GNUNET_PQ_query_param_string (wb->http_method),
GNUNET_PQ_query_param_string (wb->header_template),
GNUNET_PQ_query_param_string (wb->body_template),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_webhook",
params);
}
/**
* Context used for postgres_lookup_webhook().
*/
struct LookupWebhookContext
{
/**
* Function to call with the results.
*/
TALER_MERCHANTDB_WebhooksCallback cb;
/**
* Closure for @a cb.
*/
void *cb_cls;
/**
* Did database result extraction fail?
*/
bool extract_failed;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about webhook.
*
* @param[in,out] cls of type `struct LookupWebhookContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_webhooks_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupWebhookContext *wlc = cls;
for (unsigned int i = 0; i < num_results; i++)
{
char *webhook_id;
char *event_type;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("webhook_id",
&webhook_id),
GNUNET_PQ_result_spec_string ("event_type",
&event_type),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
wlc->extract_failed = true;
return;
}
wlc->cb (wlc->cb_cls,
webhook_id,
event_type);
GNUNET_PQ_cleanup_result (rs);
}
}
/**
* Lookup all of the webhooks the given instance has configured.
*
* @param cls closure
* @param instance_id instance to lookup webhook for
* @param cb function to call on all webhook found
* @param cb_cls closure for @a cb
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_webhooks (void *cls,
const char *instance_id,
TALER_MERCHANTDB_WebhooksCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupWebhookContext wlc = {
.cb = cb,
.cb_cls = cb_cls,
/* Can be overwritten by the lookup_webhook_cb */
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_webhooks",
params,
&lookup_webhooks_cb,
&wlc);
/* If there was an error inside lookup_webhook_cb, return a hard error. */
if (wlc.extract_failed)
return GNUNET_DB_STATUS_HARD_ERROR;
return qs;
}
/**
* Lookup details about a particular webhook.
*
* @param cls closure
* @param instance_id instance to lookup webhook for
* @param webhook_id webhook to lookup
* @param[out] wb set to the webhook details on success, can be NULL
* (in that case we only want to check if the webhook exists)
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_webhook (void *cls,
const char *instance_id,
const char *webhook_id,
struct TALER_MERCHANTDB_WebhookDetails *wb)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (webhook_id),
GNUNET_PQ_query_param_end
};
if (NULL == wb)
{
struct GNUNET_PQ_ResultSpec rs_null[] = {
GNUNET_PQ_result_spec_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_webhook",
params,
rs_null);
}
else
{
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("event_type",
&wb->event_type),
GNUNET_PQ_result_spec_string ("url",
&wb->url),
GNUNET_PQ_result_spec_string ("http_method",
&wb->http_method),
GNUNET_PQ_result_spec_string ("header_template",
&wb->header_template),
GNUNET_PQ_result_spec_string ("body_template",
&wb->body_template),
GNUNET_PQ_result_spec_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_webhook",
params,
rs);
}
}
/**
* Context used for postgres_lookup_webhook().
*/
struct LookupWebhookDetailContext
{
/**
* Function to call with the results.
*/
TALER_MERCHANTDB_WebhookDetailCallback cb;
/**
* Closure for @a cb.
*/
void *cb_cls;
/**
* Did database result extraction fail?
*/
bool extract_failed;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about webhook.
*
* @param[in,out] cls of type `struct LookupPendingWebhookContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_webhook_by_event_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupWebhookDetailContext *wlc = cls;
for (unsigned int i = 0; i < num_results; i++)
{
uint64_t webhook_serial;
char *event_type;
char *url;
char *http_method;
char *header_template;
char *body_template;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("webhook_serial",
&webhook_serial),
GNUNET_PQ_result_spec_string ("event_type",
&event_type),
GNUNET_PQ_result_spec_string ("url",
&url),
GNUNET_PQ_result_spec_string ("http_method",
&http_method),
GNUNET_PQ_result_spec_string ("header_template",
&header_template),
GNUNET_PQ_result_spec_string ("body_template",
&body_template),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
wlc->extract_failed = true;
return;
}
wlc->cb (wlc->cb_cls,
webhook_serial,
event_type,
url,
http_method,
header_template,
body_template);
GNUNET_PQ_cleanup_result (rs);
}
}
/**
* Lookup webhook by event
*
* @param cls closure
* @param instance_id instance to lookup webhook for
* @param event_type event that we need to put in the pending webhook
* @param[out] cb set to the webhook details on success
* @param cb_cls callback closure
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_lookup_webhook_by_event(void *cls,
const char *instance_id,
const char *event_type,
TALER_MERCHANTDB_WebhookDetailCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupWebhookDetailContext wlc = {
.cb = cb,
.cb_cls = cb_cls,
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (event_type),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_webhook_by_event",
params,
&lookup_webhook_by_event_cb,
&wlc);
if (wlc.extract_failed)
return GNUNET_DB_STATUS_HARD_ERROR;
return qs;
}
/**
* Insert webhook in the pending webhook.
*
* @param cls closure
* @param instance_id instance to insert webhook for
* @param webhook_serial webhook to insert in the pending webhook
* @param url to make the request to
* @param http_method for the webhook
* @param header of the webhook
* @param body of the webhook
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_insert_pending_webhook(void *cls,
const char *instance_id,
uint64_t webhook_serial,
const char *url,
const char *http_method,
const char *header,
const char *body)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&webhook_serial),
GNUNET_PQ_query_param_string (url),
GNUNET_PQ_query_param_string (http_method),
NULL == header
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (header),
NULL == body
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (body),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_pending_webhook",
params);
}
/**
* Context used for postgres_lookup_future_webhook().
*/
struct LookupPendingWebhookContext
{
/**
* Function to call with the results.
*/
TALER_MERCHANTDB_PendingWebhooksCallback cb;
/**
* Closure for @a cb.
*/
void *cb_cls;
/**
* Did database result extraction fail?
*/
bool extract_failed;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results about webhook.
*
* @param[in,out] cls of type `struct LookupPendingWebhookContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lookup_pending_webhooks_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupPendingWebhookContext *pwlc = cls;
for (unsigned int i = 0; i < num_results; i++)
{
uint64_t webhook_serial;
struct GNUNET_TIME_Absolute next_attempt;
uint32_t retries;
char *url;
char *http_method;
char *header;
char *body;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("webhook_serial",
&webhook_serial),
GNUNET_PQ_result_spec_absolute_time ("next_attempt",
&next_attempt),
GNUNET_PQ_result_spec_uint32 ("retries",
&retries),
GNUNET_PQ_result_spec_string ("url",
&url),
GNUNET_PQ_result_spec_string ("http_method",
&http_method),
GNUNET_PQ_result_spec_string ("header",
&header),
GNUNET_PQ_result_spec_string ("body",
&body),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
pwlc->extract_failed = true;
return;
}
pwlc->cb (pwlc->cb_cls,
webhook_serial,
next_attempt,
retries,
url,
http_method,
header,
body);
GNUNET_PQ_cleanup_result (rs);
}
}
/**
* Lookup the webhook that need to be send in priority.
* send.
*
* @param cls closure
* @param cb pending webhook callback
* @param cb_cls callback closure
*/
// WHERE next_attempt <= now ORDER BY next_attempt ASC
static enum GNUNET_DB_QueryStatus
postgres_lookup_pending_webhook(void *cls,
TALER_MERCHANTDB_PendingWebhooksCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupPendingWebhookContext pwlc = {
.cb = cb,
.cb_cls = cb_cls,
.extract_failed = false,
};
struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
struct GNUNET_PQ_QueryParam params_null[] = {
GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_pending_webhook",
params_null,
&lookup_pending_webhooks_cb,
&pwlc);
if (pwlc.extract_failed)
return GNUNET_DB_STATUS_HARD_ERROR;
return qs;
}
/**
* Lookup future webhook in the pending webhook that need to be send.
* With that we can know how long the system can 'sleep'.
*
* @param cls closure
* @param cb pending webhook callback
* @param cb_cls callback closure
*/
// ORDER BY next_attempt ASC LIMIT 1
static enum GNUNET_DB_QueryStatus
postgres_lookup_future_webhook(void *cls,
TALER_MERCHANTDB_PendingWebhooksCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupPendingWebhookContext pwlc = {
.cb = cb,
.cb_cls = cb_cls,
.extract_failed = false,
};
struct GNUNET_PQ_QueryParam params_null[] = {
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_future_webhook",
params_null,
&lookup_pending_webhooks_cb,
&pwlc);
if (pwlc.extract_failed)
return GNUNET_DB_STATUS_HARD_ERROR;
return qs;
}
/**
* Lookup all the webhooks in the pending webhook.
* Use by the administrator
*
* @param cls closure
* @param instance_id to lookup webhooks for this instance particularly
* @param min_row to see the list of the pending webhook that it is started with this minimum row.
* @param max_results to see the list of the pending webhook that it is end with this max results.
* @param cb pending webhook callback
* @param cb_cls callback closure
*/
// WHERE webhook_pending_serial > min_row ORDER BY webhook_pending_serial ASC LIMIT max_results
static enum GNUNET_DB_QueryStatus
postgres_lookup_all_webhooks(void *cls,
const char *instance_id,
uint64_t min_row,
uint32_t max_results,
TALER_MERCHANTDB_PendingWebhooksCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
struct LookupPendingWebhookContext pwlc = {
.cb = cb,
.cb_cls = cb_cls,
.extract_failed = false,
};
uint64_t max_results64 = max_results;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_uint64 (&min_row),
GNUNET_PQ_query_param_uint64 (&max_results64),
GNUNET_PQ_query_param_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"lookup_all_webhooks",
params,
&lookup_pending_webhooks_cb,
&pwlc);
if (pwlc.extract_failed)
return GNUNET_DB_STATUS_HARD_ERROR;
return qs;
}
/**
* Update the pending webhook. It is use if the webhook can't be send.
*
* @param cls closure
* @param webhook_serial webhook that need to be update
* @param next_attempt when we should make the next request to the webhook
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_update_pending_webhook(void *cls,
uint64_t webhook_serial,
struct GNUNET_TIME_Absolute next_attempt)
// maybe add: http status of failure?
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&webhook_serial),
GNUNET_PQ_query_param_absolute_time (&next_attempt),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_pending_webhook",
params);
}
/**
* Delete a webhook in the pending webhook if it is successfull
*
* @param cls closure
* @param webhook_serial webhook that need to be delete in the pending webhook
* @return database result code
*/
static enum GNUNET_DB_QueryStatus
postgres_delete_pending_webhook(void *cls,
uint64_t webhook_serial)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&webhook_serial),
GNUNET_PQ_query_param_end
};
check_connection (pg);
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"delete_pending_webhook",
params);
}
/**
* Establish connection to the database.
*
* @param cls plugin context
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
static int
postgres_connect (void *cls)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_PreparedStatement ps[] = {
GNUNET_PQ_make_prepare ("end_transaction",
"COMMIT"),
/* for call_with_accounts(), part of postgres_lookup_instances() */
GNUNET_PQ_make_prepare ("lookup_instance_private_key",
"SELECT"
" merchant_priv"
" FROM merchant_keys"
" WHERE merchant_serial=$1"),
/* for find_instances_cb(), part of postgres_lookup_instances() */
GNUNET_PQ_make_prepare ("lookup_accounts",
"SELECT"
" h_wire"
",salt"
",payto_uri"
",active"
" FROM merchant_accounts"
" WHERE merchant_serial=$1"),
/* for postgres_lookup_instances() */
GNUNET_PQ_make_prepare ("lookup_instance_auth",
"SELECT"
" auth_hash"
",auth_salt"
" FROM merchant_instances"
" WHERE merchant_id=$1"),
/* for postgres_lookup_instances() */
GNUNET_PQ_make_prepare ("lookup_instances",
"SELECT"
" merchant_serial"
",merchant_pub"
",auth_hash"
",auth_salt"
",merchant_id"
",merchant_name"
",address"
",jurisdiction"
",default_max_deposit_fee_val"
",default_max_deposit_fee_frac"
",default_max_wire_fee_val"
",default_max_wire_fee_frac"
",default_wire_fee_amortization"
",default_wire_transfer_delay"
",default_pay_delay"
",website"
",email"
",logo"
" FROM merchant_instances"),
/* for postgres_lookup_instance() */
GNUNET_PQ_make_prepare ("lookup_instance",
"SELECT"
" merchant_serial"
",merchant_pub"
",auth_hash"
",auth_salt"
",merchant_id"
",merchant_name"
",address"
",jurisdiction"
",default_max_deposit_fee_val"
",default_max_deposit_fee_frac"
",default_max_wire_fee_val"
",default_max_wire_fee_frac"
",default_wire_fee_amortization"
",default_wire_transfer_delay"
",default_pay_delay"
",website"
",email"
",logo"
" FROM merchant_instances"
" WHERE merchant_id=$1"),
/* for postgres_insert_instance() */
GNUNET_PQ_make_prepare ("insert_instance",
"INSERT INTO merchant_instances"
"(merchant_pub"
",auth_hash"
",auth_salt"
",merchant_id"
",merchant_name"
",address"
",jurisdiction"
",default_max_deposit_fee_val"
",default_max_deposit_fee_frac"
",default_max_wire_fee_val"
",default_max_wire_fee_frac"
",default_wire_fee_amortization"
",default_wire_transfer_delay"
",default_pay_delay"
",website"
",email"
",logo)"
"VALUES"
"($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)"),
/* for postgres_insert_instance() */
GNUNET_PQ_make_prepare ("insert_keys",
"INSERT INTO merchant_keys"
"(merchant_priv"
",merchant_serial)"
" SELECT $1, merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$2"),
/* for postgres_account_kyc_set_status */
GNUNET_PQ_make_prepare ("upsert_account_kyc",
"INSERT INTO merchant_kyc"
"(kyc_timestamp"
",kyc_ok"
",exchange_kyc_serial"
",account_serial"
",exchange_url"
",exchange_pub"
",exchange_sig)"
" SELECT $5, $6, $4, account_serial, $3, $7, $8"
" FROM merchant_instances"
" JOIN merchant_accounts USING (merchant_serial)"
" WHERE merchant_id=$1"
" AND h_wire=$2"
" ON CONFLICT(account_serial,exchange_url) DO "
"UPDATE"
" SET exchange_kyc_serial=$4"
" ,kyc_timestamp=$5"
" ,kyc_ok=$6"
" ,exchange_pub=$7"
" ,exchange_sig=$8"),
/* for postgres_account_kyc_get_status */
GNUNET_PQ_make_prepare ("lookup_kyc_status",
"SELECT"
" h_wire"
",exchange_kyc_serial"
",payto_uri"
",exchange_url"
",kyc_timestamp"
",kyc_ok"
" FROM merchant_instances"
" JOIN merchant_accounts"
" USING (merchant_serial)"
" JOIN merchant_kyc"
" USING (account_serial)"
" WHERE merchant_instances.merchant_id=$1"),
/* for postgres_insert_account() */
GNUNET_PQ_make_prepare ("insert_account",
"INSERT INTO merchant_accounts"
"(merchant_serial"
",h_wire"
",salt"
",payto_uri"
",active)"
" SELECT merchant_serial, $2, $3, $4, $5"
" FROM merchant_instances"
" WHERE merchant_id=$1"),
/* for postgres_delete_instance_private_key() */
GNUNET_PQ_make_prepare ("delete_key",
"DELETE FROM merchant_keys"
" USING merchant_instances"
" WHERE merchant_keys.merchant_serial"
" = merchant_instances.merchant_serial"
" AND merchant_instances.merchant_id = $1"),
/* for postgres_purge_instance() */
GNUNET_PQ_make_prepare ("purge_instance",
"DELETE FROM merchant_instances"
" WHERE merchant_instances.merchant_id = $1"),
/* for postgres_update_instance() */
GNUNET_PQ_make_prepare ("update_instance",
"UPDATE merchant_instances SET"
" merchant_name=$2"
",address=$3"
",jurisdiction=$4"
",default_max_deposit_fee_val=$5"
",default_max_deposit_fee_frac=$6"
",default_max_wire_fee_val=$7"
",default_max_wire_fee_frac=$8"
",default_wire_fee_amortization=$9"
",default_wire_transfer_delay=$10"
",default_pay_delay=$11"
",website=$12"
",email=$13"
",logo=$14"
" WHERE merchant_id = $1"),
/* for postgres_update_instance_auth() */
GNUNET_PQ_make_prepare ("update_instance_auth",
"UPDATE merchant_instances SET"
" auth_hash=$2"
",auth_salt=$3"
" WHERE merchant_id=$1"),
/* for postgres_inactivate_account(); the merchant
instance is implied from the random salt that
is part of the h_wire calculation */
GNUNET_PQ_make_prepare ("inactivate_account",
"UPDATE merchant_accounts SET"
" active=FALSE"
" WHERE h_wire=$2 AND"
" merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* for postgres_activate_account() */
GNUNET_PQ_make_prepare ("activate_account",
"UPDATE merchant_accounts SET"
" active=TRUE"
" WHERE h_wire=$2 AND"
" merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* for postgres_lookup_products() */
GNUNET_PQ_make_prepare ("lookup_products",
"SELECT"
" product_id"
" FROM merchant_inventory"
" JOIN merchant_instances"
" USING (merchant_serial)"
" WHERE merchant_instances.merchant_id=$1"),
/* for postgres_lookup_product() */
GNUNET_PQ_make_prepare ("lookup_product",
"SELECT"
" description"
",description_i18n"
",unit"
",price_val"
",price_frac"
",taxes"
",total_stock"
",total_sold"
",total_lost"
",image"
",merchant_inventory.address"
",next_restock"
",minimum_age"
" FROM merchant_inventory"
" JOIN merchant_instances"
" USING (merchant_serial)"
" WHERE merchant_instances.merchant_id=$1"
" AND merchant_inventory.product_id=$2"),
/* for postgres_delete_product() */
GNUNET_PQ_make_prepare ("delete_product",
"DELETE"
" FROM merchant_inventory"
" WHERE merchant_inventory.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND merchant_inventory.product_id=$2"
" AND product_serial NOT IN "
" (SELECT product_serial FROM merchant_order_locks)"
" AND product_serial NOT IN "
" (SELECT product_serial FROM merchant_inventory_locks)"),
/* for postgres_insert_product() */
GNUNET_PQ_make_prepare ("insert_product",
"INSERT INTO merchant_inventory"
"(merchant_serial"
",product_id"
",description"
",description_i18n"
",unit"
",image"
",taxes"
",price_val"
",price_frac"
",total_stock"
",address"
",next_restock"
",minimum_age"
")"
" SELECT merchant_serial,"
" $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13"
" FROM merchant_instances"
" WHERE merchant_id=$1"),
/* for postgres_update_product() */
GNUNET_PQ_make_prepare ("update_product",
"UPDATE merchant_inventory SET"
" description=$3"
",description_i18n=$4"
",unit=$5"
",image=$6"
",taxes=$7"
",price_val=$8"
",price_frac=$9"
",total_stock=$10"
",total_lost=$11"
",address=$12"
",next_restock=$13"
",minimum_age=$14"
" WHERE merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND product_id=$2"
" AND total_stock <= $10"
" AND total_lost <= $11"),
/* for postgres_lock_product() */
GNUNET_PQ_make_prepare ("lock_product",
"WITH ps AS"
" (SELECT product_serial"
" FROM merchant_inventory"
" WHERE product_id=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1))"
"INSERT INTO merchant_inventory_locks"
"(product_serial"
",lock_uuid"
",total_locked"
",expiration)"
" SELECT product_serial, $3, $4, $5"
" FROM merchant_inventory"
" JOIN ps USING (product_serial)"
" WHERE "
" total_stock - total_sold - total_lost - $4 >= "
" (SELECT COALESCE(SUM(total_locked), 0)"
" FROM merchant_inventory_locks"
" WHERE product_serial=ps.product_serial) + "
" (SELECT COALESCE(SUM(total_locked), 0)"
" FROM merchant_order_locks"
" WHERE product_serial=ps.product_serial)"),
/* for postgres_expire_locks() */
GNUNET_PQ_make_prepare ("unlock_products",
"DELETE FROM merchant_inventory_locks"
" WHERE expiration < $1"),
/* for postgres_expire_locks() */
GNUNET_PQ_make_prepare ("unlock_orders",
"DELETE FROM merchant_orders"
" WHERE pay_deadline < $1"),
/* for postgres_expire_locks() */
GNUNET_PQ_make_prepare ("unlock_contracts",
"DELETE FROM merchant_contract_terms"
" WHERE NOT paid"
" AND pay_deadline < $1"),
/* for postgres_delete_order() */
GNUNET_PQ_make_prepare ("delete_order",
"WITH ms AS"
"(SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
", mc AS"
"(SELECT paid"
" FROM merchant_contract_terms"
" JOIN ms USING (merchant_serial)"
" WHERE order_id=$2) "
"DELETE"
" FROM merchant_orders mo"
" WHERE order_id=$2"
" AND merchant_serial=(SELECT merchant_serial FROM ms)"
" AND ( (pay_deadline < $3)"
" OR (NOT EXISTS (SELECT paid FROM mc))"
" OR ($4 AND (FALSE=(SELECT paid FROM mc))) );"),
GNUNET_PQ_make_prepare ("delete_contract",
"DELETE"
" FROM merchant_contract_terms"
" WHERE order_id=$2 AND"
" merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND NOT paid;"),
/* for postgres_lookup_order() */
GNUNET_PQ_make_prepare ("lookup_order",
"SELECT"
" contract_terms"
",claim_token"
",h_post_data"
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND merchant_orders.order_id=$2"),
/* for postgres_lookup_order_summary() */
GNUNET_PQ_make_prepare ("lookup_order_summary",
"(SELECT"
" creation_time"
",order_serial"
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND merchant_contract_terms.order_id=$2)"
"UNION"
"(SELECT"
" creation_time"
",order_serial"
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND merchant_orders.order_id=$2)"),
/* for postgres_lookup_orders() */
GNUNET_PQ_make_prepare ("lookup_orders_inc",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" ORDER BY order_serial ASC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" ORDER BY order_serial ASC"
" LIMIT $2)"
" ORDER BY order_serial ASC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_inc_paid",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" AND"
" NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
" AND"
" order_serial NOT IN"
" (SELECT order_serial"
" FROM merchant_contract_terms)" /* only select unclaimed orders */
" ORDER BY order_serial ASC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" AND"
" BOOL($5) = paid"
" ORDER BY order_serial ASC"
" LIMIT $2)"
" ORDER BY order_serial ASC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_inc_refunded",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" AND"
" NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
" AND"
" order_serial NOT IN"
" (SELECT order_serial"
" FROM merchant_contract_terms)" /* only select unclaimed orders */
" ORDER BY order_serial ASC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" AND"
" CAST($6 as BOOL) = (order_serial IN"
" (SELECT order_serial "
" FROM merchant_refunds))"
" ORDER BY order_serial ASC"
" LIMIT $2)"
" ORDER BY order_serial ASC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_inc_wired",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" AND"
" NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
" AND"
" order_serial NOT IN"
" (SELECT order_serial"
" FROM merchant_contract_terms)" /* only select unclaimed orders */
" ORDER BY order_serial ASC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" AND"
" BOOL($7) = wired"
" ORDER BY order_serial ASC"
" LIMIT $2)"
" ORDER BY order_serial ASC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_inc_paid_refunded",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" AND"
" NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
" AND"
" NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
" AND"
" order_serial NOT IN"
" (SELECT order_serial"
" FROM merchant_contract_terms)" /* only select unclaimed orders */
" ORDER BY order_serial ASC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" AND"
" BOOL($5) = paid"
" AND"
" BOOL($6) = (order_serial IN"
" (SELECT order_serial "
" FROM merchant_refunds))"
" ORDER BY order_serial ASC"
" LIMIT $2)"
" ORDER BY order_serial ASC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_inc_paid_wired",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" AND"
" NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
" AND"
" NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
" AND"
" order_serial NOT IN"
" (SELECT order_serial"
" FROM merchant_contract_terms)" /* only select unclaimed orders */
" ORDER BY order_serial ASC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" AND"
" BOOL($5) = paid"
" AND"
" BOOL($7) = wired"
" ORDER BY order_serial ASC"
" LIMIT $2)"
" ORDER BY order_serial ASC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_inc_refunded_wired",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" AND"
" NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
" AND"
" NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
" AND"
" order_serial NOT IN"
" (SELECT order_serial"
" FROM merchant_contract_terms)" /* only select unclaimed orders */
" ORDER BY order_serial ASC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" AND"
" BOOL($6) = (order_serial IN"
" (SELECT order_serial "
" FROM merchant_refunds))"
" AND"
" BOOL($7) = wired"
" ORDER BY order_serial ASC"
" LIMIT $2)"
" ORDER BY order_serial ASC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_inc_paid_refunded_wired",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" AND"
" NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
" AND"
" NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
" AND"
" NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
" AND"
" order_serial NOT IN"
" (SELECT order_serial"
" FROM merchant_contract_terms)" /* only select unclaimed orders */
" ORDER BY order_serial ASC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial > $3"
" AND"
" creation_time > $4"
" AND"
" BOOL($5) = paid"
" AND"
" BOOL($6) = (order_serial IN"
" (SELECT order_serial "
" FROM merchant_refunds))"
" AND"
" BOOL($7) = wired"
" ORDER BY order_serial ASC"
" LIMIT $2)"
" ORDER BY order_serial ASC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_dec",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" ORDER BY order_serial DESC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" ORDER BY order_serial DESC"
" LIMIT $2)"
" ORDER BY order_serial DESC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_dec_paid",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" AND"
" NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
" AND"
" order_serial NOT IN"
" (SELECT order_serial"
" FROM merchant_contract_terms)" /* only select unclaimed orders */
" ORDER BY order_serial DESC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" AND"
" BOOL($5) = paid"
" ORDER BY order_serial DESC"
" LIMIT $2)"
" ORDER BY order_serial DESC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_dec_refunded",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" AND"
" NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
" AND"
" order_serial NOT IN"
" (SELECT order_serial"
" FROM merchant_contract_terms)" /* only select unclaimed orders */
" ORDER BY order_serial DESC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" AND"
" BOOL($6) = (order_serial IN"
" (SELECT order_serial "
" FROM merchant_refunds))"
" ORDER BY order_serial DESC"
" LIMIT $2)"
" ORDER BY order_serial DESC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_dec_wired",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" AND"
" NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
" AND"
" order_serial NOT IN"
" (SELECT order_serial"
" FROM merchant_contract_terms)" /* only select unclaimed orders */
" ORDER BY order_serial DESC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" AND"
" BOOL($7) = wired"
" ORDER BY order_serial DESC"
" LIMIT $2)"
" ORDER BY order_serial DESC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_dec_paid_refunded",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" AND"
" NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
" AND"
" NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
" AND"
" order_serial NOT IN"
" (SELECT order_serial"
" FROM merchant_contract_terms)" /* only select unclaimed orders */
" ORDER BY order_serial DESC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" AND"
" BOOL($5) = paid"
" AND"
" BOOL($6) = (order_serial IN"
" (SELECT order_serial "
" FROM merchant_refunds))"
" ORDER BY order_serial DESC"
" LIMIT $2)"
" ORDER BY order_serial DESC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_dec_paid_wired",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" AND"
" NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
" AND"
" NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
" AND"
" order_serial NOT IN"
" (SELECT order_serial"
" FROM merchant_contract_terms)" /* only select unclaimed orders */
" ORDER BY order_serial DESC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" AND"
" BOOL($5) = paid"
" AND"
" BOOL($7) = wired"
" ORDER BY order_serial DESC"
" LIMIT $2)"
" ORDER BY order_serial DESC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_dec_refunded_wired",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" AND"
" NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
" AND"
" NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
" AND"
" order_serial NOT IN"
" (SELECT order_serial"
" FROM merchant_contract_terms)" /* only select unclaimed orders */
" ORDER BY order_serial DESC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" AND"
" BOOL($6) = (order_serial IN"
" (SELECT order_serial "
" FROM merchant_refunds))"
" AND"
" BOOL($7) = wired"
" ORDER BY order_serial DESC"
" LIMIT $2)"
" ORDER BY order_serial DESC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_orders_dec_paid_refunded_wired",
"(SELECT"
" order_id"
",order_serial"
",creation_time"
" FROM merchant_orders"
" WHERE merchant_orders.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" AND"
" NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */
" AND"
" NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */
" AND"
" NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */
" AND"
" order_serial NOT IN"
" (SELECT order_serial"
" FROM merchant_contract_terms)" /* only select unclaimed orders */
" ORDER BY order_serial DESC"
" LIMIT $2)"
"UNION " /* union ensures elements are distinct! */
"(SELECT"
" order_id"
",order_serial"
",creation_time"
" FROM merchant_contract_terms"
" WHERE merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" order_serial < $3"
" AND"
" creation_time < $4"
" AND"
" BOOL($5) = paid"
" AND"
" BOOL($6) = (order_serial IN"
" (SELECT order_serial "
" FROM merchant_refunds))"
" AND"
" BOOL($7) = wired"
" ORDER BY order_serial DESC"
" LIMIT $2)"
" ORDER BY order_serial DESC"
" LIMIT $2"),
/* for postgres_insert_order() */
GNUNET_PQ_make_prepare ("insert_order",
"INSERT INTO merchant_orders"
"(merchant_serial"
",order_id"
",pay_deadline"
",claim_token"
",h_post_data"
",creation_time"
",contract_terms)"
" SELECT merchant_serial,"
" $2, $3, $4, $5, $6, $7"
" FROM merchant_instances"
" WHERE merchant_id=$1"),
/* for postgres_unlock_inventory() */
GNUNET_PQ_make_prepare ("unlock_inventory",
"DELETE"
" FROM merchant_inventory_locks"
" WHERE lock_uuid=$1"),
/* for postgres_insert_order_lock() */
GNUNET_PQ_make_prepare ("insert_order_lock",
"WITH tmp AS"
" (SELECT "
" product_serial"
" ,merchant_serial"
" ,total_stock"
" ,total_sold"
" ,total_lost"
" FROM merchant_inventory"
" WHERE product_id=$3"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1))"
" INSERT INTO merchant_order_locks"
" (product_serial"
" ,total_locked"
" ,order_serial)"
" SELECT tmp.product_serial, $4, order_serial"
" FROM merchant_orders"
" JOIN tmp USING(merchant_serial)"
" WHERE order_id=$2 AND"
" tmp.total_stock - tmp.total_sold - tmp.total_lost - $4 >= "
" (SELECT COALESCE(SUM(total_locked), 0)"
" FROM merchant_inventory_locks"
" WHERE product_serial=tmp.product_serial) + "
" (SELECT COALESCE(SUM(total_locked), 0)"
" FROM merchant_order_locks"
" WHERE product_serial=tmp.product_serial)"),
/* for postgres_lookup_contract_terms() */
GNUNET_PQ_make_prepare ("lookup_contract_terms",
"SELECT"
" contract_terms"
",order_serial"
",claim_token"
",paid"
" FROM merchant_contract_terms"
" WHERE order_id=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* for postgres_insert_contract_terms() */
GNUNET_PQ_make_prepare ("insert_contract_terms",
"INSERT INTO merchant_contract_terms"
"(order_serial"
",merchant_serial"
",order_id"
",contract_terms"
",h_contract_terms"
",creation_time"
",pay_deadline"
",refund_deadline"
",fulfillment_url"
",claim_token)"
"SELECT"
" mo.order_serial"
",mo.merchant_serial"
",mo.order_id"
",$3" /* contract_terms */
",$4" /* h_contract_terms */
",mo.creation_time"
",$5" /* pay_deadline */
",$6" /* refund_deadline */
",$7" /* fulfillment_url */
",mo.claim_token "
"FROM merchant_orders mo"
" WHERE order_id=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" RETURNING order_serial"),
/* for postgres_update_contract_terms() */
GNUNET_PQ_make_prepare ("update_contract_terms",
"UPDATE merchant_contract_terms SET"
" contract_terms=$3"
",h_contract_terms=$4"
",pay_deadline=$5"
",refund_deadline=$6"
",fulfillment_url=$7"
" WHERE order_id=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* for postgres_delete_contract_terms() */
GNUNET_PQ_make_prepare ("delete_contract_terms",
"DELETE FROM merchant_contract_terms"
" WHERE order_id=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND ( ( (pay_deadline < $4) AND"
" (NOT paid) ) OR"
" (creation_time + $3 < $4) )"),
/* for postgres_lookup_deposits() */
GNUNET_PQ_make_prepare ("lookup_deposits",
"SELECT"
" exchange_url"
",coin_pub"
",amount_with_fee_val"
",amount_with_fee_frac"
",deposit_fee_val"
",deposit_fee_frac"
",refund_fee_val"
",refund_fee_frac"
",wire_fee_val"
",wire_fee_frac"
" FROM merchant_deposits"
" WHERE order_serial="
" (SELECT order_serial"
" FROM merchant_contract_terms"
" WHERE h_contract_terms=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1))"),
/* for postgres_insert_exchange_signkey() */
GNUNET_PQ_make_prepare ("insert_exchange_signkey",
"INSERT INTO merchant_exchange_signing_keys"
"(master_pub"
",exchange_pub"
",start_date"
",expire_date"
",end_date"
",master_sig)"
"VALUES"
"($1, $2, $3, $4, $5, $6)"),
/* for postgres_insert_deposit() */
GNUNET_PQ_make_prepare ("insert_deposit",
"WITH md AS"
" (SELECT account_serial, merchant_serial"
" FROM merchant_accounts"
" WHERE h_wire=$14"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1))"
", ed AS"
" (SELECT signkey_serial"
" FROM merchant_exchange_signing_keys"
" WHERE exchange_pub=$16"
" ORDER BY start_date DESC"
" LIMIT 1)"
"INSERT INTO merchant_deposits"
"(order_serial"
",deposit_timestamp"
",coin_pub"
",exchange_url"
",amount_with_fee_val"
",amount_with_fee_frac"
",deposit_fee_val"
",deposit_fee_frac"
",refund_fee_val"
",refund_fee_frac"
",wire_fee_val"
",wire_fee_frac"
",exchange_sig"
",signkey_serial"
",account_serial)"
" SELECT "
" order_serial"
" ,$3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $15"
" ,ed.signkey_serial"
" ,md.account_serial"
" FROM merchant_contract_terms"
" JOIN md USING (merchant_serial)"
" FULL OUTER JOIN ed ON TRUE"
" WHERE h_contract_terms=$2"),
/* for postgres_lookup_refunds() */
GNUNET_PQ_make_prepare ("lookup_refunds",
"SELECT"
" coin_pub"
",refund_amount_val"
",refund_amount_frac"
" FROM merchant_refunds"
" WHERE order_serial="
" (SELECT order_serial"
" FROM merchant_contract_terms"
" WHERE h_contract_terms=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1))"),
/* for postgres_mark_contract_paid() */
GNUNET_PQ_make_prepare ("mark_contract_paid",
"UPDATE merchant_contract_terms SET"
" paid=TRUE"
",session_id=$3"
" WHERE h_contract_terms=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* for postgres_mark_contract_paid() */
GNUNET_PQ_make_prepare ("mark_inventory_sold",
"UPDATE merchant_inventory SET"
" total_sold=total_sold + order_locks.total_locked"
" FROM (SELECT total_locked,product_serial"
" FROM merchant_order_locks"
" WHERE order_serial="
" (SELECT order_serial"
" FROM merchant_contract_terms"
" WHERE h_contract_terms=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1))"
" ) AS order_locks"
" WHERE merchant_inventory.product_serial"
" =order_locks.product_serial"),
/* for postgres_mark_contract_paid() */
GNUNET_PQ_make_prepare ("delete_completed_order",
"WITH md AS"
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1) "
"DELETE"
" FROM merchant_orders"
" WHERE order_serial="
" (SELECT order_serial"
" FROM merchant_contract_terms"
" JOIN md USING (merchant_serial)"
" WHERE h_contract_terms=$2)"),
/* for postgres_refund_coin() */
GNUNET_PQ_make_prepare ("refund_coin",
"INSERT INTO merchant_refunds"
"(order_serial"
",rtransaction_id"
",refund_timestamp"
",coin_pub"
",reason"
",refund_amount_val"
",refund_amount_frac"
") "
"SELECT "
" order_serial"
",0" /* rtransaction_id always 0 for /abort */
",$3"
",coin_pub"
",$5"
",amount_with_fee_val"
",amount_with_fee_frac"
" FROM merchant_deposits"
" WHERE coin_pub=$4"
" AND order_serial="
" (SELECT order_serial"
" FROM merchant_contract_terms"
" WHERE h_contract_terms=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1))"),
/* for postgres_lookup_order_status() */
GNUNET_PQ_make_prepare ("lookup_order_status",
"SELECT"
" h_contract_terms"
",paid"
" FROM merchant_contract_terms"
" WHERE merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND order_id=$2"),
/* for postgres_lookup_order_status_by_serial() */
GNUNET_PQ_make_prepare ("lookup_order_status_by_serial",
"SELECT"
" h_contract_terms"
",order_id"
",paid"
" FROM merchant_contract_terms"
" WHERE merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND order_serial=$2"),
/* for postgres_lookup_payment_status() */
GNUNET_PQ_make_prepare ("lookup_payment_status",
"SELECT"
" wired"
",paid"
" FROM merchant_contract_terms"
" WHERE order_serial=$1"),
/* for postgres_lookup_payment_status() */
GNUNET_PQ_make_prepare ("lookup_payment_status_session_id",
"SELECT"
" wired"
",paid"
" FROM merchant_contract_terms"
" WHERE order_serial=$1"
" AND session_id=$2"),
/* for postgres_lookup_deposits_by_order() */
GNUNET_PQ_make_prepare ("lookup_deposits_by_order",
"SELECT"
" deposit_serial"
",exchange_url"
",h_wire"
",amount_with_fee_val"
",amount_with_fee_frac"
",deposit_fee_val"
",deposit_fee_frac"
",coin_pub"
" FROM merchant_deposits"
" JOIN merchant_accounts USING (account_serial)"
" WHERE order_serial=$1"),
/* for postgres_lookup_transfer_details_by_order() */
GNUNET_PQ_make_prepare ("lookup_transfer_details_by_order",
"SELECT"
" md.deposit_serial"
",md.exchange_url"
",mt.wtid"
",exchange_deposit_value_val"
",exchange_deposit_value_frac"
",exchange_deposit_fee_val"
",exchange_deposit_fee_frac"
",deposit_timestamp"
",mt.confirmed AS transfer_confirmed"
" FROM merchant_transfer_to_coin"
" JOIN merchant_deposits AS md USING (deposit_serial)"
" JOIN merchant_transfers AS mt USING (credit_serial)"
" WHERE deposit_serial IN"
" (SELECT deposit_serial"
" FROM merchant_deposits"
" WHERE order_serial=$1)"),
/* for postgres_insert_deposit_to_transfer() */
GNUNET_PQ_make_prepare ("insert_deposit_to_transfer",
"INSERT INTO merchant_deposit_to_transfer"
"(deposit_serial"
",coin_contribution_value_val"
",coin_contribution_value_frac"
",credit_serial"
",execution_time"
",signkey_serial"
",exchange_sig"
") SELECT $1, $2, $3, credit_serial, $4, signkey_serial, $5"
" FROM merchant_transfers"
" CROSS JOIN merchant_exchange_signing_keys"
" WHERE exchange_pub=$6"
" AND wtid=$7"),
/* for postgres_mark_order_wired() */
GNUNET_PQ_make_prepare ("mark_order_wired",
"UPDATE merchant_contract_terms SET"
" wired=true"
" WHERE order_serial=$1"),
/* for process_refund_cb() used in postgres_increase_refund() */
GNUNET_PQ_make_prepare ("find_refunds_by_coin",
"SELECT"
" refund_amount_val"
",refund_amount_frac"
",rtransaction_id"
" FROM merchant_refunds"
" WHERE coin_pub=$1"
" AND order_serial=$2"),
/* for process_deposits_for_refund_cb() used in postgres_increase_refund() */
GNUNET_PQ_make_prepare ("insert_refund",
"INSERT INTO merchant_refunds"
"(order_serial"
",rtransaction_id"
",refund_timestamp"
",coin_pub"
",reason"
",refund_amount_val"
",refund_amount_frac"
") VALUES"
"($1, $2, $3, $4, $5, $6, $7)"),
/* for postgres_increase_refund() */
GNUNET_PQ_make_prepare ("find_deposits_for_refund",
"SELECT"
" coin_pub"
",order_serial"
",amount_with_fee_val"
",amount_with_fee_frac"
" FROM merchant_deposits"
" WHERE order_serial="
" (SELECT order_serial"
" FROM merchant_contract_terms"
" WHERE order_id=$2"
" AND paid=TRUE"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1))"),
/* for postgres_lookup_refunds_detailed() */
GNUNET_PQ_make_prepare ("lookup_refunds_detailed",
"SELECT"
" refund_serial"
",refund_timestamp"
",coin_pub"
",merchant_deposits.exchange_url"
",rtransaction_id"
",reason"
",refund_amount_val"
",refund_amount_frac"
",merchant_refund_proofs.exchange_sig IS NULL AS pending"
" FROM merchant_refunds"
" JOIN merchant_deposits USING (order_serial, coin_pub)"
" LEFT JOIN merchant_refund_proofs USING (refund_serial)"
" WHERE order_serial="
" (SELECT order_serial"
" FROM merchant_contract_terms"
" WHERE h_contract_terms=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1))"),
/* for postgres_insert_refund_proof() */
GNUNET_PQ_make_prepare ("insert_refund_proof",
"INSERT INTO merchant_refund_proofs"
"(refund_serial"
",exchange_sig"
",signkey_serial)"
"SELECT $1, $2, signkey_serial"
" FROM merchant_exchange_signing_keys"
" WHERE exchange_pub=$3"
" ORDER BY start_date DESC"
" LIMIT 1"),
/* for postgres_lookup_refund_proof() */
GNUNET_PQ_make_prepare ("lookup_refund_proof",
"SELECT"
" merchant_exchange_signing_keys.exchange_pub"
",exchange_sig"
" FROM merchant_refund_proofs"
" JOIN merchant_exchange_signing_keys"
" USING (signkey_serial)"
" WHERE"
" refund_serial=$1"),
/* for postgres_lookup_order_by_fulfillment() */
GNUNET_PQ_make_prepare ("lookup_order_by_fulfillment",
"SELECT"
" order_id"
" FROM merchant_contract_terms"
" WHERE fulfillment_url=$2"
" AND session_id=$3"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* for postgres_insert_transfer() */
GNUNET_PQ_make_prepare ("insert_transfer",
"INSERT INTO merchant_transfers"
"(exchange_url"
",wtid"
",credit_amount_val"
",credit_amount_frac"
",account_serial"
",confirmed)"
"SELECT"
" $1, $2, $3, $4, account_serial, $6"
" FROM merchant_accounts"
" WHERE payto_uri=$5"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$7)"),
/* for postgres_delete_transfer() */
GNUNET_PQ_make_prepare ("delete_transfer",
"DELETE FROM merchant_transfers"
" WHERE"
" credit_serial=$2"
" AND account_serial IN "
" (SELECT account_serial "
" FROM merchant_accounts"
" WHERE merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1))"),
/* for postgres_check_transfer_exists() */
GNUNET_PQ_make_prepare ("check_transfer_exists",
"SELECT"
" 1"
" FROM merchant_transfers"
" JOIN merchant_accounts"
" USING (account_serial)"
" WHERE"
" credit_serial=$2"
" AND"
" merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* for postgres_lookup_account() */
GNUNET_PQ_make_prepare ("lookup_account",
"SELECT"
" account_serial"
" FROM merchant_accounts"
" WHERE payto_uri=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* for postgres_insert_transfer_details() */
GNUNET_PQ_make_prepare ("lookup_credit_serial",
"SELECT"
" credit_serial"
" FROM merchant_transfers"
" WHERE exchange_url=$1"
" AND wtid=$4"
" AND account_serial="
" (SELECT account_serial"
" FROM merchant_accounts"
" WHERE payto_uri=$2"
" AND exchange_url=$1"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$3))"),
/* for postgres_insert_transfer_details() */
GNUNET_PQ_make_prepare ("insert_transfer_signature",
"INSERT INTO merchant_transfer_signatures"
"(credit_serial"
",signkey_serial"
",credit_amount_val"
",credit_amount_frac"
",wire_fee_val"
",wire_fee_frac"
",execution_time"
",exchange_sig) "
"SELECT $1, signkey_serial, $2, $3, $4, $5, $6, $7"
" FROM merchant_exchange_signing_keys"
" WHERE exchange_pub=$8"
" ORDER BY start_date DESC"
" LIMIT 1"),
/* for postgres_insert_transfer_details() */
GNUNET_PQ_make_prepare ("insert_transfer_to_coin_mapping",
"INSERT INTO merchant_transfer_to_coin"
"(deposit_serial"
",credit_serial"
",offset_in_exchange_list"
",exchange_deposit_value_val"
",exchange_deposit_value_frac"
",exchange_deposit_fee_val"
",exchange_deposit_fee_frac) "
"SELECT deposit_serial, $1, $2, $3, $4, $5, $6"
" FROM merchant_deposits"
" JOIN merchant_contract_terms USING (order_serial)"
" WHERE coin_pub=$7"
" AND h_contract_terms=$8"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$9)"),
/* for postgres_insert_transfer_details() */
GNUNET_PQ_make_prepare ("update_wired_by_coin_pub",
"WITH os AS" /* select orders affected by the coin */
"(SELECT order_serial"
" FROM merchant_deposits"
" WHERE coin_pub=$1)"
"UPDATE merchant_contract_terms "
" SET wired=TRUE "
" WHERE order_serial IN "
" (SELECT order_serial FROM merchant_deposits" /* only orders for which NO un-wired coin exists*/
" WHERE NOT EXISTS "
" (SELECT order_serial FROM merchant_deposits" /* orders for which ANY un-wired coin exists */
" JOIN os USING (order_serial)" /* filter early */
" WHERE deposit_serial NOT IN"
" (SELECT deposit_serial " /* all coins associated with order that WERE wired */
" FROM merchant_deposits "
" JOIN os USING (order_serial)" /* filter early */
" JOIN merchant_deposit_to_transfer USING (deposit_serial)"
" JOIN merchant_transfers USING (credit_serial)"
" WHERE confirmed=TRUE)))"),
/* for postgres_lookup_wire_fee() */
GNUNET_PQ_make_prepare ("lookup_wire_fee",
"SELECT"
" wire_fee_val"
",wire_fee_frac"
",closing_fee_val"
",closing_fee_frac"
",start_date"
",end_date"
",master_sig"
" FROM merchant_exchange_wire_fees"
" WHERE master_pub=$1"
" AND h_wire_method=$2"
" AND start_date <= $3"
" AND end_date > $3"),
/* for postgres_lookup_deposits_by_contract_and_coin() */
GNUNET_PQ_make_prepare ("lookup_deposits_by_contract_and_coin",
"SELECT"
" exchange_url"
",amount_with_fee_val"
",amount_with_fee_frac"
",deposit_fee_val"
",deposit_fee_frac"
",refund_fee_val"
",refund_fee_frac"
",wire_fee_val"
",wire_fee_frac"
",h_wire"
",deposit_timestamp"
",refund_deadline"
",exchange_sig"
",exchange_pub"
" FROM merchant_contract_terms"
" JOIN merchant_deposits USING (order_serial)"
" JOIN merchant_exchange_signing_keys USING (signkey_serial)"
" JOIN merchant_accounts USING (account_serial)"
" WHERE h_contract_terms=$2"
" AND coin_pub=$3"
" AND merchant_contract_terms.merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* for postgres_lookup_transfer() */
GNUNET_PQ_make_prepare ("lookup_transfer",
"SELECT"
" mt.credit_amount_val AS credit_amount_val"
",mt.credit_amount_frac AS credit_amount_frac"
",mts.credit_amount_val AS exchange_amount_val"
",mts.credit_amount_frac AS exchange_amount_frac"
",wire_fee_val"
",wire_fee_frac"
",execution_time"
",verified"
" FROM merchant_transfers mt"
" JOIN merchant_accounts USING (account_serial)"
" JOIN merchant_instances USING (merchant_serial)"
" LEFT JOIN merchant_transfer_signatures mts USING (credit_serial)"
" WHERE wtid=$2"
" AND exchange_url=$1"
" AND merchant_id=$3;"),
/* for postgres_set_transfer_status_to_verified() */
GNUNET_PQ_make_prepare ("set_transfer_status_to_verified",
"UPDATE merchant_transfers SET"
" verified=TRUE"
" WHERE wtid=$1"
" AND exchange_url=$2"),
/* for postgres_lookup_transfer_summary() */
GNUNET_PQ_make_prepare ("lookup_transfer_summary",
"SELECT"
" order_id"
",exchange_deposit_value_val"
",exchange_deposit_value_frac"
",exchange_deposit_fee_val"
",exchange_deposit_fee_frac"
" FROM merchant_transfers"
" JOIN merchant_transfer_to_coin USING (credit_serial)"
" JOIN merchant_deposits USING (deposit_serial)"
" JOIN merchant_contract_terms USING (order_serial)"
" WHERE wtid=$2"
" AND merchant_transfers.exchange_url=$1"),
/* for postgres_lookup_transfer_details() */
GNUNET_PQ_make_prepare ("lookup_transfer_details",
"SELECT"
" merchant_contract_terms.h_contract_terms"
",merchant_transfer_to_coin.offset_in_exchange_list"
",merchant_deposits.coin_pub"
",exchange_deposit_value_val"
",exchange_deposit_value_frac"
",exchange_deposit_fee_val"
",exchange_deposit_fee_frac"
" FROM merchant_transfer_to_coin"
" JOIN merchant_deposits USING (deposit_serial)"
" JOIN merchant_contract_terms USING (order_serial)"
" JOIN merchant_transfers USING (credit_serial)"
" WHERE merchant_transfers.wtid=$2"
" AND merchant_transfers.exchange_url=$1"),
/* for postgres_lookup_transfers() */
GNUNET_PQ_make_prepare ("lookup_transfers_time_payto_asc",
"SELECT"
" mt.credit_amount_val"
",mt.credit_amount_frac"
",wtid"
",merchant_accounts.payto_uri"
",exchange_url"
",credit_serial"
",merchant_transfer_signatures.execution_time"
",verified"
",confirmed"
" FROM merchant_transfers mt"
" JOIN merchant_accounts USING (account_serial)"
" JOIN merchant_transfer_signatures USING (credit_serial)"
" WHERE execution_time < $2"
" AND execution_time >= $3"
" AND credit_serial > $4"
" AND payto_uri = $6"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" ORDER BY credit_serial ASC"
" LIMIT $5"),
/* for postgres_lookup_transfers() */
GNUNET_PQ_make_prepare ("lookup_transfers_time_asc",
"SELECT"
" mt.credit_amount_val"
",mt.credit_amount_frac"
",wtid"
",merchant_accounts.payto_uri"
",exchange_url"
",credit_serial"
",merchant_transfer_signatures.execution_time"
",verified"
",confirmed"
" FROM merchant_transfers mt"
" JOIN merchant_accounts USING (account_serial)"
" JOIN merchant_transfer_signatures USING (credit_serial)"
" WHERE execution_time < $2"
" AND execution_time >= $3"
" AND credit_serial > $4"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" ORDER BY credit_serial ASC"
" LIMIT $5"),
/* for postgres_lookup_transfers() */
GNUNET_PQ_make_prepare ("lookup_transfers_payto_asc",
"SELECT"
" mt.credit_amount_val"
",mt.credit_amount_frac"
",wtid"
",merchant_accounts.payto_uri"
",exchange_url"
",credit_serial"
",CASE WHEN (merchant_transfer_signatures.execution_time) IS NULL"
" THEN 9223372036854775807" /* largest BIGINT possible */
" ELSE merchant_transfer_signatures.execution_time"
" END AS execution_time"
",verified"
",confirmed"
" FROM merchant_transfers mt"
" JOIN merchant_accounts USING (account_serial)"
" LEFT JOIN merchant_transfer_signatures USING (credit_serial)"
" WHERE credit_serial > $2"
" AND payto_uri = $4"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" ORDER BY credit_serial ASC"
" LIMIT $3"),
/* for postgres_lookup_transfers() */
GNUNET_PQ_make_prepare ("lookup_transfers_asc",
"SELECT"
" mt.credit_amount_val"
",mt.credit_amount_frac"
",wtid"
",merchant_accounts.payto_uri"
",exchange_url"
",credit_serial"
",CASE WHEN (merchant_transfer_signatures.execution_time) IS NULL"
" THEN 9223372036854775807" /* largest BIGINT possible */
" ELSE merchant_transfer_signatures.execution_time"
" END AS execution_time"
",verified"
",confirmed"
" FROM merchant_transfers mt"
" JOIN merchant_accounts USING (account_serial)"
" LEFT JOIN merchant_transfer_signatures USING (credit_serial)"
" WHERE credit_serial > $2"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" ORDER BY credit_serial ASC"
" LIMIT $3"),
/* for postgres_lookup_transfers() */
GNUNET_PQ_make_prepare ("lookup_transfers_time_payto_desc",
"SELECT"
" mt.credit_amount_val"
",mt.credit_amount_frac"
",wtid"
",merchant_accounts.payto_uri"
",exchange_url"
",credit_serial"
",merchant_transfer_signatures.execution_time"
",verified"
",confirmed"
" FROM merchant_transfers mt"
" JOIN merchant_accounts USING (account_serial)"
" JOIN merchant_transfer_signatures USING (credit_serial)"
" WHERE execution_time < $2"
" AND execution_time >= $3"
" AND credit_serial < $4"
" AND payto_uri = $6"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" ORDER BY credit_serial DESC"
" LIMIT $5"),
/* for postgres_lookup_transfers() */
GNUNET_PQ_make_prepare ("lookup_transfers_time_desc",
"SELECT"
" mt.credit_amount_val"
",mt.credit_amount_frac"
",wtid"
",merchant_accounts.payto_uri"
",exchange_url"
",credit_serial"
",merchant_transfer_signatures.execution_time"
",verified"
",confirmed"
" FROM merchant_transfers mt"
" JOIN merchant_accounts USING (account_serial)"
" JOIN merchant_transfer_signatures USING (credit_serial)"
" WHERE execution_time < $2"
" AND execution_time >= $3"
" AND credit_serial < $4"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" ORDER BY credit_serial DESC"
" LIMIT $5"),
/* for postgres_lookup_transfers() */
GNUNET_PQ_make_prepare ("lookup_transfers_payto_desc",
"SELECT"
" mt.credit_amount_val"
",mt.credit_amount_frac"
",wtid"
",merchant_accounts.payto_uri"
",exchange_url"
",credit_serial"
",CASE WHEN (merchant_transfer_signatures.execution_time) IS NULL"
" THEN 9223372036854775807" /* largest BIGINT possible */
" ELSE merchant_transfer_signatures.execution_time"
" END AS execution_time"
",verified"
",confirmed"
" FROM merchant_transfers mt"
" JOIN merchant_accounts USING (account_serial)"
" LEFT JOIN merchant_transfer_signatures USING (credit_serial)"
" WHERE credit_serial < $2"
" AND payto_uri = $4"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" ORDER BY credit_serial DESC"
" LIMIT $3"),
/* for postgres_lookup_transfers() */
GNUNET_PQ_make_prepare ("lookup_transfers_desc",
"SELECT"
" mt.credit_amount_val"
",mt.credit_amount_frac"
",wtid"
",merchant_accounts.payto_uri"
",exchange_url"
",credit_serial"
",CASE WHEN (merchant_transfer_signatures.execution_time) IS NULL"
" THEN 9223372036854775807" /* largest BIGINT possible */
" ELSE merchant_transfer_signatures.execution_time"
" END AS execution_time"
",verified"
",confirmed"
" FROM merchant_transfers mt"
" JOIN merchant_accounts USING (account_serial)"
" LEFT JOIN merchant_transfer_signatures USING (credit_serial)"
" WHERE credit_serial < $2"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" ORDER BY credit_serial DESC"
" LIMIT $3"),
/* For postgres_store_wire_fee_by_exchange() */
GNUNET_PQ_make_prepare ("insert_wire_fee",
"INSERT INTO merchant_exchange_wire_fees"
"(master_pub"
",h_wire_method"
",wire_fee_val"
",wire_fee_frac"
",closing_fee_val"
",closing_fee_frac"
",start_date"
",end_date"
",master_sig)"
" VALUES "
"($1, $2, $3, $4, $5, $6, $7, $8, $9)"),
/* For postgres_insert_reserve() */
GNUNET_PQ_make_prepare ("insert_reserve",
"INSERT INTO merchant_tip_reserves"
"(reserve_pub"
",merchant_serial"
",creation_time"
",expiration"
",merchant_initial_balance_val"
",merchant_initial_balance_frac"
")"
"SELECT $2, merchant_serial, $3, $4, $5, $6"
" FROM merchant_instances"
" WHERE merchant_id=$1"),
/* For postgres_activate_reserve() */
GNUNET_PQ_make_prepare ("activate_reserve",
"UPDATE merchant_tip_reserves SET"
" exchange_initial_balance_val=$3"
",exchange_initial_balance_frac=$4"
" WHERE reserve_pub=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* For postgres_insert_reserve() */
GNUNET_PQ_make_prepare ("insert_reserve_key",
"INSERT INTO merchant_tip_reserve_keys"
"(reserve_serial"
",reserve_priv"
",exchange_url"
",payto_uri"
")"
"SELECT reserve_serial, $3, $4, $5"
" FROM merchant_tip_reserves"
" WHERE reserve_pub=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* For postgres_lookup_reserves() */
GNUNET_PQ_make_prepare ("lookup_reserves",
"SELECT"
" reserve_pub"
",creation_time"
",expiration"
",merchant_initial_balance_val"
",merchant_initial_balance_frac"
",exchange_initial_balance_val"
",exchange_initial_balance_frac"
",tips_committed_val"
",tips_committed_frac"
",tips_picked_up_val"
",tips_picked_up_frac"
",reserve_priv IS NOT NULL AS active"
" FROM merchant_tip_reserves"
" FULL OUTER JOIN merchant_tip_reserve_keys USING (reserve_serial)"
" WHERE creation_time > $2"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* For postgres_lookup_pending_reserves() */
GNUNET_PQ_make_prepare ("lookup_pending_reserves",
"SELECT"
" reserve_pub"
",merchant_id"
",exchange_url"
",merchant_initial_balance_val"
",merchant_initial_balance_frac"
" FROM merchant_tip_reserves"
" JOIN merchant_instances USING (merchant_serial)"
" JOIN merchant_tip_reserve_keys USING (reserve_serial)"
" WHERE exchange_initial_balance_val=0"
" AND exchange_initial_balance_frac=0"),
/* For postgres_lookup_reserve() */
GNUNET_PQ_make_prepare ("lookup_reserve",
"SELECT"
" creation_time"
",expiration"
",merchant_initial_balance_val"
",merchant_initial_balance_frac"
",exchange_initial_balance_val"
",exchange_initial_balance_frac"
",tips_committed_val"
",tips_committed_frac"
",tips_picked_up_val"
",tips_picked_up_frac"
",reserve_priv IS NOT NULL AS active"
",exchange_url"
",payto_uri"
" FROM merchant_tip_reserves"
" FULL OUTER JOIN merchant_tip_reserve_keys USING (reserve_serial)"
" WHERE reserve_pub = $2"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* For postgres_lookup_reserve() */
GNUNET_PQ_make_prepare ("lookup_reserve_tips",
"SELECT"
" justification"
",tip_id"
",amount_val"
",amount_frac"
" FROM merchant_tips"
" WHERE reserve_serial ="
" (SELECT reserve_serial"
" FROM merchant_tip_reserves"
" WHERE reserve_pub=$2"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1))"),
/* for postgres_delete_reserve() */
GNUNET_PQ_make_prepare ("delete_reserve",
"DELETE"
" FROM merchant_tip_reserve_keys"
" WHERE reserve_serial="
" (SELECT reserve_serial"
" FROM merchant_tip_reserves"
" WHERE reserve_pub=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1))"),
/* for postgres_purge_reserve() */
GNUNET_PQ_make_prepare ("purge_reserve",
"DELETE"
" FROM merchant_tip_reserves"
" WHERE reserve_pub=$2"
" AND merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* For postgres_authorize_tip() */
GNUNET_PQ_make_prepare ("lookup_reserve_for_tip",
"SELECT"
" reserve_pub"
",expiration"
",exchange_initial_balance_val"
",exchange_initial_balance_frac"
",tips_committed_val"
",tips_committed_frac"
" FROM merchant_tip_reserves"
" WHERE"
" merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* For postgres_authorize_tip() */
GNUNET_PQ_make_prepare ("lookup_reserve_status",
"SELECT"
" expiration"
",exchange_initial_balance_val"
",exchange_initial_balance_frac"
",tips_committed_val"
",tips_committed_frac"
" FROM merchant_tip_reserves"
" WHERE reserve_pub = $2"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* For postgres_authorize_tip() */
GNUNET_PQ_make_prepare ("update_reserve_tips_committed",
"UPDATE merchant_tip_reserves SET"
" tips_committed_val=$3"
",tips_committed_frac=$4"
" WHERE reserve_pub = $2"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* For postgres_authorize_tip() */
GNUNET_PQ_make_prepare ("insert_tip",
"INSERT INTO merchant_tips"
"(reserve_serial"
",tip_id"
",justification"
",next_url"
",expiration"
",amount_val"
",amount_frac"
") "
"SELECT"
" reserve_serial, $3, $4, $5, $6, $7, $8"
" FROM merchant_tip_reserves"
" WHERE reserve_pub=$2"
" AND merchant_serial = "
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* For postgres_lookup_pickup() */
GNUNET_PQ_make_prepare ("lookup_pickup",
"SELECT"
" exchange_url"
",reserve_priv"
",pickup_serial"
" FROM merchant_tip_pickups"
" JOIN merchant_tips USING (tip_serial)"
" JOIN merchant_tip_reserves USING (reserve_serial)"
" JOIN merchant_tip_reserve_keys USING (reserve_serial)"
" WHERE pickup_id = $3"
" AND tip_id = $2"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* For postgres_lookup_pickup() */
GNUNET_PQ_make_prepare ("lookup_pickup_signatures",
"SELECT"
" coin_offset"
",blind_sig"
" FROM merchant_tip_pickup_signatures"
" WHERE pickup_serial = $1"),
/* For postgres_lookup_tip() */
GNUNET_PQ_make_prepare ("lookup_tip",
"SELECT"
" amount_val"
",amount_frac"
",picked_up_val"
",picked_up_frac"
",merchant_tips.expiration"
",exchange_url"
",reserve_priv"
" FROM merchant_tips"
" JOIN merchant_tip_reserves USING (reserve_serial)"
" JOIN merchant_tip_reserve_keys USING (reserve_serial)"
" WHERE tip_id = $2"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* For postgres_lookup_tip() */
GNUNET_PQ_make_prepare ("lookup_tips_inc",
"SELECT"
" tip_serial"
",tip_id"
",amount_val"
",amount_frac"
",CAST($4 as BIGINT)" /* otherwise $4 is unused and Postgres unhappy */
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
" FROM merchant_tips"
" JOIN merchant_tip_reserves USING (reserve_serial)"
" WHERE merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" tip_serial > $3"
" ORDER BY tip_serial ASC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_tips_dec",
"SELECT"
" tip_serial"
",tip_id"
",amount_val"
",amount_frac"
",CAST($4 as BIGINT)" /* otherwise $4 is unused and Postgres unhappy */
",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */
" FROM merchant_tips"
" JOIN merchant_tip_reserves USING (reserve_serial)"
" WHERE merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" tip_serial < $3"
" ORDER BY tip_serial DESC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_tips_inc_expired",
"SELECT"
" tip_serial"
",tip_id"
",amount_val"
",amount_frac"
" FROM merchant_tips"
" JOIN merchant_tip_reserves USING (reserve_serial)"
" WHERE merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" tip_serial > $3"
" AND"
" CAST($5 as BOOL) = (merchant_tips.expiration < $4)"
" ORDER BY tip_serial ASC"
" LIMIT $2"),
GNUNET_PQ_make_prepare ("lookup_tips_dec_expired",
"SELECT"
" tip_serial"
",tip_id"
",amount_val"
",amount_frac"
" FROM merchant_tips"
" JOIN merchant_tip_reserves USING (reserve_serial)"
" WHERE merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND"
" tip_serial < $3"
" AND"
" CAST($5 as BOOL) = (merchant_tips.expiration < $4)"
" ORDER BY tip_serial DESC"
" LIMIT $2"),
/* for postgres_lookup_tip_details() */
GNUNET_PQ_make_prepare ("lookup_tip_details",
"SELECT"
" tip_serial"
",amount_val"
",amount_frac"
",picked_up_val"
",picked_up_frac"
",justification"
",merchant_tips.expiration"
",reserve_pub"
" FROM merchant_tips"
" JOIN merchant_tip_reserves USING (reserve_serial)"
" WHERE tip_id = $2"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* for postgres_lookup_tip_details() */
GNUNET_PQ_make_prepare ("lookup_pickup_details",
"SELECT"
" pickup_id"
",amount_val"
",amount_frac"
",COUNT(blind_sig) AS num_planchets"
" FROM merchant_tip_pickups"
" JOIN merchant_tip_pickup_signatures USING (pickup_serial)"
" WHERE tip_serial = $1"
" GROUP BY pickup_serial"),
/* for postgres_insert_pickup() */
GNUNET_PQ_make_prepare ("insert_pickup",
"INSERT INTO merchant_tip_pickups"
"(tip_serial"
",pickup_id"
",amount_val"
",amount_frac"
") "
"SELECT"
" tip_serial, $3, $4, $5"
" FROM merchant_tips"
" JOIN merchant_tip_reserves USING (reserve_serial)"
" WHERE tip_id=$2"
" AND merchant_serial = "
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* for postgres_insert_pickup() */
GNUNET_PQ_make_prepare ("update_picked_up_tip",
"UPDATE merchant_tips SET"
" picked_up_val=$2"
",picked_up_frac=$3"
",was_picked_up = ($2 = amount_val AND $3 = amount_frac)"
" WHERE tip_id = $1"),
/* for postgres_insert_pickup() */
GNUNET_PQ_make_prepare ("lookup_picked_up_reserve",
"SELECT"
" reserve_serial"
",tips_picked_up_val"
",tips_picked_up_frac"
" FROM merchant_tip_reserves"
" JOIN merchant_tips USING (reserve_serial)"
" WHERE tip_id=$2"
" AND merchant_serial ="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"),
/* for postgres_insert_pickup() */
GNUNET_PQ_make_prepare ("update_picked_up_reserve",
"UPDATE merchant_tip_reserves SET"
" tips_picked_up_val=$2"
",tips_picked_up_frac=$3"
" WHERE reserve_serial = $1"),
/* for postgres_insert_pickup_blind_signature() */
GNUNET_PQ_make_prepare ("insert_pickup_blind_signature",
"INSERT INTO merchant_tip_pickup_signatures"
"(pickup_serial"
",coin_offset"
",blind_sig"
") "
"SELECT"
" pickup_serial, $2, $3"
" FROM merchant_tip_pickups"
" WHERE pickup_id=$1"),
/* for postgres_lookup_templates() */
GNUNET_PQ_make_prepare ("lookup_templates",
"SELECT"
" template_id"
",template_description"
" FROM merchant_template"
" JOIN merchant_instances"
" USING (merchant_serial)"
" WHERE merchant_instances.merchant_id=$1"),
/* for postgres_lookup_template() */
GNUNET_PQ_make_prepare ("lookup_template",
"SELECT"
" template_description"
",image"
",template_contract"
" FROM merchant_template"
" JOIN merchant_instances"
" USING (merchant_serial)"
" WHERE merchant_instances.merchant_id=$1"
" AND merchant_template.template_id=$2"),
/* for postgres_delete_template() */
GNUNET_PQ_make_prepare ("delete_template",
"DELETE"
" FROM merchant_template"
" WHERE merchant_template.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND merchant_template.template_id=$2"),
/* for postgres_insert_template() */
GNUNET_PQ_make_prepare ("insert_template",
"INSERT INTO merchant_template"
"(merchant_serial"
",template_id"
",template_description"
",image"
",template_contract"
")"
" SELECT merchant_serial,"
" $2, $3, $4, $5"
" FROM merchant_instances"
" WHERE merchant_id=$1"),
/* for postgres_update_template() */
GNUNET_PQ_make_prepare ("update_template",
"UPDATE merchant_template SET"
" template_description=$3"
",image=$4"
",template_contract=$5"
" WHERE merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND template_id=$2"),
/* for postgres_lookup_webhooks() */
GNUNET_PQ_make_prepare ("lookup_webhooks",
"SELECT"
" webhook_id"
",event_type"
" FROM merchant_webhook"
" JOIN merchant_instances"
" USING (merchant_serial)"
" WHERE merchant_instances.merchant_id=$1"),
/* for postgres_lookup_webhook() */
GNUNET_PQ_make_prepare ("lookup_webhook",
"SELECT"
" event_type"
",url"
",http_method"
",header_template"
",body_template"
" FROM merchant_webhook"
" JOIN merchant_instances"
" USING (merchant_serial)"
" WHERE merchant_instances.merchant_id=$1"
" AND merchant_webhook.webhook_id=$2"),
/* for postgres_delete_webhook() */
GNUNET_PQ_make_prepare ("delete_webhook",
"DELETE"
" FROM merchant_webhook"
" WHERE merchant_webhook.merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND merchant_webhook.webhook_id=$2"),
/* for postgres_insert_webhook() */
GNUNET_PQ_make_prepare ("insert_webhook",
"INSERT INTO merchant_webhook"
"(merchant_serial"
",webhook_id"
",event_type"
",url"
",http_method"
",header_template"
",body_template"
")"
" SELECT merchant_serial,"
" $2, $3, $4, $5, $6, $7"
" FROM merchant_instances"
" WHERE merchant_id=$1"),
/* for postgres_update_webhook() */
GNUNET_PQ_make_prepare ("update_webhook",
"UPDATE merchant_webhook SET"
" event_type=$3"
",url=$4"
",http_method=$5"
",header_template=$6"
",body_template=$7"
" WHERE merchant_serial="
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND webhook_id=$2"),
/* for postgres_lookup_webhook_by_event() */
GNUNET_PQ_make_prepare ("lookup_webhook_by_event",
"SELECT"
" webhook_serial"
",event_type"
",url"
",http_method"
",header_template"
",body_template"
" FROM merchant_webhook"
" JOIN merchant_instances"
" USING (merchant_serial)"
" WHERE merchant_instances.merchant_id=$1"
" AND event_type=$2"),
/* for postgres_delete_pending_webhook() */
GNUNET_PQ_make_prepare ("delete_pending_webhook",
"DELETE"
" FROM merchant_pending_webhooks"
" WHERE merchant_pending_webhooks.webhook_serial="
" (SELECT webhook_serial "
" FROM merchant_webhook"
" WHERE webhook_serial=$1)"),
/* for postgres_insert_pending_webhook() */
GNUNET_PQ_make_prepare ("insert_pending_webhook",
"INSERT INTO merchant_pending_webhooks"
"(merchant_serial"
",webhook_serial"
",url"
",http_method"
",header"
",body"
")"
" SELECT mi.merchant_serial,"
" $2, $3, $4, $5, $6"
" FROM merchant_instances mi"
" WHERE mi.merchant_id=$1"),
/* for postgres_update_pending_webhook() */
GNUNET_PQ_make_prepare ("update_pending_webhook",
"UPDATE merchant_pending_webhooks SET"
" retries=retries+1"
",next_attempt=$2"
" WHERE webhook_serial="
" (SELECT webhook_serial"
" FROM merchant_webhook"
" WHERE webhook_serial=$1)"),
/* for postgres_lookup_pending_webhook() */
GNUNET_PQ_make_prepare ("lookup_pending_webhook",
"SELECT"
" webhook_serial"
",next_attempt"
",retries"
",url"
",http_method"
",header"
",body"
" FROM merchant_pending_webhooks"
" WHERE next_attempt <= $1"
" ORDER BY next_attempt ASC"
),
/* for postgres_lookup_future_webhook() */
GNUNET_PQ_make_prepare ("lookup_future_webhook",
"SELECT"
" webhook_serial"
",next_attempt"
",retries"
",url"
",http_method"
",header"
",body"
" FROM merchant_pending_webhooks"
" ORDER BY next_attempt ASC LIMIT 1"
),
/* for postgres_lookup_all_webhooks() */
GNUNET_PQ_make_prepare ("lookup_all_webhooks",
" SELECT"
" webhook_serial"
",next_attempt"
",retries"
",url"
",http_method"
",header"
",body"
" FROM merchant_pending_webhooks"
" JOIN merchant_instances"
" USING (merchant_serial)"
" WHERE merchant_instances.merchant_id=$1"
" AND webhook_serial > $2"
" ORDER BY webhook_serial"
" ASC LIMIT $3"),
GNUNET_PQ_PREPARED_STATEMENT_END
};
struct GNUNET_PQ_ExecuteStatement es[] = {
GNUNET_PQ_make_try_execute ("SET search_path TO merchant;"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
pg->conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
"merchantdb-postgres",
NULL,
es,
ps);
if (NULL == pg->conn)
return GNUNET_SYSERR;
return GNUNET_OK;
};
/**
* Initialize Postgres database subsystem.
*
* @param cls a configuration instance
* @return NULL on error, otherwise a `struct TALER_MERCHANTDB_Plugin`
*/
void *
libtaler_plugin_merchantdb_postgres_init (void *cls)
{
const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
struct PostgresClosure *pg;
struct TALER_MERCHANTDB_Plugin *plugin;
pg = GNUNET_new (struct PostgresClosure);
pg->cfg = cfg;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_filename (cfg,
"merchantdb-postgres",
"SQL_DIR",
&pg->sql_dir))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"merchantdb-postgres",
"SQL_DIR");
GNUNET_free (pg);
return NULL;
}
if (GNUNET_OK !=
TALER_config_get_currency (cfg,
&pg->currency))
{
GNUNET_free (pg->sql_dir);
GNUNET_free (pg);
return NULL;
}
plugin = GNUNET_new (struct TALER_MERCHANTDB_Plugin);
plugin->cls = pg;
plugin->connect = &postgres_connect;
plugin->create_tables = &postgres_create_tables;
plugin->drop_tables = &postgres_drop_tables;
plugin->event_listen = &postgres_event_listen;
plugin->event_listen_cancel = &postgres_event_listen_cancel;
plugin->event_notify = &postgres_event_notify;
plugin->preflight = &postgres_preflight;
plugin->start = &postgres_start;
plugin->start_read_committed = &postgres_start_read_committed;
plugin->rollback = &postgres_rollback;
plugin->commit = &postgres_commit;
plugin->lookup_instances = &postgres_lookup_instances;
plugin->lookup_instance = &postgres_lookup_instance;
plugin->lookup_instance_auth = &postgres_lookup_instance_auth;
plugin->insert_instance = &postgres_insert_instance;
plugin->insert_account = &postgres_insert_account;
plugin->account_kyc_set_status
= &postgres_account_kyc_set_status;
plugin->account_kyc_get_status
= &postgres_account_kyc_get_status;
plugin->delete_instance_private_key = &postgres_delete_instance_private_key;
plugin->purge_instance = &postgres_purge_instance;
plugin->update_instance = &postgres_update_instance;
plugin->update_instance_auth = &postgres_update_instance_auth;
plugin->activate_account = &postgres_activate_account;
plugin->inactivate_account = &postgres_inactivate_account;
plugin->lookup_products = &postgres_lookup_products;
plugin->lookup_product = &postgres_lookup_product;
plugin->delete_product = &postgres_delete_product;
plugin->insert_product = &postgres_insert_product;
plugin->update_product = &postgres_update_product;
plugin->lock_product = &postgres_lock_product;
plugin->expire_locks = &postgres_expire_locks;
plugin->delete_order = &postgres_delete_order;
plugin->lookup_order = &postgres_lookup_order;
plugin->lookup_order_summary = &postgres_lookup_order_summary;
plugin->lookup_orders = &postgres_lookup_orders;
plugin->insert_order = &postgres_insert_order;
plugin->unlock_inventory = &postgres_unlock_inventory;
plugin->insert_order_lock = &postgres_insert_order_lock;
plugin->lookup_contract_terms = &postgres_lookup_contract_terms;
plugin->insert_contract_terms = &postgres_insert_contract_terms;
plugin->update_contract_terms = &postgres_update_contract_terms;
plugin->delete_contract_terms = &postgres_delete_contract_terms;
plugin->lookup_deposits = &postgres_lookup_deposits;
plugin->insert_exchange_signkey = &postgres_insert_exchange_signkey;
plugin->insert_deposit = &postgres_insert_deposit;
plugin->lookup_refunds = &postgres_lookup_refunds;
plugin->mark_contract_paid = &postgres_mark_contract_paid;
plugin->refund_coin = &postgres_refund_coin;
plugin->lookup_order_status = &postgres_lookup_order_status;
plugin->lookup_order_status_by_serial =
&postgres_lookup_order_status_by_serial;
plugin->lookup_payment_status = &postgres_lookup_payment_status;
plugin->lookup_deposits_by_order = &postgres_lookup_deposits_by_order;
plugin->lookup_transfer_details_by_order =
&postgres_lookup_transfer_details_by_order;
plugin->insert_deposit_to_transfer = &postgres_insert_deposit_to_transfer;
plugin->mark_order_wired = &postgres_mark_order_wired;
plugin->increase_refund = &postgres_increase_refund;
plugin->lookup_refunds_detailed = &postgres_lookup_refunds_detailed;
plugin->insert_refund_proof = &postgres_insert_refund_proof;
plugin->lookup_refund_proof = &postgres_lookup_refund_proof;
plugin->lookup_order_by_fulfillment = &postgres_lookup_order_by_fulfillment;
plugin->insert_transfer = &postgres_insert_transfer;
plugin->delete_transfer = &postgres_delete_transfer;
plugin->check_transfer_exists = &postgres_check_transfer_exists;
plugin->lookup_account = &postgres_lookup_account;
plugin->insert_transfer_details = &postgres_insert_transfer_details;
plugin->lookup_wire_fee = &postgres_lookup_wire_fee;
plugin->lookup_deposits_by_contract_and_coin =
&postgres_lookup_deposits_by_contract_and_coin;
plugin->lookup_transfer = &postgres_lookup_transfer;
plugin->set_transfer_status_to_verified =
&postgres_set_transfer_status_to_verified;
plugin->lookup_transfer_summary = &postgres_lookup_transfer_summary;
plugin->lookup_transfer_details = &postgres_lookup_transfer_details;
plugin->lookup_transfers = &postgres_lookup_transfers;
plugin->store_wire_fee_by_exchange = &postgres_store_wire_fee_by_exchange;
plugin->insert_reserve = &postgres_insert_reserve;
plugin->activate_reserve = &postgres_activate_reserve;
plugin->lookup_reserves = &postgres_lookup_reserves;
plugin->lookup_pending_reserves = &postgres_lookup_pending_reserves;
plugin->lookup_reserve = &postgres_lookup_reserve;
plugin->delete_reserve = &postgres_delete_reserve;
plugin->purge_reserve = &postgres_purge_reserve;
plugin->authorize_tip = &postgres_authorize_tip;
plugin->lookup_pickup = &postgres_lookup_pickup;
plugin->lookup_tip = &postgres_lookup_tip;
plugin->lookup_tips = &postgres_lookup_tips;
plugin->lookup_tip_details = &postgres_lookup_tip_details;
plugin->insert_pickup = &postgres_insert_pickup;
plugin->insert_pickup_blind_signature =
&postgres_insert_pickup_blind_signature;
plugin->lookup_templates = &postgres_lookup_templates;
plugin->lookup_template = &postgres_lookup_template;
plugin->delete_template = &postgres_delete_template;
plugin->insert_template = &postgres_insert_template;
plugin->update_template = &postgres_update_template;
plugin->lookup_webhooks = &postgres_lookup_webhooks;
plugin->lookup_webhook = &postgres_lookup_webhook;
plugin->delete_webhook = &postgres_delete_webhook;
plugin->insert_webhook = &postgres_insert_webhook;
plugin->update_webhook = &postgres_update_webhook;
plugin->lookup_webhook_by_event = &postgres_lookup_webhook_by_event;
plugin->lookup_all_webhooks = &postgres_lookup_all_webhooks;
plugin->lookup_future_webhook = &postgres_lookup_future_webhook;
plugin->lookup_pending_webhook = &postgres_lookup_pending_webhook;
plugin->delete_pending_webhook = &postgres_delete_pending_webhook;
plugin->insert_pending_webhook = &postgres_insert_pending_webhook;
plugin->update_pending_webhook = &postgres_update_pending_webhook;
return plugin;
}
/**
* Shutdown Postgres database subsystem.
*
* @param cls a `struct TALER_MERCHANTDB_Plugin`
* @return NULL (always)
*/
void *
libtaler_plugin_merchantdb_postgres_done (void *cls)
{
struct TALER_MERCHANTDB_Plugin *plugin = cls;
struct PostgresClosure *pg = plugin->cls;
if (NULL != pg->conn)
{
GNUNET_PQ_disconnect (pg->conn);
pg->conn = NULL;
}
GNUNET_free (pg->sql_dir);
GNUNET_free (pg->currency);
GNUNET_free (pg);
GNUNET_free (plugin);
return NULL;
}
/* end of plugin_merchantdb_postgres.c */