/* This file is part of TALER (C) 2014--2020 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 */ #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 int postgres_drop_tables (void *cls) { struct PostgresClosure *pc = cls; struct GNUNET_PQ_Context *conn; conn = GNUNET_PQ_connect_with_cfg (pc->cfg, "merchantdb-postgres", "drop", NULL, NULL); if (NULL == conn) return GNUNET_SYSERR; GNUNET_PQ_disconnect (conn); return GNUNET_OK; } /** * Initialize tables. * * @param cls the `struct PostgresClosure` with the plugin-specific state * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ static int postgres_create_tables (void *cls) { struct PostgresClosure *pc = cls; struct GNUNET_PQ_Context *conn; conn = GNUNET_PQ_connect_with_cfg (pc->cfg, "merchantdb-postgres", "merchant-", NULL, NULL); if (NULL == conn) return GNUNET_SYSERR; GNUNET_PQ_disconnect (conn); return GNUNET_OK; } /** * 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 int 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 int 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_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 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), 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; uint8_t active = account_details->active; 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_auto_from_type (&active), GNUNET_PQ_query_param_end }; check_connection (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_account", 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), 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 GNUNET_HashCode *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 GNUNET_HashCode *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_absolute_time ("next_restock", &pd->next_restock), 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_absolute_time (&pd->next_restock), 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_absolute_time (&pd->next_restock), 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_Absolute 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_absolute_time (&expiration_time), GNUNET_PQ_query_param_end }; check_connection (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "lock_product", params); } /** * 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 * @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) { 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_end }; check_connection (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "delete_order", params); } /** * 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 GNUNET_HashCode *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_Absolute *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_absolute_time ("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_Absolute 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_absolute_time ("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_absolute_time (&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 GNUNET_HashCode *h_post_data, struct GNUNET_TIME_Absolute pay_deadline, const struct TALER_ClaimTokenP *claim_token, const json_t *contract_terms) { struct PostgresClosure *pg = cls; struct GNUNET_TIME_Absolute now; 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 (&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_absolute_time (&now), TALER_PQ_query_param_json (contract_terms), GNUNET_PQ_query_param_end }; now = GNUNET_TIME_absolute_get (); (void) GNUNET_TIME_round_abs (&now); 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 * @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) { 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[] = { /* 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_end }; check_connection (pg); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "lookup_contract_terms", params, (NULL != contract_terms) ? rs : &rs[1]); } /** * 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 * @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) { struct PostgresClosure *pg = cls; struct GNUNET_TIME_Absolute pay_deadline; struct GNUNET_TIME_Absolute refund_deadline; const char *fulfillment_url; struct GNUNET_HashCode 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[] = { TALER_JSON_spec_absolute_time ("pay_deadline", &pay_deadline), TALER_JSON_spec_absolute_time ("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_absolute_time (&pay_deadline), GNUNET_PQ_query_param_absolute_time (&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, "insert_contract_terms", params); } } /** * 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_Absolute pay_deadline; struct GNUNET_TIME_Absolute refund_deadline; const char *fulfillment_url = NULL; struct GNUNET_HashCode 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[] = { TALER_JSON_spec_absolute_time ("pay_deadline", &pay_deadline), TALER_JSON_spec_absolute_time ("refund_deadline", &refund_deadline), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("fulfillment_url", &fulfillment_url)), 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_absolute_time (&pay_deadline), GNUNET_PQ_query_param_absolute_time (&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 GNUNET_HashCode *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)); 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_Absolute start_date, struct GNUNET_TIME_Absolute expire_date, struct GNUNET_TIME_Absolute 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_absolute_time (&start_date), GNUNET_PQ_query_param_absolute_time (&expire_date), GNUNET_PQ_query_param_absolute_time (&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_Absolute deposit_timestamp, const struct GNUNET_HashCode *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 GNUNET_HashCode *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_absolute_time (&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), 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 GNUNET_HashCode *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), 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 GNUNET_HashCode *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), 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 GNUNET_HashCode *h_contract_terms, struct GNUNET_TIME_Absolute 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_absolute_time (&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 GNUNET_HashCode *h_contract_terms, bool *paid) { struct PostgresClosure *pg = cls; uint8_t paid8; enum GNUNET_DB_QueryStatus qs; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", h_contract_terms), GNUNET_PQ_result_spec_auto_from_type ("paid", &paid8), GNUNET_PQ_result_spec_end }; check_connection (pg); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "lookup_order_status", params, rs); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) *paid = (0 != paid8); else *paid = false; /* just to be safe(r) */ return qs; } /** * 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_absolute_time (&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_Absolute now; now = GNUNET_TIME_absolute_get (); (void) GNUNET_TIME_round_abs (&now); GNUNET_assert (GNUNET_OK == TALER_amount_get_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_get_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_absolute_time (&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 GNUNET_HashCode *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), 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_absolute_time (&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) { /* FIXME: distinguish hard error */ 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_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_transfer_to_coin_mapping", params); if (0 > qs) { /* FIXME: distinguish hard error */ 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; } } /* 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) { /* FIXME: distinguish hard error */ 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] wire_fee wire fee charged * @param[out] closing_fee closing fee charged (irrelevant for us, * but needed to check signature) * @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_Absolute contract_date, struct TALER_Amount *wire_fee, struct TALER_Amount *closing_fee, struct GNUNET_TIME_Absolute *start_date, struct GNUNET_TIME_Absolute *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_absolute_time (&contract_date), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", wire_fee), TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee", closing_fee), GNUNET_PQ_result_spec_absolute_time ("start_date", start_date), GNUNET_PQ_result_spec_absolute_time ("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 GNUNET_HashCode *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_Absolute *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_absolute_time ("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_ABS; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "lookup_transfer", params, rs); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "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_Absolute before, struct GNUNET_TIME_Absolute 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 = ( (before.abs_value_us != GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us) || (after.abs_value_us != GNUNET_TIME_UNIT_ZERO_ABS.abs_value_us) ); 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_absolute_time (&before), GNUNET_PQ_query_param_absolute_time (&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_absolute_time (&before), GNUNET_PQ_query_param_absolute_time (&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 wire_fee wire fee charged * @param closing_fee closing fee charged (irrelevant for us, * but needed to check signature) * @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_Amount *wire_fee, const struct TALER_Amount *closing_fee, struct GNUNET_TIME_Absolute start_date, struct GNUNET_TIME_Absolute 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 (wire_fee), TALER_PQ_query_param_amount (closing_fee), GNUNET_PQ_query_param_absolute_time (&start_date), GNUNET_PQ_query_param_absolute_time (&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_STRINGS_absolute_time_to_string (start_date), TALER_amount2s (wire_fee)); 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_Absolute 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_Absolute 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_absolute_time (&now), GNUNET_PQ_query_param_absolute_time (&expiration), TALER_PQ_query_param_amount (initial_balance), GNUNET_PQ_query_param_end }; now = GNUNET_TIME_absolute_get (); (void) GNUNET_TIME_round_abs (&now); 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_Absolute creation_time; struct GNUNET_TIME_Absolute 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_absolute_time ("creation_time", &creation_time), GNUNET_PQ_result_spec_absolute_time ("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_Absolute 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_absolute_time (&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_Absolute creation_time; struct GNUNET_TIME_Absolute 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_absolute_time ("creation_time", &creation_time), GNUNET_PQ_result_spec_absolute_time ("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_Absolute 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_Absolute 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_absolute_time ("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 ( (lac->expiration.abs_value_us != GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us) && ( (expiration.abs_value_us > lac->expiration.abs_value_us) && (GNUNET_TIME_absolute_get_remaining (lac->expiration).rel_value_us > MIN_EXPIRATION.rel_value_us) ) ) { /* 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 GNUNET_HashCode *tip_id, struct GNUNET_TIME_Absolute *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_ABS }; 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, "enable tip reserve")) { 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) 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_absolute_time ("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_absolute_time (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 GNUNET_CRYPTO_RsaSignature **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 GNUNET_CRYPTO_RsaSignature *bsig; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_uint32 ("coin_offset", &offset), GNUNET_PQ_result_spec_rsa_signature ("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. */ GNUNET_assert (NULL == lsc->sigs[offset]); 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 GNUNET_HashCode *tip_id, const struct GNUNET_HashCode *pickup_id, char **exchange_url, struct TALER_ReservePrivateKeyP *reserve_priv, unsigned int sigs_length, struct GNUNET_CRYPTO_RsaSignature *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 GNUNET_HashCode *tip_id, struct TALER_Amount *total_authorized, struct TALER_Amount *total_picked_up, struct GNUNET_TIME_Absolute *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_absolute_time ("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 GNUNET_HashCode 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 GNUNET_HashCode *tip_id, bool fpu, struct TALER_Amount *total_authorized, struct TALER_Amount *total_picked_up, char **justification, struct GNUNET_TIME_Absolute *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_absolute_time ("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 GNUNET_HashCode *tip_id, const struct TALER_Amount *total_picked_up, const struct GNUNET_HashCode *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 GNUNET_HashCode *pickup_id, uint32_t offset, const struct GNUNET_CRYPTO_RsaSignature *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), GNUNET_PQ_query_param_rsa_signature (blind_sig), GNUNET_PQ_query_param_end }; check_connection (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_pickup_blind_signature", 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", 0), /* 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", 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", 1), /* for postgres_lookup_instances() */ GNUNET_PQ_make_prepare ("lookup_instance_auth", "SELECT" " auth_hash" ",auth_salt" " FROM merchant_instances" " WHERE merchant_id=$1", 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" " FROM merchant_instances", 0), /* 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)" "VALUES" "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)", 14), /* 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", 2), /* 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", 5), /* 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", 1), /* for postgres_purge_instance() */ GNUNET_PQ_make_prepare ("purge_instance", "DELETE FROM merchant_instances" " WHERE merchant_instances.merchant_id = $1", 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" " WHERE merchant_id = $1", 11), /* 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", 3), /* 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)", 2), /* 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)", 2), /* 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", 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" " FROM merchant_inventory" " JOIN merchant_instances" " USING (merchant_serial)" " WHERE merchant_instances.merchant_id=$1" " AND merchant_inventory.product_id=$2", 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)", 2), /* 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)" " SELECT merchant_serial," " $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12" " FROM merchant_instances" " WHERE merchant_id=$1", 12), /* 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" " 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", 13), /* 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)", 5), /* for postgres_delete_order() */ GNUNET_PQ_make_prepare ("delete_order", "DELETE" " FROM merchant_orders" " WHERE merchant_orders.merchant_serial=" " (SELECT merchant_serial " " FROM merchant_instances" " WHERE merchant_id=$1)" " AND merchant_orders.order_id=$2" " AND" " ((NOT EXISTS" " (SELECT order_id" " FROM merchant_contract_terms" " WHERE merchant_contract_terms.order_id=$2))" " OR pay_deadline < $3)", 3), /* 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", 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)", 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", 7), 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", 7), 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", 7), 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", 7), 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", 7), 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", 7), 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", 7), 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", 7), 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", 7), 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", 7), 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", 7), 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", 7), 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", 7), 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", 7), 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", 7), 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", 7), /* 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", 7), /* for postgres_unlock_inventory() */ GNUNET_PQ_make_prepare ("unlock_inventory", "DELETE" " FROM merchant_inventory_locks" " WHERE lock_uuid=$1", 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)", 4), /* for postgres_lookup_contract_terms() */ GNUNET_PQ_make_prepare ("lookup_contract_terms", "SELECT" " contract_terms" ",order_serial" " FROM merchant_contract_terms" " WHERE order_id=$2" " AND merchant_serial=" " (SELECT merchant_serial" " FROM merchant_instances" " WHERE merchant_id=$1)", 2), /* 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)" "SELECT" " order_serial" ",merchant_serial" ",order_id" ",$3" /* contract_terms */ ",$4" /* h_contract_terms */ ",creation_time" ",$5" /* pay_deadline */ ",$6" /* refund_deadline */ ",$7" /* fulfillment_url */ "FROM merchant_orders" " WHERE order_id=$2" " AND merchant_serial=" " (SELECT merchant_serial" " FROM merchant_instances" " WHERE merchant_id=$1)", 7), /* 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)", 7), /* 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) )", 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))", 2), /* 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)", 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", 16), /* 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))", 2), /* 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)", 3), /* 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", 2), /* 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)", 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))", 5), /* for postgres_lookup_order_status() */ GNUNET_PQ_make_prepare ("lookup_order_status", "SELECT" " h_contract_terms" ",paid" " FROM merchant_contract_terms" " WHERE merchant_contract_terms.merchant_serial=" " (SELECT merchant_serial " " FROM merchant_instances" " WHERE merchant_id=$1)" " AND order_id=$2", 2), /* for postgres_lookup_payment_status() */ GNUNET_PQ_make_prepare ("lookup_payment_status", "SELECT" " wired" ",paid" " FROM merchant_contract_terms" " WHERE order_serial=$1", 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", 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", 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)", 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", 7), /* for postgres_mark_order_wired() */ GNUNET_PQ_make_prepare ("mark_order_wired", "UPDATE merchant_contract_terms SET" " wired=true" " WHERE order_serial=$1", 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", 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)", 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))", 2), /* 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))", 2), /* 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", 5), /* 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", 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)", 3), /* 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)", 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))", 2), /* 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)", 2), /* 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)", 2), /* 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))", 4), /* 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", 8), /* 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", 8), /* 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)))", 1), /* 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", 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)", 3), /* 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;", 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", 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", 2), /* 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", 2), /* 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", 6), /* 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", 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", 4), /* 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", 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", 6), /* 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", 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", 4), /* 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", 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)", 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", 6), /* 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)", 4), /* 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)", 5), /* 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)", 2), /* 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", 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)", 2), /* 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))", 2), /* 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))", 2), /* 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)", 2), /* 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)", 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)", 2), /* 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)", 4), /* 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)", 8), /* 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)", 2), /* 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", 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)", 2), /* 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", 5), 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", 5), 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", 5), 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", 5), /* 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)", 2), /* 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", 1), /* 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)", 5), /* 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", 3), /* 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)", 2), /* 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", 3), /* 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", 3), GNUNET_PQ_PREPARED_STATEMENT_END }; pg->conn = GNUNET_PQ_connect_with_cfg (pg->cfg, "merchantdb-postgres", NULL, NULL, 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_PQ_disconnect (pg->conn); 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->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_auth = &postgres_lookup_instance_auth; plugin->insert_instance = &postgres_insert_instance; plugin->insert_account = &postgres_insert_account; 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->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_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; 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 */