/* 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 merchant/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 "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 amountp[out] 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 amountp[out] 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 amountp[out] 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; }; /* ********************* NEW API ************************** */ /** * Drop merchant tables * * @param cls closure our `struct Plugin` * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ static int postgres_drop_tables (void *cls) { struct PostgresClosure *pg = cls; char *load_path; GNUNET_asprintf (&load_path, "%s%s", pg->sql_dir, "drop"); GNUNET_PQ_run_sql (pg->conn, load_path); GNUNET_free (load_path); return GNUNET_OK; } /** * Do a pre-flight check that we are not in an uncommitted transaction. * If we are, try to commit the previous transaction and output a warning. * 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; struct GNUNET_PQ_ExecuteStatement es[] = { GNUNET_PQ_make_execute ("COMMIT"), GNUNET_PQ_EXECUTE_STATEMENT_END }; if (NULL == pg->transaction_name) return; /* all good */ if (GNUNET_OK == GNUNET_PQ_exec_statements (pg->conn, es)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "BUG: Preflight check committed transaction `%s'!\n", pg->transaction_name); } else { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "BUG: Preflight check failed to commit transaction `%s'!\n", pg->transaction_name); } pg->transaction_name = NULL; } /** * Check that the database connection is still up. * * @param pg connection to check */ static void check_connection (struct PostgresClosure *pg) { 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_DEBUG, "Starting merchant DB transaction\n"); 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 * @return #GNUNET_OK on success */ 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_DEBUG, "Rolling back merchant DB transaction\n"); 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_DEBUG, "Committing merchant DB transaction\n"); 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 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, 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_result 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_result 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++) { 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_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 }; 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) { /* find_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; } /** * 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 * @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) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (merchant_pub), 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); } /** * Set an instance's account in our database to "inactive". * * @param cls closure * @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 struct GNUNET_HashCode *h_wire) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { 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); } /** * 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; /** * 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 products. * * @param[in,out] cls of type `struct LookupProductsContext *` * @param result the postgres result * @param num_result 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->qs = GNUNET_DB_STATUS_HARD_ERROR; 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 }; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "lookup_products", params, &lookup_products_cb, &plc); if (0 != plc.qs) return plc.qs; 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 }; 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), TALER_PQ_result_spec_json ("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 }; 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, (NULL == pd) ? rs_null : 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), TALER_PQ_query_param_json (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_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), TALER_PQ_query_param_json (pd->image), /* $6 */ TALER_PQ_query_param_json (pd->taxes), TALER_PQ_query_param_amount (&pd->price), GNUNET_PQ_query_param_uint64 (&pd->total_stock), GNUNET_PQ_query_param_uint64 (&pd->total_lost), /* $11 */ 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, "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_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, uint32_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_uint32 (&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); } /* ********************* OLD API ************************** */ /** * Retrieve proposal data given its proposal data's hashcode * * @param cls closure * @param contract_terms where to store the retrieved proposal data * @param h_contract_terms proposal data's hashcode that will be used to * perform the lookup * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_find_contract_terms_from_hash ( void *cls, json_t **contract_terms, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_MerchantPublicKeyP *merchant_pub) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_result_spec_json ("contract_terms", contract_terms), GNUNET_PQ_result_spec_end }; check_connection (pg); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "find_contract_terms_from_hash", params, rs); } /** * Retrieve proposal data given its proposal data's hashcode * * @param cls closure * @param contract_terms where to store the retrieved proposal data * @param h_contract_terms proposal data's hashcode that will be used to * perform the lookup * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_find_paid_contract_terms_from_hash (void *cls, json_t **contract_terms, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_MerchantPublicKeyP * merchant_pub) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_result_spec_json ("contract_terms", contract_terms), GNUNET_PQ_result_spec_end }; /* no preflight check here, runs in its own transaction from caller (in /pay case) */ check_connection (pg); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "find_paid_contract_terms_from_hash", params, rs); } /** * Retrieve proposal data given its order id. Ignores if the * proposal has been paid or not. * * @param cls closure * @param[out] contract_terms where to store the retrieved contract terms * @param order id order id used to perform the lookup * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_find_contract_terms (void *cls, json_t **contract_terms, const char *order_id, const struct TALER_MerchantPublicKeyP *merchant_pub) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_result_spec_json ("contract_terms", contract_terms), GNUNET_PQ_result_spec_end }; *contract_terms = NULL; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Finding contract term, order_id: '%s', merchant_pub: '%s'.\n", order_id, TALER_B2S (merchant_pub)); check_connection (pg); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "find_contract_terms", params, rs); } /** * Retrieve order given its order id and the instance's merchant public key. * * @param cls closure * @param[out] contract_terms where to store the retrieved contract terms * @param order id order id used to perform the lookup * @param merchant_pub merchant public key that identifies the instance * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_find_order (void *cls, json_t **contract_terms, const char *order_id, const struct TALER_MerchantPublicKeyP *merchant_pub) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_result_spec_json ("contract_terms", contract_terms), GNUNET_PQ_result_spec_end }; *contract_terms = NULL; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Finding contract term, order_id: '%s', merchant_pub: '%s'.\n", order_id, TALER_B2S (merchant_pub)); check_connection (pg); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "find_order", params, rs); } /** * Insert proposal data and its hashcode into db * * @param cls closure * @param order_id identificator of the proposal being stored * @param merchant_pub merchant's public key * @param timestamp timestamp of this proposal data * @param contract_terms proposal data to store * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_insert_contract_terms (void *cls, const char *order_id, const struct TALER_MerchantPublicKeyP *merchant_pub, struct GNUNET_TIME_Absolute timestamp, const json_t *contract_terms) { struct PostgresClosure *pg = cls; struct GNUNET_HashCode h_contract_terms; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_absolute_time (×tamp), TALER_PQ_query_param_json (contract_terms), GNUNET_PQ_query_param_auto_from_type (&h_contract_terms), GNUNET_PQ_query_param_end }; if (GNUNET_OK != TALER_JSON_hash (contract_terms, &h_contract_terms)) { GNUNET_break (0); return GNUNET_SYSERR; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "inserting contract terms: order_id: %s, merchant_pub: %s, h_contract_terms: %s.\n", order_id, TALER_B2S (merchant_pub), GNUNET_h2s (&h_contract_terms)); check_connection (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_contract_terms", params); } /** * Insert order into the DB. * * @param cls closure * @param order_id identificator of the proposal being stored * @param merchant_pub merchant's public key * @param timestamp timestamp of this proposal data * @param contract_terms proposal data to store * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_insert_order (void *cls, const char *order_id, const struct TALER_MerchantPublicKeyP *merchant_pub, struct GNUNET_TIME_Absolute timestamp, const json_t *contract_terms) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_absolute_time (×tamp), TALER_PQ_query_param_json (contract_terms), GNUNET_PQ_query_param_end }; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "inserting order: order_id: %s, merchant_pub: %s.\n", order_id, TALER_B2S (merchant_pub)); check_connection (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_order", params); } /** * Mark contract terms as paid. Needed by /history as only paid * contracts must be shown. * * NOTE: we can't get the list of (paid) contracts from the * transactions table because it lacks contract_terms plain JSON. In * facts, the protocol doesn't allow to store contract_terms in * transactions table, as /pay handler doesn't receive this data (only * /proposal does). * * @param cls closure * @param h_contract_terms hash of the contract that is now paid * @param merchant_pub merchant's public key * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_mark_proposal_paid (void *cls, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_MerchantPublicKeyP *merchant_pub) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_end }; TALER_LOG_DEBUG ("Marking proposal paid, h_contract_terms: '%s'," " merchant_pub: '%s'\n", GNUNET_h2s (h_contract_terms), TALER_B2S (merchant_pub)); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "mark_proposal_paid", params); } /** * Store the order ID that was used to pay for a resource within a session. * * @param cls closure * @param session_id session id * @param fulfillment_url URL that canonically identifies the resource * being paid for * @param order_id the order ID that was used when paying for the resource URL * @param merchant_pub public key of the merchant, identifying the instance * @return transaction status */ enum GNUNET_DB_QueryStatus postgres_insert_session_info (void *cls, const char *session_id, const char *fulfillment_url, const char *order_id, const struct TALER_MerchantPublicKeyP *merchant_pub) { struct PostgresClosure *pg = cls; struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (session_id), GNUNET_PQ_query_param_string (fulfillment_url), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_end }; return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_session_info", params); } /** * Retrieve the order ID that was used to pay for a resource within a session. * * @param cls closure * @param[out] order_id where to store the order ID that was used when * paying for the resource URL * @param session_id session id * @param fulfillment_url URL that canonically identifies the resource * being paid for * @param merchant_pub public key of the merchant, identifying the instance * @return transaction status */ enum GNUNET_DB_QueryStatus postgres_find_session_info (void *cls, char **order_id, const char *session_id, const char *fulfillment_url, const struct TALER_MerchantPublicKeyP *merchant_pub) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (fulfillment_url), GNUNET_PQ_query_param_string (session_id), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_string ("order_id", order_id), GNUNET_PQ_result_spec_end }; // We don't clean up the result spec since we want // to keep around the memory for order_id. return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "find_session_info", params, rs); } /** * Insert payment confirmation from the exchange into the database. * * @param cls closure * @param order_id identificator of the proposal associated with this revenue * @param merchant_pub merchant's public key * @param coin_pub public key of the coin * @param amount_with_fee amount the exchange will deposit for this coin * @param deposit_fee fee the exchange will charge for this coin * @param refund_fee fee the exchange will charge for refunding this coin * @param wire_fee wire fee changed by the exchange * @param signkey_pub public key used by the exchange for @a exchange_proof * @param exchange_proof proof from exchange that coin was accepted * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_store_deposit (void *cls, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_MerchantPublicKeyP *merchant_pub, const struct TALER_CoinSpendPublicKeyP *coin_pub, const char *exchange_url, const struct TALER_Amount *amount_with_fee, const struct TALER_Amount *deposit_fee, const struct TALER_Amount *refund_fee, const struct TALER_Amount *wire_fee, const struct TALER_ExchangePublicKeyP *signkey_pub, const json_t *exchange_proof) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_string (exchange_url), TALER_PQ_query_param_amount (amount_with_fee), TALER_PQ_query_param_amount (deposit_fee), TALER_PQ_query_param_amount (refund_fee), TALER_PQ_query_param_amount (wire_fee), GNUNET_PQ_query_param_auto_from_type (signkey_pub), TALER_PQ_query_param_json (exchange_proof), GNUNET_PQ_query_param_end }; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Storing payment for h_contract_terms `%s', coin_pub: `%s', amount_with_fee: %s\n", GNUNET_h2s (h_contract_terms), TALER_B2S (coin_pub), TALER_amount2s (amount_with_fee)); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Merchant pub is `%s'\n", TALER_B2S (merchant_pub)); check_connection (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_deposit", params); } /** * Insert mapping of @a coin_pub and @a h_contract_terms to * corresponding @a wtid. * * @param cls closure * @param h_contract_terms hashcode of the proposal data paid by @a coin_pub * @param coin_pub public key of the coin * @param wtid identifier of the wire transfer in which the exchange * send us the money for the coin deposit * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_store_coin_to_transfer (void *cls, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_WireTransferIdentifierRawP *wtid) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_auto_from_type (wtid), GNUNET_PQ_query_param_end }; check_connection (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_transfer", params); } /** * Insert wire transfer confirmation from the exchange into the database. * * @param cls closure * @param exchange_url URL of the exchange * @param wtid identifier of the wire transfer * @param execution_time when was @a wtid executed * @param signkey_pub public key used by the exchange for @a exchange_proof * @param exchange_proof proof from exchange about what the deposit was for * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_store_transfer_to_proof (void *cls, const char *exchange_url, const struct TALER_WireTransferIdentifierRawP *wtid, struct GNUNET_TIME_Absolute execution_time, const struct TALER_ExchangePublicKeyP *signkey_pub, const json_t *exchange_proof) { 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_absolute_time (&execution_time), GNUNET_PQ_query_param_auto_from_type (signkey_pub), TALER_PQ_query_param_json (exchange_proof), GNUNET_PQ_query_param_end }; check_connection (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_proof", params); } /** * Lookup for a proposal, respecting the signature used by the * /history's db methods. * * @param cls db plugin handle * @param order_id order id used to search for the proposal data * @param merchant_pub public key of the merchant using this method * @param cb the callback * @param cb_cls closure to pass to the callback * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_find_contract_terms_history (void *cls, const char *order_id, const struct TALER_MerchantPublicKeyP *merchant_pub, TALER_MERCHANTDB_ProposalDataCallback cb, void *cb_cls) { struct PostgresClosure *pg = cls; json_t *contract_terms; enum GNUNET_DB_QueryStatus qs; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_result_spec_json ("contract_terms", &contract_terms), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "find_contract_terms_history", params, rs); if (qs <= 0) return qs; if (NULL != cb) cb (cb_cls, order_id, 0, contract_terms); GNUNET_PQ_cleanup_result (rs); return qs; } /** * Closure for #find_contracts_cb(). */ struct FindContractsContext { /** * Function to call on each result. */ TALER_MERCHANTDB_ProposalDataCallback cb; /** * Closure for @e cb. */ void *cb_cls; /** * Transaction status code to 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 cls of type `struct FindContractsContext *` * @param result the postgres result * @param num_result the number of results in @a result */ static void find_contracts_cb (void *cls, PGresult *result, unsigned int num_results) { struct FindContractsContext *fcctx = cls; for (unsigned int i = 0; i < num_results; i++) { char *order_id; json_t *contract_terms; uint64_t row_id; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_string ("order_id", &order_id), TALER_PQ_result_spec_json ("contract_terms", &contract_terms), GNUNET_PQ_result_spec_uint64 ("row_id", &row_id), GNUNET_PQ_result_spec_end }; if (GNUNET_OK != GNUNET_PQ_extract_result (result, rs, i)) { GNUNET_break (0); fcctx->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } fcctx->qs = i + 1; fcctx->cb (fcctx->cb_cls, order_id, row_id, contract_terms); GNUNET_PQ_cleanup_result (rs); } } /** * Return proposals whose timestamp are older than `date`. * Among those proposals, only those ones being between the * start-th and (start-nrows)-th record are returned. The rows * are sorted having the youngest first. * * @param cls our plugin handle. * @param date only results older than this date are returned. * @param merchant_pub instance's public key; only rows related to this * instance are returned. * @param start only rows with serial id less than start are returned. * In other words, you lower `start` to get older records. The tipical * usage is to firstly call `find_contract_terms_by_date`, so that you get * the `nrows` youngest records. The oldest of those records will tell you * from which timestamp and `start` you can query the DB in order to get * furtherly older records, and so on. Alternatively, you can use always * the same timestamp and just go behind in history by tuning `start`. * @param nrows only nrows rows are returned. * @param past if set to #GNUNET_YES, retrieves rows older than `date`. * @param ascending if #GNUNET_YES, results will be sorted in chronological order. * This is typically used to show live updates on the merchant's backoffice * Web interface. * @param cb function to call with transaction data, can be NULL. * @param cb_cls closure for @a cb * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_find_contract_terms_by_date_and_range (void *cls, struct GNUNET_TIME_Absolute date, const struct TALER_MerchantPublicKeyP * merchant_pub, uint64_t start, uint64_t nrows, int past, unsigned int ascending, TALER_MERCHANTDB_ProposalDataCallback cb, void *cb_cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_absolute_time (&date), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_uint64 (&start), GNUNET_PQ_query_param_uint64 (&nrows), GNUNET_PQ_query_param_end }; const char *stmt; enum GNUNET_DB_QueryStatus qs; struct FindContractsContext fcctx = { .cb = cb, .cb_cls = cb_cls }; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "DB serving /history with date %s\n", GNUNET_STRINGS_absolute_time_to_string (date)); stmt = (GNUNET_YES == past) ? ( (GNUNET_YES == ascending) ? "find_contract_terms_by_date_and_range_past_asc" : "find_contract_terms_by_date_and_range_past") : ( (GNUNET_YES == ascending) ? "find_contract_terms_by_date_and_range_asc" : "find_contract_terms_by_date_and_range"); check_connection (pg); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, stmt, params, &find_contracts_cb, &fcctx); if (0 >= qs) return qs; return fcctx.qs; } /** * Closure for #find_tip_authorizations_cb(). */ struct GetAuthorizedTipAmountContext { /** * Total authorized amount. */ struct TALER_Amount authorized_amount; /** * Transaction status code to set. */ enum GNUNET_DB_QueryStatus qs; /** * Plugin context. */ struct PostgresClosure *pg; }; /** * Function to be called with the results of a SELECT statement * that has returned @a num_results results. * * @param cls of type `struct GetAuthorizedTipAmountContext *` * @param result the postgres result * @param num_result the number of results in @a result */ static void find_tip_authorizations_cb (void *cls, PGresult *result, unsigned int num_results) { struct GetAuthorizedTipAmountContext *ctx = cls; struct PostgresClosure *pg = ctx->pg; unsigned int i; for (i = 0; i < num_results; i++) { struct TALER_Amount amount; char *just; json_t *extra; struct GNUNET_HashCode h; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_string ("justification", &just), GNUNET_PQ_result_spec_auto_from_type ("tip_id", &h), TALER_PQ_RESULT_SPEC_AMOUNT ("amount", &amount), TALER_PQ_result_spec_json ("extra", &extra), GNUNET_PQ_result_spec_end }; if (GNUNET_OK != GNUNET_PQ_extract_result (result, rs, i)) { GNUNET_break (0); ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } if (0 == i) { ctx->authorized_amount = amount; } else { if (0 > TALER_amount_add (&ctx->authorized_amount, &ctx->authorized_amount, &amount)) { GNUNET_break (0); GNUNET_PQ_cleanup_result (rs); ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } } GNUNET_PQ_cleanup_result (rs); } if (0 == i) { ctx->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } else { /* one aggregated result */ ctx->qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; } } /** * Get the total amount of authorized tips for a tipping reserve. * * @param cls closure, typically a connection to the db * @param reserve_priv which reserve to check * @param[out] authorzed_amount amount we've authorized so far for tips * @return transaction status, usually * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the reserve_priv * does not identify a known tipping reserve */ static enum GNUNET_DB_QueryStatus postgres_get_authorized_tip_amount (void *cls, const struct TALER_ReservePrivateKeyP *reserve_priv, struct TALER_Amount *authorized_amount) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (reserve_priv), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; struct GetAuthorizedTipAmountContext ctx = { .pg = pg }; check_connection (pg); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "find_tip_authorizations", params, &find_tip_authorizations_cb, &ctx); if (0 >= qs) return qs; *authorized_amount = ctx.authorized_amount; return ctx.qs; } /** * Return proposals whose timestamp are older than `date`. * The rows are sorted having the youngest first. * * @param cls our plugin handle. * @param date only results older than this date are returned. * @param merchant_pub instance's public key; only rows related to this * instance are returned. * @param nrows at most nrows rows are returned. * @param cb function to call with transaction data, can be NULL. * @param cb_cls closure for @a cb * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_find_contract_terms_by_date (void *cls, struct GNUNET_TIME_Absolute date, const struct TALER_MerchantPublicKeyP *merchant_pub, uint64_t nrows, TALER_MERCHANTDB_ProposalDataCallback cb, void *cb_cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_absolute_time (&date), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_uint64 (&nrows), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; struct FindContractsContext fcctx = { .cb = cb, .cb_cls = cb_cls }; check_connection (pg); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "find_contract_terms_by_date", params, &find_contracts_cb, &fcctx); if (0 >= qs) return qs; return fcctx.qs; } /** * Closure for #find_payments_cb(). */ struct FindPaymentsContext { /** * Function to call with results. */ TALER_MERCHANTDB_CoinDepositCallback cb; /** * Closure for @e cls. */ void *cb_cls; /** * Plugin context. */ struct PostgresClosure *pg; /** * Contract term hash used for the search. */ const struct GNUNET_HashCode *h_contract_terms; /** * 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 cls of type `struct FindPaymentsContext *` * @param result the postgres result * @param num_result the number of results in @a result */ static void find_payments_cb (void *cls, PGresult *result, unsigned int num_results) { struct FindPaymentsContext *fpc = cls; struct PostgresClosure *pg = fpc->pg; for (unsigned int i = 0; iqs = GNUNET_DB_STATUS_HARD_ERROR; return; } fpc->qs = i + 1; fpc->cb (fpc->cb_cls, fpc->h_contract_terms, &coin_pub, exchange_url, &amount_with_fee, &deposit_fee, &refund_fee, &wire_fee, exchange_proof); GNUNET_PQ_cleanup_result (rs); } } /** * Lookup information about coin payments by proposal data hash * (and @a merchant_pub) * * @param cls closure * @param h_contract_terms key for the search * @param merchant_pub merchant's public key * @param cb function to call with payment data * @param cb_cls closure for @a cb * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_find_payments (void *cls, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_MerchantPublicKeyP *merchant_pub, TALER_MERCHANTDB_CoinDepositCallback cb, void *cb_cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_end }; struct FindPaymentsContext fpc = { .h_contract_terms = h_contract_terms, .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 payment for h_contract_terms '%s'\n", GNUNET_h2s (h_contract_terms)); check_connection (pg); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "find_deposits", params, &find_payments_cb, &fpc); if (qs <= 0) return qs; return fpc.qs; } /** * Closure for #find_payments_by_coin_cb(). */ struct FindPaymentsByCoinContext { /** * Function to call with results. */ TALER_MERCHANTDB_CoinDepositCallback cb; /** * Closure for @e cls. */ void *cb_cls; /** * Plugin context. */ struct PostgresClosure *pg; /** * Coin we are looking for. */ const struct TALER_CoinSpendPublicKeyP *coin_pub; /** * Hash of the contract we are looking for. */ const struct GNUNET_HashCode *h_contract_terms; /** * 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 cls of type `struct FindPaymentsByCoinContext *` * @param result the postgres result * @param num_result the number of results in @a result */ static void find_payments_by_coin_cb (void *cls, PGresult *result, unsigned int num_results) { struct FindPaymentsByCoinContext *fpc = cls; struct PostgresClosure *pg = fpc->pg; for (unsigned int i = 0; iqs = GNUNET_DB_STATUS_HARD_ERROR; return; } fpc->qs = i + 1; fpc->cb (fpc->cb_cls, fpc->h_contract_terms, fpc->coin_pub, exchange_url, &amount_with_fee, &deposit_fee, &refund_fee, &wire_fee, exchange_proof); GNUNET_PQ_cleanup_result (rs); } } /** * Retrieve information about a deposited coin. * * @param cls closure * @param h_contract_terms hashcode of the proposal data paid by @a coin_pub * @param merchant_pub merchant's public key. * @param coin_pub coin's public key used 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_find_payments_by_hash_and_coin (void *cls, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_MerchantPublicKeyP *merchant_pub, 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_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_end }; struct FindPaymentsByCoinContext fpc = { .cb = cb, .cb_cls = cb_cls, .pg = pg, .h_contract_terms = h_contract_terms, .coin_pub = coin_pub }; enum GNUNET_DB_QueryStatus qs; check_connection (pg); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "find_deposits_by_hash_and_coin", params, &find_payments_by_coin_cb, &fpc); if (0 >= qs) return qs; return fpc.qs; } /** * Closure for #find_transfers_cb(). */ struct FindTransfersContext { /** * Function to call on results. */ TALER_MERCHANTDB_TransferCallback cb; /** * Closure for @e cb. */ void *cb_cls; /** * Hash of the contract we are looking under. */ const struct GNUNET_HashCode *h_contract_terms; /** * 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 cls of type `struct FindTransfersContext *` * @param result the postgres result * @param num_result the number of results in @a result */ static void find_transfers_cb (void *cls, PGresult *result, unsigned int num_results) { struct FindTransfersContext *ftc = cls; for (unsigned int i = 0; iqs = GNUNET_DB_STATUS_HARD_ERROR; return; } ftc->qs = i + 1; ftc->cb (ftc->cb_cls, ftc->h_contract_terms, &coin_pub, &wtid, execution_time, proof); GNUNET_PQ_cleanup_result (rs); } } /** * Lookup information about a transfer by @a h_contract_terms. Note * that in theory there could be multiple wire transfers for a * single @a h_contract_terms, as the transaction may have involved * multiple coins and the coins may be spread over different wire * transfers. * * @param cls closure * @param h_contract_terms key for the search * @param cb function to call with transfer data * @param cb_cls closure for @a cb * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_find_transfers_by_hash (void *cls, const struct GNUNET_HashCode *h_contract_terms, TALER_MERCHANTDB_TransferCallback cb, void *cb_cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_end }; struct FindTransfersContext ftc = { .h_contract_terms = h_contract_terms, .cb = cb, .cb_cls = cb_cls }; enum GNUNET_DB_QueryStatus qs; check_connection (pg); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "find_transfers_by_hash", params, &find_transfers_cb, &ftc); if (0 >= qs) return qs; return ftc.qs; } /** * Closure for #find_deposits_cb(). */ struct FindDepositsContext { /** * Function to call for each result. */ TALER_MERCHANTDB_CoinDepositCallback cb; /** * Closure for @e cb. */ 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 cls of type `struct FindDepositsContext *` * @param result the postgres result * @param num_result the number of results in @a result */ static void find_deposits_cb (void *cls, PGresult *result, unsigned int num_results) { struct FindDepositsContext *fdc = cls; struct PostgresClosure *pg = fdc->pg; for (unsigned int i = 0; iqs = GNUNET_DB_STATUS_HARD_ERROR; return; } fdc->qs = i + 1; fdc->cb (fdc->cb_cls, &h_contract_terms, &coin_pub, exchange_url, &amount_with_fee, &deposit_fee, &refund_fee, &wire_fee, exchange_proof); GNUNET_PQ_cleanup_result (rs); } } /** * Lookup information about a coin deposits by @a wtid. * * @param cls closure * @param wtid wire transfer identifier to find matching transactions for * @param cb function to call with payment data * @param cb_cls closure for @a cb * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_find_deposits_by_wtid (void *cls, const struct TALER_WireTransferIdentifierRawP *wtid, TALER_MERCHANTDB_CoinDepositCallback cb, void *cb_cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (wtid), GNUNET_PQ_query_param_end }; struct FindDepositsContext fdc = { .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, "find_deposits_by_wtid", params, &find_deposits_cb, &fdc); if (0 >= qs) return qs; return fdc.qs; } /** * Closure for #get_refunds_cb(). */ struct GetRefundsContext { /** * 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 GetRefundsContext *` * @param result the postgres result * @param num_result the number of results in @a result */ static void get_refunds_cb (void *cls, PGresult *result, unsigned int num_results) { struct GetRefundsContext *grc = cls; struct PostgresClosure *pg = grc->pg; for (unsigned int i = 0; iqs = GNUNET_DB_STATUS_HARD_ERROR; return; } grc->qs = i + 1; grc->rc (grc->rc_cls, &coin_pub, exchange_url, rtransaction_id, reason, &refund_amount, &refund_fee); GNUNET_PQ_cleanup_result (rs); } } /** * Obtain refunds associated with a contract. * * @param cls closure, typically a connection to the db * @param merchant_pub public key of the merchant instance * @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_get_refunds_from_contract_terms_hash ( void *cls, const struct TALER_MerchantPublicKeyP *merchant_pub, 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_auto_from_type (merchant_pub), GNUNET_PQ_query_param_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_end }; struct GetRefundsContext grc = { .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), TALER_B2S (merchant_pub)); check_connection (pg); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "find_refunds_from_contract_terms_hash", params, &get_refunds_cb, &grc); if (0 >= qs) return qs; return grc.qs; } /** * Obtain refund proofs associated with a refund operation on a * coin. * * @param cls closure, typically a connection to the db * @param merchant_pub public key of the merchant instance * @param h_contract_terms hash code of the contract * @param coin_pub public key of the coin * @param rtransaction_id identificator of the refund * @param[out] exchange_pub public key of the exchange affirming the refund * @param[out] exchange_sig signature of the exchange affirming the refund * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_get_refund_proof ( void *cls, const struct TALER_MerchantPublicKeyP *merchant_pub, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_CoinSpendPublicKeyP *coin_pub, uint64_t rtransaction_id, struct TALER_ExchangePublicKeyP *exchange_pub, struct TALER_ExchangeSignatureP *exchange_sig) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_uint64 (&rtransaction_id), 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, "get_refund_proof", params, rs); } /** * Store refund proofs associated with a refund operation on a * coin. * * @param cls closure, typically a connection to the db * @param merchant_pub public key of the merchant instance * @param h_contract_terms hash code of the contract * @param coin_pub public key of the coin * @param rtransaction_id identificator of the refund * @param exchange_pub public key of the exchange affirming the refund * @param exchange_sig signature of the exchange affirming the refund * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_put_refund_proof ( void *cls, const struct TALER_MerchantPublicKeyP *merchant_pub, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_CoinSpendPublicKeyP *coin_pub, uint64_t rtransaction_id, const struct TALER_ExchangePublicKeyP *exchange_pub, const struct TALER_ExchangeSignatureP *exchange_sig) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&rtransaction_id), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_auto_from_type (exchange_sig), GNUNET_PQ_query_param_auto_from_type (exchange_pub), GNUNET_PQ_query_param_end }; TALER_LOG_DEBUG ("Inserting refund proof %s + %s\n", GNUNET_h2s (h_contract_terms), TALER_B2S (coin_pub)); check_connection (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_refund_proof", params); } /** * Insert a refund row into merchant_refunds. Not meant to be exported * in the db API. * * @param cls closure, typically a connection to the db * @param merchant_pub merchant instance public key * @param h_contract_terms hashcode of the contract related to this refund * @param coin_pub public key of the coin giving the (part of) refund * @param reason human readable explanation behind the refund * @param refund how much this coin is refunding */ static enum GNUNET_DB_QueryStatus insert_refund (void *cls, const struct TALER_MerchantPublicKeyP *merchant_pub, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_CoinSpendPublicKeyP *coin_pub, const char *reason, const struct TALER_Amount *refund) { 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 (h_contract_terms), GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_string (reason), TALER_PQ_query_param_amount (refund), GNUNET_PQ_query_param_end }; TALER_LOG_DEBUG ("Inserting refund %s + %s\n", GNUNET_h2s (h_contract_terms), TALER_B2S (merchant_pub)); check_connection (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_refund", params); } /** * Store information about wire fees charged by an exchange, * including signature (so we have proof). * * @param cls closure * @paramm exchange_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 exchange_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 *exchange_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 *exchange_sig) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (exchange_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 (exchange_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 (exchange_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); } /** * Obtain information about wire fees charged by an exchange, * including signature (so we have proof). * * @param cls closure * @param exchange_pub public key of the exchange * @param h_wire_method hash of 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] exchange_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 *exchange_pub, const struct GNUNET_HashCode *h_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 *exchange_sig) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (exchange_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 ("exchange_sig", exchange_sig), GNUNET_PQ_result_spec_end }; check_connection (pg); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "lookup_wire_fee", params, rs); } /** * 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 #GNUNET_SYSERR on hard errors. */ int 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_result 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 = GNUNET_SYSERR; return; } if (0 > TALER_amount_add (&ictx->refunded_amount, &ictx->refunded_amount, &acc)) { GNUNET_break (0); ictx->err = GNUNET_SYSERR; return; } 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; /** * Merchant instance public key */ const struct TALER_MerchantPublicKeyP *merchant_pub; /** * Hash code representing the contract */ const struct GNUNET_HashCode *h_contract_terms; /** * Human-readable reason behind this refund */ const char *reason; /** * Transaction status code. */ 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 closure, our `struct InsertRefundContext` * @param result the postgres result * @param num_result 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 TALER_Amount deposit_refund[GNUNET_NZL (num_results)]; struct TALER_CoinSpendPublicKeyP deposit_coin_pubs[GNUNET_NZL (num_results)]; struct TALER_Amount deposit_amount_with_fee[GNUNET_NZL (num_results)]; GNUNET_assert (GNUNET_OK == TALER_amount_get_zero (ctx->refund->currency, ¤t_refund)); /* 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; iqs = GNUNET_DB_STATUS_HARD_ERROR; return; } 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", params, &process_refund_cb, &ictx); if ( (GNUNET_OK != ictx.err) || (GNUNET_DB_STATUS_HARD_ERROR == ires) ) { GNUNET_break (0); ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } if (GNUNET_DB_STATUS_SOFT_ERROR == ires) { ctx->qs = GNUNET_DB_STATUS_SOFT_ERROR; return; } deposit_refund[i] = ictx.refunded_amount; deposit_amount_with_fee[i] = amount_with_fee; deposit_coin_pubs[i] = coin_pub; if (0 > TALER_amount_add (¤t_refund, ¤t_refund, &ictx.refunded_amount)) { GNUNET_break (0); ctx->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Existing refund for coin %s is %s\n", TALER_B2S (&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)); return; } /* Phase 2: Try to increase current refund until it matches desired refund */ for (unsigned int i = 0; i TALER_amount_subtract (&left, &deposit_amount_with_fee[i], &deposit_refund[i])) { GNUNET_break (0); ctx->qs = GNUNET_DB_STATUS_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 (&deposit_coin_pubs[i])); continue; } /* 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->qs = GNUNET_DB_STATUS_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->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } /* actually run the refund */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Coin %s deposit amount is %s\n", TALER_B2S (&deposit_coin_pubs[i]), TALER_amount2s (&deposit_amount_with_fee[i])); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Coin %s refund will be incremented by %s\n", TALER_B2S (&deposit_coin_pubs[i]), TALER_amount2s (increment)); { enum GNUNET_DB_QueryStatus qs; if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != (qs = insert_refund (ctx->pg, ctx->merchant_pub, ctx->h_contract_terms, &deposit_coin_pubs[i], ctx->reason, increment))) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); ctx->qs = qs; return; } } /* stop immediately if we are done */ if (0 == TALER_amount_cmp (ctx->refund, ¤t_refund)) 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->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } /** * 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 (NT). * * @param cls closure * @param h_contract_terms * @param merchant_pub merchant's instance public key * @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 GNUNET_DB_QueryStatus postgres_increase_refund_for_contract_NT ( void *cls, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_MerchantPublicKeyP *merchant_pub, 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_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_end }; struct InsertRefundContext ctx = { .pg = pg, .qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, .refund = refund, .reason = reason, .h_contract_terms = h_contract_terms, .merchant_pub = merchant_pub }; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Asked to refund %s on contract %s\n", TALER_amount2s (refund), GNUNET_h2s (h_contract_terms)); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "find_deposits", 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 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; case GNUNET_DB_STATUS_SOFT_ERROR: case GNUNET_DB_STATUS_HARD_ERROR: return qs; default: /* Got one or more deposits */ return ctx.qs; } } /** * Lookup proof information about a wire transfer. * * @param cls closure * @param exchange_url from which exchange are we looking for proof * @param wtid wire transfer identifier for the search * @param cb function to call with proof data * @param cb_cls closure for @a cb * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_find_proof_by_wtid (void *cls, const char *exchange_url, const struct TALER_WireTransferIdentifierRawP *wtid, TALER_MERCHANTDB_ProofCallback cb, void *cb_cls) { 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 }; json_t *proof; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_result_spec_json ("proof", &proof), GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; check_connection (pg); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "find_proof_by_wtid", params, rs); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) { cb (cb_cls, proof); GNUNET_PQ_cleanup_result (rs); } return qs; } /** * 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 was topped up. This has to happen before tips can be * authorized. * * @param cls closure, typically a connection to the db * @param reserve_priv which reserve is topped up or created * @param credit_uuid unique identifier for the credit operation * @param credit how much money was added to the reserve * @param expiration when does the reserve expire? * @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_enable_tip_reserve_TR (void *cls, const struct TALER_ReservePrivateKeyP *reserve_priv, const struct GNUNET_HashCode *credit_uuid, const struct TALER_Amount *credit, struct GNUNET_TIME_Absolute expiration) { struct PostgresClosure *pg = cls; struct GNUNET_TIME_Absolute old_expiration; struct TALER_Amount old_balance; enum GNUNET_DB_QueryStatus qs; struct GNUNET_TIME_Absolute new_expiration; struct TALER_Amount new_balance; unsigned int retries; retries = 0; check_connection (pg); RETRY: if (MAX_RETRIES < ++retries) return GNUNET_DB_STATUS_SOFT_ERROR; if (GNUNET_OK != postgres_start (pg, "enable tip reserve")) { GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } /* ensure that credit_uuid is new/unique */ { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (credit_uuid), GNUNET_PQ_query_param_auto_from_type (reserve_priv), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "lookup_tip_credit_uuid", 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; return qs; } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs) { /* UUID already exists, we are done! */ postgres_rollback (pg); return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } } { struct GNUNET_TIME_Absolute now; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (reserve_priv), GNUNET_PQ_query_param_auto_from_type (credit_uuid), GNUNET_PQ_query_param_absolute_time (&now), TALER_PQ_query_param_amount (credit), 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_tip_credit_uuid", params); if (0 > qs) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); postgres_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; return qs; } } /* Obtain existing reserve balance */ { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (reserve_priv), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_absolute_time ("expiration", &old_expiration), TALER_PQ_RESULT_SPEC_AMOUNT ("balance", &old_balance), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "lookup_tip_reserve_balance", 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; return qs; } if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) && (GNUNET_TIME_absolute_get_remaining (old_expiration).rel_value_us > 0) ) { new_expiration = GNUNET_TIME_absolute_max (old_expiration, expiration); if (0 > TALER_amount_add (&new_balance, credit, &old_balance)) { GNUNET_break (0); postgres_rollback (pg); return GNUNET_DB_STATUS_HARD_ERROR; } } else { if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Old reserve balance of %s had expired at %s, not carrying it over!\n", TALER_amount2s (&old_balance), GNUNET_STRINGS_absolute_time_to_string (old_expiration)); } new_expiration = expiration; new_balance = *credit; } { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (reserve_priv), GNUNET_PQ_query_param_absolute_time (&new_expiration), TALER_PQ_query_param_amount (&new_balance), GNUNET_PQ_query_param_end }; const char *stmt; stmt = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) ? "update_tip_reserve_balance" : "insert_tip_reserve_balance"; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, stmt, params); if (0 > qs) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); postgres_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; return qs; } } 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; } /** * Authorize a tip over @a amount from reserve @a reserve_priv. Remember * the authorization under @a tip_id for later, together with the * @a justification. * * @param cls closure, typically a connection to the db * @param justification why was the tip approved * @param extra extra data for the customer's wallet * @param amount how high is the tip (with fees) * @param reserve_priv which reserve is debited * @param exchange_url which exchange manages the tip * @param[out] expiration set to when the tip expires * @param[out] tip_id set to the unique ID for the tip * @return taler error code * #TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but has expired * #TALER_EC_TIP_AUTHORIZE_RESERVE_UNKNOWN if the reserve is not known * #TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has insufficient funds left * #TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR on hard DB errors * #TALER_EC_TIP_AUTHORIZE_DB_SOFT_ERROR on soft DB errors (client should retry) * #TALER_EC_NONE upon success */ static enum TALER_ErrorCode postgres_authorize_tip_TR (void *cls, const char *justification, const json_t *extra, const struct TALER_Amount *amount, const struct TALER_ReservePrivateKeyP *reserve_priv, const char *exchange_url, struct GNUNET_TIME_Absolute *expiration, struct GNUNET_HashCode *tip_id) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (reserve_priv), GNUNET_PQ_query_param_end }; struct GNUNET_TIME_Absolute old_expiration; struct TALER_Amount old_balance; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_absolute_time ("expiration", &old_expiration), TALER_PQ_RESULT_SPEC_AMOUNT ("balance", &old_balance), GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; struct TALER_Amount new_balance; unsigned int retries; retries = 0; check_connection (pg); RETRY: if (MAX_RETRIES < ++retries) return TALER_EC_TIP_AUTHORIZE_DB_SOFT_ERROR; if (GNUNET_OK != postgres_start (pg, "authorize tip")) { GNUNET_break (0); return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; } qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "lookup_tip_reserve_balance", params, rs); if (0 >= qs) { /* reserve unknown */ postgres_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) return TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS; return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; } if (0 == GNUNET_TIME_absolute_get_remaining (old_expiration).rel_value_us) { /* reserve expired, can't be used */ postgres_rollback (pg); return TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED; } if (0 > TALER_amount_subtract (&new_balance, &old_balance, amount)) { /* insufficient funds left in reserve */ postgres_rollback (pg); return TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS; } /* Update reserve balance */ { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (reserve_priv), GNUNET_PQ_query_param_absolute_time (&old_expiration), TALER_PQ_query_param_amount (&new_balance), GNUNET_PQ_query_param_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "update_tip_reserve_balance", params); if (0 > qs) { postgres_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; } } /* Generate and store tip ID */ *expiration = old_expiration; GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG, tip_id); { struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (reserve_priv), GNUNET_PQ_query_param_auto_from_type (tip_id), GNUNET_PQ_query_param_string (exchange_url), GNUNET_PQ_query_param_string (justification), TALER_PQ_query_param_json (extra), GNUNET_PQ_query_param_absolute_time (&now), TALER_PQ_query_param_amount (amount), /* overall amount */ TALER_PQ_query_param_amount (amount), /* amount left */ GNUNET_PQ_query_param_end }; (void) GNUNET_TIME_round_abs (&now); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_tip_justification", params); if (0 > qs) { postgres_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; } } qs = postgres_commit (pg); if (0 <= qs) return TALER_EC_NONE; /* success! */ if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; } /** * Find out tip authorization details associated with @a tip_id * * @param cls closure, typically a connection to the d * @param tip_id the unique ID for the tip * @param[out] exchange_url set to the URL of the exchange (unless NULL) * @param[out] extra extra data to pass to the wallet (unless NULL) * @param[out] amount set to the authorized amount (unless NULL) * @param[out] amount_left set to the amount left (unless NULL) * @param[out] timestamp set to the timestamp of the tip authorization (unless NULL) * @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_lookup_tip_by_id (void *cls, const struct GNUNET_HashCode *tip_id, char **exchange_url, json_t **extra, struct TALER_Amount *amount, struct TALER_Amount *amount_left, struct GNUNET_TIME_Absolute *timestamp) { struct PostgresClosure *pg = cls; char *res_exchange_url; json_t *res_extra; struct TALER_Amount res_amount; struct TALER_Amount res_amount_left; struct GNUNET_TIME_Absolute res_timestamp; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (tip_id), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_string ("exchange_url", &res_exchange_url), GNUNET_PQ_result_spec_absolute_time ("timestamp", &res_timestamp), TALER_PQ_result_spec_json ("extra", &res_extra), TALER_PQ_RESULT_SPEC_AMOUNT ("amount", &res_amount), TALER_PQ_RESULT_SPEC_AMOUNT ("left", &res_amount_left), GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "find_tip_by_id", params, rs); if (0 >= qs) { if (NULL != exchange_url) *exchange_url = NULL; return qs; } if (NULL != exchange_url) *exchange_url = strdup (res_exchange_url); if (NULL != amount) *amount = res_amount; if (NULL != amount_left) *amount_left = res_amount_left; if (NULL != timestamp) *timestamp = res_timestamp; if (NULL != extra) { json_incref (res_extra); *extra = res_extra; } GNUNET_PQ_cleanup_result (rs); return qs; } /** * Pickup a tip over @a amount using pickup id @a pickup_id. * * @param cls closure, typically a connection to the db * @param amount how high is the amount picked up (with fees) * @param tip_id the unique ID from the tip authorization * @param pickup_id the unique ID identifying the pick up operation * (to allow replays, hash over the coin envelope and denomination key) * @param[out] reserve_priv which reserve key to use to sign * @return taler error code * #TALER_EC_TIP_PICKUP_ID_UNKNOWN if @a tip_id is unknown * #TALER_EC_TIP_PICKUP_NO_FUNDS if @a tip_id has insufficient funds left * #TALER_EC_TIP_PICKUP_DB_ERROR_HARD on hard database errors * #TALER_EC_TIP_PICKUP_AMOUNT_CHANGED if @a amount is different for known @a pickup_id * #TALER_EC_TIP_PICKUP_DB_ERROR_SOFT on soft database errors (client should retry) * #TALER_EC_NONE upon success (@a reserve_priv was set) */ static enum TALER_ErrorCode postgres_pickup_tip_TR (void *cls, const struct TALER_Amount *amount, const struct GNUNET_HashCode *tip_id, const struct GNUNET_HashCode *pickup_id, struct TALER_ReservePrivateKeyP *reserve_priv) { struct PostgresClosure *pg = cls; struct TALER_Amount left_amount; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (tip_id), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("reserve_priv", reserve_priv), TALER_PQ_RESULT_SPEC_AMOUNT ("left", &left_amount), GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; unsigned int retries; retries = 0; check_connection (pg); RETRY: if (MAX_RETRIES < ++retries) return TALER_EC_TIP_PICKUP_DB_ERROR_SOFT; if (GNUNET_OK != postgres_start (pg, "pickup tip")) { GNUNET_break (0); return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; } qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "lookup_reserve_by_tip_id", params, rs); if (0 >= qs) { /* tip ID unknown */ memset (reserve_priv, 0, sizeof (*reserve_priv)); postgres_rollback (pg); if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) return TALER_EC_TIP_PICKUP_TIP_ID_UNKNOWN; if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; } /* Check if pickup_id already exists */ { struct TALER_Amount existing_amount; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (pickup_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", &existing_amount), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "lookup_amount_by_pickup", params, rs); if (0 > qs) { /* DB error */ memset (reserve_priv, 0, sizeof (*reserve_priv)); postgres_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; } if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) { if (0 != TALER_amount_cmp (&existing_amount, amount)) { GNUNET_break_op (0); postgres_rollback (pg); return TALER_EC_TIP_PICKUP_AMOUNT_CHANGED; } postgres_commit (pg); return TALER_EC_NONE; /* we are done! */ } } /* Calculate new balance */ { struct TALER_Amount new_left; if (0 > TALER_amount_subtract (&new_left, &left_amount, amount)) { /* attempt to take more tips than the tipping amount */ GNUNET_break_op (0); memset (reserve_priv, 0, sizeof (*reserve_priv)); postgres_rollback (pg); return TALER_EC_TIP_PICKUP_NO_FUNDS; } /* Update DB: update balance */ { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (tip_id), TALER_PQ_query_param_amount (&new_left), GNUNET_PQ_query_param_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "update_tip_balance", params); if (0 > qs) { postgres_rollback (pg); memset (reserve_priv, 0, sizeof (*reserve_priv)); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; } } /* Update DB: remember pickup_id */ { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (tip_id), GNUNET_PQ_query_param_auto_from_type (pickup_id), TALER_PQ_query_param_amount (amount), GNUNET_PQ_query_param_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_pickup_id", params); if (0 > qs) { postgres_rollback (pg); memset (reserve_priv, 0, sizeof (*reserve_priv)); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; } } } qs = postgres_commit (pg); if (0 <= qs) return TALER_EC_NONE; /* success */ if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; } /** * 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; 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_instances", "SELECT" " merchant_serial" ",merchant_pub" ",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" ",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)", 12), /* 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_keys", "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_inactivate_account() */ GNUNET_PQ_make_prepare ("inactivate_account", "UPDATE merchant_accounts SET" " active=FALSE" " WHERE h_wire = $1", 1), /* for postgres_lookup_products() */ GNUNET_PQ_make_prepare ("lookup_products", "SELECT" " product_id" " FROM merchant_inventory" " JOIN merchant_instances" " USING (merchant_serial)" " WHERE merchant_instances.merchant_id=$1", 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 $10 - total_sold >= $11" " 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 > " " (SELECT SUM(total_locked)" " FROM merchant_inventory_locks" " WHERE product_serial=ps.product_serial) + " " (SELECT SUM(total_locked)" " 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 pay_deadline < $3", 3), /* OLD API: */ #if 0 GNUNET_PQ_make_prepare ("insert_deposit", "INSERT INTO merchant_deposits" "(h_contract_terms" ",merchant_pub" ",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" ",signkey_pub" ",exchange_proof) VALUES " "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)", 14), GNUNET_PQ_make_prepare ("insert_transfer", "INSERT INTO merchant_transfers" "(h_contract_terms" ",coin_pub" ",wtid) VALUES " "($1, $2, $3)", 3), GNUNET_PQ_make_prepare ("insert_refund", "INSERT INTO merchant_refunds" "(merchant_pub" ",h_contract_terms" ",coin_pub" ",reason" ",refund_amount_val" ",refund_amount_frac" ") VALUES" "($1, $2, $3, $4, $5, $6)", 6), GNUNET_PQ_make_prepare ("insert_proof", "INSERT INTO merchant_proofs" "(exchange_url" ",wtid" ",execution_time" ",signkey_pub" ",proof) VALUES " "($1, $2, $3, $4, $5)", 5), GNUNET_PQ_make_prepare ("insert_contract_terms", "INSERT INTO merchant_contract_terms" "(order_id" ",merchant_pub" ",timestamp" ",contract_terms" ",h_contract_terms)" " VALUES " "($1, $2, $3, $4, $5)", 5), GNUNET_PQ_make_prepare ("insert_order", "INSERT INTO merchant_orders" "(order_id" ",merchant_pub" ",timestamp" ",contract_terms)" " VALUES " "($1, $2, $3, $4)", 4), GNUNET_PQ_make_prepare ("insert_session_info", "INSERT INTO merchant_session_info" "(session_id" ",fulfillment_url" ",order_id" ",merchant_pub" ",timestamp)" " VALUES " "($1, $2, $3, $4, $5)", 5), GNUNET_PQ_make_prepare ("mark_proposal_paid", "UPDATE merchant_contract_terms SET" " paid=TRUE" " WHERE h_contract_terms=$1" " AND merchant_pub=$2", 2), GNUNET_PQ_make_prepare ("insert_wire_fee", "INSERT INTO exchange_wire_fees" "(exchange_pub" ",h_wire_method" ",wire_fee_val" ",wire_fee_frac" ",closing_fee_val" ",closing_fee_frac" ",start_date" ",end_date" ",exchange_sig)" " VALUES " "($1, $2, $3, $4, $5, $6, $7, $8, $9)", 9), GNUNET_PQ_make_prepare ("lookup_wire_fee", "SELECT" " wire_fee_val" ",wire_fee_frac" ",closing_fee_val" ",closing_fee_frac" ",start_date" ",end_date" ",exchange_sig" " FROM exchange_wire_fees" " WHERE exchange_pub=$1" " AND h_wire_method=$2" " AND start_date <= $3" " AND end_date > $3", 1), GNUNET_PQ_make_prepare ("find_contract_terms_from_hash", "SELECT" " contract_terms" " FROM merchant_contract_terms" " WHERE h_contract_terms=$1" " AND merchant_pub=$2", 2), GNUNET_PQ_make_prepare ("find_paid_contract_terms_from_hash", "SELECT" " contract_terms" " FROM merchant_contract_terms" " WHERE h_contract_terms=$1" " AND merchant_pub=$2" " AND paid=TRUE", 2), GNUNET_PQ_make_prepare ("find_refunds", "SELECT" " refund_amount_val" ",refund_amount_frac" " FROM merchant_refunds" " WHERE coin_pub=$1", 1), GNUNET_PQ_make_prepare ("find_contract_terms_history", "SELECT" " contract_terms" " FROM merchant_contract_terms" " WHERE" " order_id=$1" " AND merchant_pub=$2" " AND paid=TRUE", 2), GNUNET_PQ_make_prepare ("find_contract_terms", "SELECT" " contract_terms" " FROM merchant_contract_terms" " WHERE" " order_id=$1" " AND merchant_pub=$2", 2), GNUNET_PQ_make_prepare ("find_order", "SELECT" " contract_terms" " FROM merchant_orders" " WHERE" " order_id=$1" " AND merchant_pub=$2", 2), GNUNET_PQ_make_prepare ("find_session_info", "SELECT" " order_id" " FROM merchant_session_info" " WHERE" " fulfillment_url=$1" " AND session_id=$2" " AND merchant_pub=$3", 2), GNUNET_PQ_make_prepare ("find_contract_terms_by_date", "SELECT" " contract_terms" ",order_id" ",row_id" " FROM merchant_contract_terms" " WHERE" " timestamp<$1" " AND merchant_pub=$2" " AND paid=TRUE" " ORDER BY row_id DESC, timestamp DESC" " LIMIT $3", 3), GNUNET_PQ_make_prepare ("find_refunds_from_contract_terms_hash", "SELECT" " coin_pub" ",merchant_deposits.exchange_url" ",rtransaction_id" ",refund_amount_val" ",refund_amount_frac" ",merchant_deposits.refund_fee_val" ",merchant_deposits.refund_fee_frac" ",reason" " FROM merchant_refunds" " JOIN merchant_deposits USING (merchant_pub, coin_pub)" " WHERE merchant_refunds.merchant_pub=$1" " AND merchant_refunds.h_contract_terms=$2", 2), GNUNET_PQ_make_prepare ("get_refund_proof", "SELECT" " exchange_pub" ",exchange_sig" " FROM merchant_refund_proofs" " WHERE" " h_contract_terms=$1" " AND merchant_pub=$2" " AND coin_pub=$3" " AND rtransaction_id=$4", 4), GNUNET_PQ_make_prepare ("insert_refund_proof", "INSERT INTO merchant_refund_proofs" "(rtransaction_id" ",merchant_pub" ",h_contract_terms" ",coin_pub" ",exchange_sig" ",exchange_pub)" " VALUES " "($1, $2, $3, $4, $5, $6)", 6), GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_asc", "SELECT" " contract_terms" ",order_id" ",row_id" " FROM merchant_contract_terms" " WHERE" " timestamp>$1" " AND merchant_pub=$2" " AND row_id>$3" " AND paid=TRUE" " ORDER BY row_id ASC, timestamp ASC" " LIMIT $4", 4), GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range", "SELECT" " contract_terms" ",order_id" ",row_id" " FROM merchant_contract_terms" " WHERE" " timestamp>$1" " AND merchant_pub=$2" " AND row_id>$3" " AND paid=TRUE" " ORDER BY row_id DESC, timestamp DESC" " LIMIT $4", 4), GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_past_asc", "SELECT" " contract_terms" ",order_id" ",row_id" " FROM merchant_contract_terms" " WHERE" " timestamp<$1" " AND merchant_pub=$2" " AND row_id<$3" " AND paid=TRUE" " ORDER BY row_id ASC, timestamp ASC" " LIMIT $4", 4), GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_past", "SELECT" " contract_terms" ",order_id" ",row_id" " FROM merchant_contract_terms" " WHERE" " timestamp<$1" " AND merchant_pub=$2" " AND row_id<$3" " AND paid=TRUE" " ORDER BY row_id DESC, timestamp DESC" " LIMIT $4", 4), GNUNET_PQ_make_prepare ("find_deposits", "SELECT" " 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_proof" " FROM merchant_deposits" " WHERE h_contract_terms=$1" " AND merchant_pub=$2", 2), GNUNET_PQ_make_prepare ("find_deposits_by_hash_and_coin", "SELECT" " 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_url" ",exchange_proof" " FROM merchant_deposits" " WHERE h_contract_terms=$1" " AND merchant_pub=$2" " AND coin_pub=$3", 3), GNUNET_PQ_make_prepare ("find_transfers_by_hash", "SELECT" " coin_pub" ",wtid" ",merchant_proofs.execution_time" ",merchant_proofs.proof" " FROM merchant_transfers" " JOIN merchant_proofs USING (wtid)" " WHERE h_contract_terms=$1", 1), GNUNET_PQ_make_prepare ("find_deposits_by_wtid", "SELECT" " merchant_transfers.h_contract_terms" ",merchant_transfers.coin_pub" ",merchant_deposits.amount_with_fee_val" ",merchant_deposits.amount_with_fee_frac" ",merchant_deposits.deposit_fee_val" ",merchant_deposits.deposit_fee_frac" ",merchant_deposits.refund_fee_val" ",merchant_deposits.refund_fee_frac" ",merchant_deposits.wire_fee_val" ",merchant_deposits.wire_fee_frac" ",merchant_deposits.exchange_url" ",merchant_deposits.exchange_proof" " FROM merchant_transfers" " JOIN merchant_deposits" " USING (h_contract_terms,coin_pub)" " WHERE wtid=$1", 1), GNUNET_PQ_make_prepare ("find_proof_by_wtid", "SELECT" " proof" " FROM merchant_proofs" " WHERE wtid=$1" " AND exchange_url=$2", 2), GNUNET_PQ_make_prepare ("lookup_tip_reserve_balance", "SELECT" " expiration" ",balance_val" ",balance_frac" " FROM merchant_tip_reserves" " WHERE reserve_priv=$1", 1), GNUNET_PQ_make_prepare ("find_tip_authorizations", "SELECT" " amount_val" ",amount_frac" ",justification" ",extra" ",tip_id" " FROM merchant_tips" " WHERE reserve_priv=$1", 1), GNUNET_PQ_make_prepare ("update_tip_reserve_balance", "UPDATE merchant_tip_reserves SET" " expiration=$2" ",balance_val=$3" ",balance_frac=$4" " WHERE reserve_priv=$1", 4), GNUNET_PQ_make_prepare ("insert_tip_reserve_balance", "INSERT INTO merchant_tip_reserves" "(reserve_priv" ",expiration" ",balance_val" ",balance_frac" ") VALUES " "($1, $2, $3, $4)", 4), GNUNET_PQ_make_prepare ("insert_tip_justification", "INSERT INTO merchant_tips" "(reserve_priv" ",tip_id" ",exchange_url" ",justification" ",extra" ",timestamp" ",amount_val" ",amount_frac" ",left_val" ",left_frac" ") VALUES " "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", 10), GNUNET_PQ_make_prepare ("lookup_reserve_by_tip_id", "SELECT" " reserve_priv" ",left_val" ",left_frac" " FROM merchant_tips" " WHERE tip_id=$1", 1), GNUNET_PQ_make_prepare ("lookup_amount_by_pickup", "SELECT" " amount_val" ",amount_frac" " FROM merchant_tip_pickups" " WHERE pickup_id=$1" " AND tip_id=$2", 2), GNUNET_PQ_make_prepare ("find_tip_by_id", "SELECT" " exchange_url" ",extra" ",timestamp" ",amount_val" ",amount_frac" ",left_val" ",left_frac" " FROM merchant_tips" " WHERE tip_id=$1", 1), GNUNET_PQ_make_prepare ("update_tip_balance", "UPDATE merchant_tips SET" " left_val=$2" ",left_frac=$3" " WHERE tip_id=$1", 3), GNUNET_PQ_make_prepare ("insert_pickup_id", "INSERT INTO merchant_tip_pickups" "(tip_id" ",pickup_id" ",amount_val" ",amount_frac" ") VALUES " "($1, $2, $3, $4)", 4), GNUNET_PQ_make_prepare ("insert_tip_credit_uuid", "INSERT INTO merchant_tip_reserve_credits" "(reserve_priv" ",credit_uuid" ",timestamp" ",amount_val" ",amount_frac" ") VALUES " "($1, $2, $3, $4, $5)", 5), GNUNET_PQ_make_prepare ("lookup_tip_credit_uuid", "SELECT 1 " "FROM merchant_tip_reserve_credits " "WHERE credit_uuid=$1 AND reserve_priv=$2", 2), #endif GNUNET_PQ_PREPARED_STATEMENT_END }; 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; } pg->conn = GNUNET_PQ_connect_with_cfg (cfg, "merchantdb-postgres", "merchant-", NULL, ps); if (NULL == pg->conn) { GNUNET_free (pg->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->drop_tables = &postgres_drop_tables; plugin->preflight = &postgres_preflight; plugin->start = &postgres_start; plugin->rollback = &postgres_rollback; plugin->commit = &postgres_commit; plugin->lookup_instances = &postgres_lookup_instances; 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->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; /* old API: */ plugin->store_deposit = &postgres_store_deposit; plugin->store_coin_to_transfer = &postgres_store_coin_to_transfer; plugin->store_transfer_to_proof = &postgres_store_transfer_to_proof; plugin->store_wire_fee_by_exchange = &postgres_store_wire_fee_by_exchange; plugin->find_payments_by_hash_and_coin = &postgres_find_payments_by_hash_and_coin; plugin->find_payments = &postgres_find_payments; plugin->find_transfers_by_hash = &postgres_find_transfers_by_hash; plugin->find_deposits_by_wtid = &postgres_find_deposits_by_wtid; plugin->find_proof_by_wtid = &postgres_find_proof_by_wtid; plugin->insert_contract_terms = &postgres_insert_contract_terms; plugin->insert_order = &postgres_insert_order; plugin->find_order = &postgres_find_order; plugin->find_contract_terms = &postgres_find_contract_terms; plugin->find_contract_terms_history = &postgres_find_contract_terms_history; plugin->find_contract_terms_by_date = &postgres_find_contract_terms_by_date; plugin->get_authorized_tip_amount = &postgres_get_authorized_tip_amount; plugin->find_contract_terms_by_date_and_range = &postgres_find_contract_terms_by_date_and_range; plugin->find_contract_terms_from_hash = &postgres_find_contract_terms_from_hash; plugin->find_paid_contract_terms_from_hash = &postgres_find_paid_contract_terms_from_hash; plugin->get_refunds_from_contract_terms_hash = &postgres_get_refunds_from_contract_terms_hash; plugin->lookup_wire_fee = &postgres_lookup_wire_fee; plugin->increase_refund_for_contract_NT = &postgres_increase_refund_for_contract_NT; plugin->get_refund_proof = &postgres_get_refund_proof; plugin->put_refund_proof = &postgres_put_refund_proof; plugin->mark_proposal_paid = &postgres_mark_proposal_paid; plugin->insert_session_info = &postgres_insert_session_info; plugin->find_session_info = &postgres_find_session_info; plugin->enable_tip_reserve_TR = &postgres_enable_tip_reserve_TR; plugin->authorize_tip_TR = &postgres_authorize_tip_TR; plugin->lookup_tip_by_id = &postgres_lookup_tip_by_id; plugin->pickup_tip_TR = &postgres_pickup_tip_TR; 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; GNUNET_PQ_disconnect (pg->conn); GNUNET_free (pg->sql_dir); GNUNET_free (pg->currency); GNUNET_free (pg); GNUNET_free (plugin); return NULL; } /* end of plugin_merchantdb_postgres.c */