diff options
author | Christian Grothoff <christian@grothoff.org> | 2020-02-03 23:21:10 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2020-02-03 23:21:10 +0100 |
commit | 93f169dcffad89aab134b9cfe541ec795c4f0fe7 (patch) | |
tree | b43944d7d544096dc42fec02ee47565c0c095652 | |
parent | 50b6632d7f1742c84ba18abfea2f17e2e0b942ff (diff) | |
parent | 4f14149f6372ec30a550650cadcc9e976d42501b (diff) | |
download | merchant-93f169dcffad89aab134b9cfe541ec795c4f0fe7.tar.gz merchant-93f169dcffad89aab134b9cfe541ec795c4f0fe7.tar.bz2 merchant-93f169dcffad89aab134b9cfe541ec795c4f0fe7.zip |
Merge branch 'master' of git+ssh://git.taler.net/merchant
-rw-r--r-- | src/backend/merchant.conf | 4 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd.c | 59 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd.h | 5 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_track-transfer.c | 36 | ||||
-rw-r--r-- | src/backenddb/0000.sql | 293 | ||||
-rw-r--r-- | src/backenddb/0001.sql | 171 | ||||
-rw-r--r-- | src/backenddb/Makefile.am | 16 | ||||
-rw-r--r-- | src/backenddb/drop0000.sql | 13 | ||||
-rw-r--r-- | src/backenddb/merchantdb-postgres.conf | 6 | ||||
-rw-r--r-- | src/backenddb/merchantdb_plugin.c | 10 | ||||
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 210 | ||||
-rw-r--r-- | src/backenddb/test-merchantdb-postgres.conf | 4 | ||||
-rw-r--r-- | src/backenddb/test_merchantdb.c | 5 | ||||
-rw-r--r-- | src/include/taler_merchantdb_lib.h | 4 | ||||
-rw-r--r-- | src/lib/merchant_api_pay.c | 25 | ||||
-rw-r--r-- | src/lib/merchant_api_proposal.c | 19 | ||||
-rw-r--r-- | src/lib/merchant_api_refund.c | 5 | ||||
-rw-r--r-- | src/lib/merchant_api_refund_increase.c | 5 | ||||
-rw-r--r-- | src/lib/merchant_api_tip_pickup.c | 2 | ||||
-rw-r--r-- | src/lib/merchant_api_tip_query.c | 2 | ||||
-rw-r--r-- | src/merchant-tools/taler-merchant-dbinit.c | 10 |
21 files changed, 652 insertions, 252 deletions
diff --git a/src/backend/merchant.conf b/src/backend/merchant.conf index fb82e642..4aac2dab 100644 --- a/src/backend/merchant.conf +++ b/src/backend/merchant.conf @@ -54,10 +54,6 @@ WIRE_TRANSFER_DELAY = 3 week # proposal be valid? DEFAULT_PAY_DEADLINE = 1 day -# Configuration for postgres database. -[merchantdb-postgres] -CONFIG = postgres:///talermerchant - [instance-default] KEYFILE = ${TALER_DATA_HOME}/merchant/merchant.priv diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index ccdf5668..7f2151ec 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -161,6 +161,11 @@ struct GNUNET_CONTAINER_MultiHashMap *payment_trigger_map; */ struct GNUNET_SCHEDULER_Task *resume_timeout_task; +/** + * Our configuration. + */ +static struct GNUNET_CONFIGURATION_Handle *cfg; + /** * Return #GNUNET_YES if given a valid correlation ID and @@ -642,12 +647,12 @@ locations_iterator_cb (void *cls, "street_number", NULL, }; - struct GNUNET_CONFIGURATION_Handle *cfg = cls; const char *prefix = "merchant-location-"; const char *substr = strstr (section, prefix); const char *locname; json_t *loc; + (void) cls; if ( (NULL == substr) || (substr != section) ) return; locname = section + strlen (prefix); @@ -730,7 +735,7 @@ wireformat_iterator_cb (void *cls, "HONOR_%s", mi->id); if (GNUNET_YES != - GNUNET_CONFIGURATION_get_value_yesno (iic->config, + GNUNET_CONFIGURATION_get_value_yesno (cfg, section, instance_option)) { @@ -739,7 +744,7 @@ wireformat_iterator_cb (void *cls, } GNUNET_free (instance_option); if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (iic->config, + GNUNET_CONFIGURATION_get_value_string (cfg, section, "PAYTO_URI", &payto)) @@ -752,7 +757,7 @@ wireformat_iterator_cb (void *cls, } if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (iic->config, + GNUNET_CONFIGURATION_get_value_filename (cfg, section, "WIRE_RESPONSE", &fn)) @@ -858,14 +863,16 @@ wireformat_iterator_cb (void *cls, return; } - if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string - (iic->config, - section, - "WIRE_FILE_MODE", - &wire_file_mode)) + if (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "WIRE_FILE_MODE", + &wire_file_mode)) { errno = 0; - mode_t mode = (mode_t) strtoul (wire_file_mode, NULL, 8); + mode_t mode = (mode_t) strtoul (wire_file_mode, + NULL, + 8); if (0 != errno) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, @@ -908,7 +915,7 @@ wireformat_iterator_cb (void *cls, GNUNET_asprintf (&instance_option, "ACTIVE_%s", mi->id); - wm->active = GNUNET_CONFIGURATION_get_value_yesno (iic->config, + wm->active = GNUNET_CONFIGURATION_get_value_yesno (cfg, section, instance_option); GNUNET_free (instance_option); @@ -956,7 +963,7 @@ instances_iterator_cb (void *cls, token + 1); mi = GNUNET_new (struct MerchantInstance); if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (iic->config, + GNUNET_CONFIGURATION_get_value_string (cfg, section, "NAME", &mi->name)) @@ -970,7 +977,7 @@ instances_iterator_cb (void *cls, } if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (iic->config, + GNUNET_CONFIGURATION_get_value_filename (cfg, section, "KEYFILE", &mi->keyfile)) @@ -984,7 +991,7 @@ instances_iterator_cb (void *cls, return; } if (GNUNET_OK == - GNUNET_CONFIGURATION_get_value_string (iic->config, + GNUNET_CONFIGURATION_get_value_string (cfg, section, "TIP_EXCHANGE", &mi->tip_exchange)) @@ -993,7 +1000,7 @@ instances_iterator_cb (void *cls, struct GNUNET_CRYPTO_EddsaPrivateKey *tip_pk; if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (iic->config, + GNUNET_CONFIGURATION_get_value_filename (cfg, section, "TIP_RESERVE_PRIV_FILENAME", &tip_reserves)) @@ -1097,7 +1104,7 @@ instances_iterator_cb (void *cls, .mi = mi }; - GNUNET_CONFIGURATION_iterate_sections (iic->config, + GNUNET_CONFIGURATION_iterate_sections (cfg, &wireformat_iterator_cb, &wfic); } @@ -1144,17 +1151,16 @@ lookup_instance (const char *instance_id) * Iterate over locations in config in order to populate * the location data. * - * @param config configuration handle * @return #GNUNET_OK if successful, #GNUNET_SYSERR upon errors */ static void -iterate_locations (const struct GNUNET_CONFIGURATION_Handle *config) +iterate_locations (void) { GNUNET_assert (NULL == default_locations); default_locations = json_object (); - GNUNET_CONFIGURATION_iterate_sections (config, + GNUNET_CONFIGURATION_iterate_sections (cfg, &locations_iterator_cb, - (void *) config); + NULL); } @@ -1162,19 +1168,17 @@ iterate_locations (const struct GNUNET_CONFIGURATION_Handle *config) * Iterate over each merchant instance, in order to populate * each instance's own data * - * @param config configuration handle * @return #GNUNET_OK if successful, #GNUNET_SYSERR upon errors * (for example, if no "default" instance is defined) */ static int -iterate_instances (const struct GNUNET_CONFIGURATION_Handle *config) +iterate_instances (void) { struct IterateInstancesCls iic; - iic.config = config; iic.default_instance = GNUNET_NO; iic.ret = GNUNET_OK; - GNUNET_CONFIGURATION_iterate_sections (config, + GNUNET_CONFIGURATION_iterate_sections (cfg, &instances_iterator_cb, &iic); @@ -1652,16 +1656,17 @@ run (void *cls, return; } + cfg = GNUNET_CONFIGURATION_dup (config); if (GNUNET_OK != - iterate_instances (config)) + iterate_instances ()) { GNUNET_SCHEDULER_shutdown (); return; } - iterate_locations (config); + iterate_locations (); if (NULL == - (db = TALER_MERCHANTDB_plugin_load (config))) + (db = TALER_MERCHANTDB_plugin_load (cfg))) { GNUNET_SCHEDULER_shutdown (); return; diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h index db01ea3c..4695edd0 100644 --- a/src/backend/taler-merchant-httpd.h +++ b/src/backend/taler-merchant-httpd.h @@ -43,11 +43,6 @@ struct IterateInstancesCls { /** - * Handle for the configuration beig parsed - */ - const struct GNUNET_CONFIGURATION_Handle *config; - - /** * Current index in the global array of #MerchantInstance * types. Used by the callback in order to know which index * is associated to the element being processed. diff --git a/src/backend/taler-merchant-httpd_track-transfer.c b/src/backend/taler-merchant-httpd_track-transfer.c index 86dc5960..f9e9c603 100644 --- a/src/backend/taler-merchant-httpd_track-transfer.c +++ b/src/backend/taler-merchant-httpd_track-transfer.c @@ -470,24 +470,24 @@ check_transfer (void *cls, /* Build the `TrackTransferConflictDetails` */ rctx->response = TALER_MHD_make_json_pack ( - "{s:I, s:s, s:o, s:I, s:o, s:o, s:s, s:o, s:o}", - "code", - (json_int_t) TALER_EC_TRACK_TRANSFER_CONFLICTING_REPORTS, - "hint", - "disagreement about deposit valuation", - "exchange_deposit_proof", exchange_proof, - "conflict_offset", - (json_int_t) rctx->current_offset, - "exchange_transfer_proof", - rctx->original_response, - "coin_pub", GNUNET_JSON_from_data_auto ( - coin_pub), - "h_contract_terms", - GNUNET_JSON_from_data_auto (&ttd->h_contract_terms), - "amount_with_fee", TALER_JSON_from_amount ( - amount_with_fee), - "deposit_fee", TALER_JSON_from_amount ( - deposit_fee)); + "{s:I, s:s, s:o, s:I, s:o, s:o, s:s, s:o, s:o}", + "code", + (json_int_t) TALER_EC_TRACK_TRANSFER_CONFLICTING_REPORTS, + "hint", + "disagreement about deposit valuation", + "exchange_deposit_proof", exchange_proof, + "conflict_offset", + (json_int_t) rctx->current_offset, + "exchange_transfer_proof", + rctx->original_response, + "coin_pub", GNUNET_JSON_from_data_auto ( + coin_pub), + "h_contract_terms", + GNUNET_JSON_from_data_auto (&ttd->h_contract_terms), + "amount_with_fee", TALER_JSON_from_amount ( + amount_with_fee), + "deposit_fee", TALER_JSON_from_amount ( + deposit_fee)); return; } rctx->check_transfer_result = GNUNET_OK; diff --git a/src/backenddb/0000.sql b/src/backenddb/0000.sql new file mode 100644 index 00000000..1483e201 --- /dev/null +++ b/src/backenddb/0000.sql @@ -0,0 +1,293 @@ +-- LICENSE AND COPYRIGHT +-- +-- Copyright (C) 2010 Hubert depesz Lubaczewski +-- +-- This program is distributed under the (Revised) BSD License: +-- L<http://www.opensource.org/licenses/bsd-license.php> +-- +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions +-- are met: +-- +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- +-- * Neither the name of Hubert depesz Lubaczewski's Organization +-- nor the names of its contributors may be used to endorse or +-- promote products derived from this software without specific +-- prior written permission. +-- +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +-- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-- +-- Code origin: https://gitlab.com/depesz/Versioning/blob/master/install.versioning.sql +-- +-- +-- # NAME +-- +-- **Versioning** - simplistic take on tracking and applying changes to databases. +-- +-- # DESCRIPTION +-- +-- This project strives to provide simple way to manage changes to +-- database. +-- +-- Instead of making changes on development server, then finding +-- differences between production and development, deciding which ones +-- should be installed on production, and finding a way to install them - +-- you start with writing diffs themselves! +-- +-- # INSTALLATION +-- +-- To install versioning simply run install.versioning.sql in your database +-- (all of them: production, stage, test, devel, ...). +-- +-- # USAGE +-- +-- In your files with patches to database, put whole logic in single +-- transaction, and use \_v.\* functions - usually \_v.register_patch() at +-- least to make sure everything is OK. +-- +-- For example. Let's assume you have patch files: +-- +-- ## 0001.sql: +-- +-- ``` +-- create table users (id serial primary key, username text); +-- ``` +-- +-- ## 0002.sql: +-- +-- ``` +-- insert into users (username) values ('depesz'); +-- ``` +-- To change it to use versioning you would change the files, to this +-- state: +-- +-- 0000.sql: +-- +-- ``` +-- BEGIN; +-- select _v.register_patch('000-base', NULL, NULL); +-- create table users (id serial primary key, username text); +-- COMMIT; +-- ``` +-- +-- ## 0002.sql: +-- +-- ``` +-- BEGIN; +-- select _v.register_patch('001-users', ARRAY['000-base'], NULL); +-- insert into users (username) values ('depesz'); +-- COMMIT; +-- ``` +-- +-- This will make sure that patch 001-users can only be applied after +-- 000-base. +-- +-- # AVAILABLE FUNCTIONS +-- +-- ## \_v.register_patch( TEXT ) +-- +-- Registers named patch, or dies if it is already registered. +-- +-- Returns integer which is id of patch in \_v.patches table - only if it +-- succeeded. +-- +-- ## \_v.register_patch( TEXT, TEXT[] ) +-- +-- Same as \_v.register_patch( TEXT ), but checks is all given patches (given as +-- array in second argument) are already registered. +-- +-- ## \_v.register_patch( TEXT, TEXT[], TEXT[] ) +-- +-- Same as \_v.register_patch( TEXT, TEXT[] ), but also checks if there are no conflicts with preexisting patches. +-- +-- Third argument is array of names of patches that conflict with current one. So +-- if any of them is installed - register_patch will error out. +-- +-- ## \_v.unregister_patch( TEXT ) +-- +-- Removes information about given patch from the versioning data. +-- +-- It doesn't remove objects that were created by this patch - just removes +-- metainformation. +-- +-- ## \_v.assert_user_is_superuser() +-- +-- Make sure that current patch is being loaded by superuser. +-- +-- If it's not - it will raise exception, and break transaction. +-- +-- ## \_v.assert_user_is_not_superuser() +-- +-- Make sure that current patch is not being loaded by superuser. +-- +-- If it is - it will raise exception, and break transaction. +-- +-- ## \_v.assert_user_is_one_of(TEXT, TEXT, ... ) +-- +-- Make sure that current patch is being loaded by one of listed users. +-- +-- If ```current_user``` is not listed as one of arguments - function will raise +-- exception and break the transaction. + +BEGIN; + +-- This file adds versioning support to database it will be loaded to. +-- It requires that PL/pgSQL is already loaded - will raise exception otherwise. +-- All versioning "stuff" (tables, functions) is in "_v" schema. + +-- All functions are defined as 'RETURNS SETOF INT4' to be able to make them to RETURN literaly nothing (0 rows). +-- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling. +CREATE SCHEMA IF NOT EXISTS _v; +COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.'; + +CREATE TABLE IF NOT EXISTS _v.patches ( + patch_name TEXT PRIMARY KEY, + applied_tsz TIMESTAMPTZ NOT NULL DEFAULT now(), + applied_by TEXT NOT NULL, + requires TEXT[], + conflicts TEXT[] +); +COMMENT ON TABLE _v.patches IS 'Contains information about what patches are currently applied on database.'; +COMMENT ON COLUMN _v.patches.patch_name IS 'Name of patch, has to be unique for every patch.'; +COMMENT ON COLUMN _v.patches.applied_tsz IS 'When the patch was applied.'; +COMMENT ON COLUMN _v.patches.applied_by IS 'Who applied this patch (PostgreSQL username)'; +COMMENT ON COLUMN _v.patches.requires IS 'List of patches that are required for given patch.'; +COMMENT ON COLUMN _v.patches.conflicts IS 'List of patches that conflict with given patch.'; + +CREATE OR REPLACE FUNCTION _v.register_patch( IN in_patch_name TEXT, IN in_requirements TEXT[], in_conflicts TEXT[], OUT versioning INT4 ) RETURNS setof INT4 AS $$ +DECLARE + t_text TEXT; + t_text_a TEXT[]; + i INT4; +BEGIN + -- Thanks to this we know only one patch will be applied at a time + LOCK TABLE _v.patches IN EXCLUSIVE MODE; + + SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name; + IF FOUND THEN + RAISE EXCEPTION 'Patch % is already applied!', in_patch_name; + END IF; + + t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE patch_name = any( in_conflicts ) ); + IF array_upper( t_text_a, 1 ) IS NOT NULL THEN + RAISE EXCEPTION 'Versioning patches conflict. Conflicting patche(s) installed: %.', array_to_string( t_text_a, ', ' ); + END IF; + + IF array_upper( in_requirements, 1 ) IS NOT NULL THEN + t_text_a := '{}'; + FOR i IN array_lower( in_requirements, 1 ) .. array_upper( in_requirements, 1 ) LOOP + SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_requirements[i]; + IF NOT FOUND THEN + t_text_a := t_text_a || in_requirements[i]; + END IF; + END LOOP; + IF array_upper( t_text_a, 1 ) IS NOT NULL THEN + RAISE EXCEPTION 'Missing prerequisite(s): %.', array_to_string( t_text_a, ', ' ); + END IF; + END IF; + + INSERT INTO _v.patches (patch_name, applied_tsz, applied_by, requires, conflicts ) VALUES ( in_patch_name, now(), current_user, coalesce( in_requirements, '{}' ), coalesce( in_conflicts, '{}' ) ); + RETURN; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[], TEXT[] ) IS 'Function to register patches in database. Raises exception if there are conflicts, prerequisites are not installed or the migration has already been installed.'; + +CREATE OR REPLACE FUNCTION _v.register_patch( TEXT, TEXT[] ) RETURNS setof INT4 AS $$ + SELECT _v.register_patch( $1, $2, NULL ); +$$ language sql; +COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[] ) IS 'Wrapper to allow registration of patches without conflicts.'; +CREATE OR REPLACE FUNCTION _v.register_patch( TEXT ) RETURNS setof INT4 AS $$ + SELECT _v.register_patch( $1, NULL, NULL ); +$$ language sql; +COMMENT ON FUNCTION _v.register_patch( TEXT ) IS 'Wrapper to allow registration of patches without requirements and conflicts.'; + +CREATE OR REPLACE FUNCTION _v.unregister_patch( IN in_patch_name TEXT, OUT versioning INT4 ) RETURNS setof INT4 AS $$ +DECLARE + i INT4; + t_text_a TEXT[]; +BEGIN + -- Thanks to this we know only one patch will be applied at a time + LOCK TABLE _v.patches IN EXCLUSIVE MODE; + + t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE in_patch_name = ANY( requires ) ); + IF array_upper( t_text_a, 1 ) IS NOT NULL THEN + RAISE EXCEPTION 'Cannot uninstall %, as it is required by: %.', in_patch_name, array_to_string( t_text_a, ', ' ); + END IF; + + DELETE FROM _v.patches WHERE patch_name = in_patch_name; + GET DIAGNOSTICS i = ROW_COUNT; + IF i < 1 THEN + RAISE EXCEPTION 'Patch % is not installed, so it can''t be uninstalled!', in_patch_name; + END IF; + + RETURN; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.unregister_patch( TEXT ) IS 'Function to unregister patches in database. Dies if the patch is not registered, or if unregistering it would break dependencies.'; + +CREATE OR REPLACE FUNCTION _v.assert_patch_is_applied( IN in_patch_name TEXT ) RETURNS TEXT as $$ +DECLARE + t_text TEXT; +BEGIN + SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name; + IF NOT FOUND THEN + RAISE EXCEPTION 'Patch % is not applied!', in_patch_name; + END IF; + RETURN format('Patch %s is applied.', in_patch_name); +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.assert_patch_is_applied( TEXT ) IS 'Function that can be used to make sure that patch has been applied.'; + +CREATE OR REPLACE FUNCTION _v.assert_user_is_superuser() RETURNS TEXT as $$ +DECLARE + v_super bool; +BEGIN + SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user; + IF v_super THEN + RETURN 'assert_user_is_superuser: OK'; + END IF; + RAISE EXCEPTION 'Current user is not superuser - cannot continue.'; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.assert_user_is_superuser() IS 'Function that can be used to make sure that patch is being applied using superuser account.'; + +CREATE OR REPLACE FUNCTION _v.assert_user_is_not_superuser() RETURNS TEXT as $$ +DECLARE + v_super bool; +BEGIN + SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user; + IF v_super THEN + RAISE EXCEPTION 'Current user is superuser - cannot continue.'; + END IF; + RETURN 'assert_user_is_not_superuser: OK'; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.assert_user_is_not_superuser() IS 'Function that can be used to make sure that patch is being applied using normal (not superuser) account.'; + +CREATE OR REPLACE FUNCTION _v.assert_user_is_one_of(VARIADIC p_acceptable_users TEXT[] ) RETURNS TEXT as $$ +DECLARE +BEGIN + IF current_user = any( p_acceptable_users ) THEN + RETURN 'assert_user_is_one_of: OK'; + END IF; + RAISE EXCEPTION 'User is not one of: % - cannot continue.', p_acceptable_users; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.assert_user_is_one_of(TEXT[]) IS 'Function that can be used to make sure that patch is being applied by one of defined users.'; + +COMMIT; diff --git a/src/backenddb/0001.sql b/src/backenddb/0001.sql new file mode 100644 index 00000000..98f20a26 --- /dev/null +++ b/src/backenddb/0001.sql @@ -0,0 +1,171 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2014--2020 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU 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 <http://www.gnu.org/licenses/> +-- + +-- Everything in one big transaction +BEGIN; + +-- Check patch versioning is in place. +SELECT _v.register_patch('merchant-0001', NULL, NULL); + + +CREATE TABLE IF NOT EXISTS merchant_orders + (order_id VARCHAR NOT NULL + ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) + ,contract_terms BYTEA NOT NULL + ,timestamp INT8 NOT NULL + ,PRIMARY KEY (order_id, merchant_pub) + ); + +-- Offers we made to customers +CREATE TABLE IF NOT EXISTS merchant_contract_terms + (order_id VARCHAR NOT NULL + ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) + ,contract_terms BYTEA NOT NULL + ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64) + ,timestamp INT8 NOT NULL + ,row_id BIGSERIAL UNIQUE + ,paid boolean DEFAULT FALSE NOT NULL + ,PRIMARY KEY (order_id, merchant_pub) + ,UNIQUE (h_contract_terms, merchant_pub) + ); + +-- Table with the proofs for each coin we deposited at the exchange +CREATE TABLE IF NOT EXISTS merchant_deposits + (h_contract_terms BYTEA NOT NULL + ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) + ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32) + ,exchange_url VARCHAR NOT NULL + ,amount_with_fee_val INT8 NOT NULL + ,amount_with_fee_frac INT4 NOT NULL + ,deposit_fee_val INT8 NOT NULL + ,deposit_fee_frac INT4 NOT NULL + ,refund_fee_val INT8 NOT NULL + ,refund_fee_frac INT4 NOT NULL + ,wire_fee_val INT8 NOT NULL + ,wire_fee_frac INT4 NOT NULL + ,signkey_pub BYTEA NOT NULL CHECK (LENGTH(signkey_pub)=32) + ,exchange_proof BYTEA NOT NULL + ,PRIMARY KEY (h_contract_terms, coin_pub) + ,FOREIGN KEY (h_contract_terms, merchant_pub) REFERENCES merchant_contract_terms (h_contract_terms, merchant_pub) + ); + +CREATE TABLE IF NOT EXISTS merchant_proofs + (exchange_url VARCHAR NOT NULL + ,wtid BYTEA CHECK (LENGTH(wtid)=32) + ,execution_time INT8 NOT NULL + ,signkey_pub BYTEA NOT NULL CHECK (LENGTH(signkey_pub)=32) + ,proof BYTEA NOT NULL + ,PRIMARY KEY (wtid, exchange_url) + ); + +-- Note that h_contract_terms + coin_pub may actually be unknown to +-- us, e.g. someone else deposits something for us at the exchange. +-- Hence those cannot be foreign keys into deposits/transactions! +CREATE TABLE IF NOT EXISTS merchant_transfers + (h_contract_terms BYTEA NOT NULL + ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32) + ,wtid BYTEA NOT NULL CHECK (LENGTH(wtid)=32) + ,PRIMARY KEY (h_contract_terms, coin_pub) + ); +CREATE INDEX IF NOT EXISTS merchant_transfers_by_coin + ON merchant_transfers + (h_contract_terms + ,coin_pub); +CREATE INDEX IF NOT EXISTS merchant_transfers_by_wtid + ON merchant_transfers + (wtid); + +CREATE TABLE IF NOT EXISTS exchange_wire_fees + (exchange_pub BYTEA NOT NULL CHECK (LENGTH(exchange_pub)=32) + ,h_wire_method BYTEA NOT NULL CHECK (LENGTH(h_wire_method)=64) + ,wire_fee_val INT8 NOT NULL + ,wire_fee_frac INT4 NOT NULL + ,closing_fee_val INT8 NOT NULL + ,closing_fee_frac INT4 NOT NULL + ,start_date INT8 NOT NULL + ,end_date INT8 NOT NULL + ,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64) + ,PRIMARY KEY (exchange_pub,h_wire_method,start_date,end_date) + ); + +CREATE TABLE IF NOT EXISTS merchant_refunds + (rtransaction_id BIGSERIAL UNIQUE + ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) + ,h_contract_terms BYTEA NOT NULL + ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32) + ,reason VARCHAR NOT NULL + ,refund_amount_val INT8 NOT NULL + ,refund_amount_frac INT4 NOT NULL + ,refund_fee_val INT8 NOT NULL + ,refund_fee_frac INT4 NOT NULL + ); + +-- balances of the reserves available for tips +CREATE TABLE IF NOT EXISTS merchant_tip_reserves + (reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32) + ,expiration INT8 NOT NULL + ,balance_val INT8 NOT NULL + ,balance_frac INT4 NOT NULL + ,PRIMARY KEY (reserve_priv) + ); + +-- table where we remember when tipping reserves where established / enabled +CREATE TABLE IF NOT EXISTS merchant_tip_reserve_credits + (reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32) + ,credit_uuid BYTEA UNIQUE NOT NULL CHECK (LENGTH(credit_uuid)=64) + ,timestamp INT8 NOT NULL + ,amount_val INT8 NOT NULL + ,amount_frac INT4 NOT NULL + ,PRIMARY KEY (credit_uuid) + ); + +-- tips that have been authorized +CREATE TABLE IF NOT EXISTS merchant_tips + (reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32) + ,tip_id BYTEA NOT NULL CHECK (LENGTH(tip_id)=64) + ,exchange_url VARCHAR NOT NULL + ,justification VARCHAR NOT NULL + ,extra BYTEA NOT NULL + ,timestamp INT8 NOT NULL + ,amount_val INT8 NOT NULL /* overall tip amount */ + ,amount_frac INT4 NOT NULL + ,left_val INT8 NOT NULL /* tip amount not yet picked up */ + ,left_frac INT4 NOT NULL + ,PRIMARY KEY (tip_id) + ); + +-- tips that have been picked up +CREATE TABLE IF NOT EXISTS merchant_tip_pickups + (tip_id BYTEA NOT NULL REFERENCES merchant_tips (tip_id) ON DELETE CASCADE + ,pickup_id BYTEA NOT NULL CHECK (LENGTH(pickup_id)=64) + ,amount_val INT8 NOT NULL + ,amount_frac INT4 NOT NULL + ,PRIMARY KEY (pickup_id) + ); + +-- sessions and their order_id/fulfillment_url mapping +CREATE TABLE IF NOT EXISTS merchant_session_info + (session_id VARCHAR NOT NULL + ,fulfillment_url VARCHAR NOT NULL + ,order_id VARCHAR NOT NULL + ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) + ,timestamp INT8 NOT NULL + ,PRIMARY KEY (session_id, fulfillment_url, merchant_pub) + ,UNIQUE (session_id, fulfillment_url, order_id, merchant_pub) + ); + +-- Complete transaction +COMMIT; diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am index e374403a..8f0479dd 100644 --- a/src/backenddb/Makefile.am +++ b/src/backenddb/Makefile.am @@ -3,6 +3,18 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/include plugindir = $(libdir)/taler +pkgcfgdir = $(prefix)/share/taler/config.d/ + +pkgcfg_DATA = \ + merchantdb-postgres.conf + +sqldir = $(prefix)/share/taler/sql/merchant/ + +sql_DATA = \ + 0000.sql \ + 0001.sql \ + drop0000.sql + if HAVE_POSTGRESQL if HAVE_GNUNETPQ plugin_LTLIBRARIES = \ @@ -68,4 +80,6 @@ TESTS = \ test-merchantdb-postgres EXTRA_DIST = \ - test-merchantdb-postgres.conf + test-merchantdb-postgres.conf \ + merchantdb-postgres.conf \ + $(sql_DATA) diff --git a/src/backenddb/drop0000.sql b/src/backenddb/drop0000.sql new file mode 100644 index 00000000..3d11bf75 --- /dev/null +++ b/src/backenddb/drop0000.sql @@ -0,0 +1,13 @@ +DROP TABLE IF EXISTS merchant_transfers CASCADE; +DROP TABLE IF EXISTS merchant_deposits CASCADE; +DROP TABLE IF EXISTS merchant_transactions CASCADE; +DROP TABLE IF EXISTS merchant_proofs CASCADE; +DROP TABLE IF EXISTS merchant_contract_terms CASCADE; +DROP TABLE IF EXISTS merchant_refunds CASCADE; +DROP TABLE IF EXISTS exchange_wire_fees CASCADE; +DROP TABLE IF EXISTS merchant_tips CASCADE; +DROP TABLE IF EXISTS merchant_tip_pickups CASCADE; +DROP TABLE IF EXISTS merchant_tip_reserve_credits CASCADE; +DROP TABLE IF EXISTS merchant_tip_reserves CASCADE; +DROP TABLE IF EXISTS merchant_orders CASCADE; +DROP TABLE IF EXISTS merchant_session_info CASCADE; diff --git a/src/backenddb/merchantdb-postgres.conf b/src/backenddb/merchantdb-postgres.conf new file mode 100644 index 00000000..db4763b9 --- /dev/null +++ b/src/backenddb/merchantdb-postgres.conf @@ -0,0 +1,6 @@ +[merchantdb-postgres] +CONFIG = "postgres:///talermerchant" + +# Where are the SQL files to setup our tables? +# Important: this MUST end with a "/"! +SQL_DIR = $DATADIR/sql/merchant/ diff --git a/src/backenddb/merchantdb_plugin.c b/src/backenddb/merchantdb_plugin.c index 5d82243e..473c6590 100644 --- a/src/backenddb/merchantdb_plugin.c +++ b/src/backenddb/merchantdb_plugin.c @@ -28,15 +28,14 @@ /** * Initialize the plugin. * - * @param cfg configuration to use + * @param[in,out] cfg configuration to use * @return #GNUNET_OK on success */ struct TALER_MERCHANTDB_Plugin * -TALER_MERCHANTDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg) +TALER_MERCHANTDB_plugin_load (struct GNUNET_CONFIGURATION_Handle *cfg) { char *plugin_name; char *lib_name; - struct GNUNET_CONFIGURATION_Handle *cfg_dup; struct TALER_MERCHANTDB_Plugin *plugin; if (GNUNET_SYSERR == @@ -54,13 +53,12 @@ TALER_MERCHANTDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg) "libtaler_plugin_merchantdb_%s", plugin_name); GNUNET_free (plugin_name); - cfg_dup = GNUNET_CONFIGURATION_dup (cfg); - plugin = GNUNET_PLUGIN_load (lib_name, cfg_dup); + plugin = GNUNET_PLUGIN_load (lib_name, + cfg); if (NULL != plugin) plugin->library_name = lib_name; else GNUNET_free (lib_name); - GNUNET_CONFIGURATION_destroy (cfg_dup); return plugin; } diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index 89f8d127..55c2fc10 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014--2019 Taler Systems SA + (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 @@ -85,6 +85,11 @@ struct PostgresClosure char *currency; /** + * Directory with SQL statements to run to create tables. + */ + char *sql_dir; + + /** * Underlying configuration. */ const struct GNUNET_CONFIGURATION_Handle *cfg; @@ -107,37 +112,16 @@ static int postgres_drop_tables (void *cls) { struct PostgresClosure *pg = cls; - struct GNUNET_PQ_ExecuteStatement es[] = { - GNUNET_PQ_make_try_execute ( - "DROP TABLE IF EXISTS merchant_transfers CASCADE;"), - GNUNET_PQ_make_try_execute ( - "DROP TABLE IF EXISTS merchant_deposits CASCADE;"), - GNUNET_PQ_make_try_execute ( - "DROP TABLE IF EXISTS merchant_transactions CASCADE;"), - GNUNET_PQ_make_try_execute ( - "DROP TABLE IF EXISTS merchant_proofs CASCADE;"), - GNUNET_PQ_make_try_execute ( - "DROP TABLE IF EXISTS merchant_contract_terms CASCADE;"), - GNUNET_PQ_make_try_execute ( - "DROP TABLE IF EXISTS merchant_refunds CASCADE;"), - GNUNET_PQ_make_try_execute ( - "DROP TABLE IF EXISTS exchange_wire_fees CASCADE;"), - GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS merchant_tips CASCADE;"), - GNUNET_PQ_make_try_execute ( - "DROP TABLE IF EXISTS merchant_tip_pickups CASCADE;"), - GNUNET_PQ_make_try_execute ( - "DROP TABLE IF EXISTS merchant_tip_reserve_credits CASCADE;"), - GNUNET_PQ_make_try_execute ( - "DROP TABLE IF EXISTS merchant_tip_reserves CASCADE;"), - GNUNET_PQ_make_try_execute ( - "DROP TABLE IF EXISTS merchant_orders CASCADE;"), - GNUNET_PQ_make_try_execute ( - "DROP TABLE IF EXISTS merchant_session_info CASCADE;"), - GNUNET_PQ_EXECUTE_STATEMENT_END - }; - - return GNUNET_PQ_exec_statements (pg->conn, - es); + 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; } @@ -3057,147 +3041,6 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) struct PostgresClosure *pg; struct TALER_MERCHANTDB_Plugin *plugin; const char *ec; - struct GNUNET_PQ_ExecuteStatement es[] = { - /* Orders created by the frontend, not signed or given a nonce yet. - The contract terms will change (nonce will be added) when moved to the - contract terms table */ - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_orders (" - "order_id VARCHAR NOT NULL" - ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)" - ",contract_terms BYTEA NOT NULL" - ",timestamp INT8 NOT NULL" - ",PRIMARY KEY (order_id, merchant_pub)" - ");"), - /* Offers we made to customers */ - GNUNET_PQ_make_execute ( - "CREATE TABLE IF NOT EXISTS merchant_contract_terms (" - "order_id VARCHAR NOT NULL" - ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)" - ",contract_terms BYTEA NOT NULL" - ",h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)" - ",timestamp INT8 NOT NULL" - ",row_id BIGSERIAL UNIQUE" - ",paid boolean DEFAULT FALSE NOT NULL" - ",PRIMARY KEY (order_id, merchant_pub)" - ",UNIQUE (h_contract_terms, merchant_pub)" - ");"), - /* Table with the proofs for each coin we deposited at the exchange */ - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_deposits (" - " h_contract_terms BYTEA NOT NULL" - ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)" - ",coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)" - ",exchange_url VARCHAR NOT NULL" - ",amount_with_fee_val INT8 NOT NULL" - ",amount_with_fee_frac INT4 NOT NULL" - ",deposit_fee_val INT8 NOT NULL" - ",deposit_fee_frac INT4 NOT NULL" - ",refund_fee_val INT8 NOT NULL" - ",refund_fee_frac INT4 NOT NULL" - ",wire_fee_val INT8 NOT NULL" - ",wire_fee_frac INT4 NOT NULL" - ",signkey_pub BYTEA NOT NULL CHECK (LENGTH(signkey_pub)=32)" - ",exchange_proof BYTEA NOT NULL" - ",PRIMARY KEY (h_contract_terms, coin_pub)" - ",FOREIGN KEY (h_contract_terms, merchant_pub) REFERENCES merchant_contract_terms (h_contract_terms, merchant_pub)" - ");"), - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_proofs (" - " exchange_url VARCHAR NOT NULL" - ",wtid BYTEA CHECK (LENGTH(wtid)=32)" - ",execution_time INT8 NOT NULL" - ",signkey_pub BYTEA NOT NULL CHECK (LENGTH(signkey_pub)=32)" - ",proof BYTEA NOT NULL" - ",PRIMARY KEY (wtid, exchange_url)" - ");"), - /* Note that h_contract_terms + coin_pub may actually be unknown to - us, e.g. someone else deposits something for us at the exchange. - Hence those cannot be foreign keys into deposits/transactions! */ - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_transfers (" - " h_contract_terms BYTEA NOT NULL" - ",coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)" - ",wtid BYTEA NOT NULL CHECK (LENGTH(wtid)=32)" - ",PRIMARY KEY (h_contract_terms, coin_pub)" - ");"), - GNUNET_PQ_make_try_execute ( - "CREATE INDEX IF NOT EXISTS merchant_transfers_by_coin" - " ON merchant_transfers (h_contract_terms, coin_pub)"), - GNUNET_PQ_make_try_execute ( - "CREATE INDEX IF NOT EXISTS merchant_transfers_by_wtid" - " ON merchant_transfers (wtid)"), - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS exchange_wire_fees (" - " exchange_pub BYTEA NOT NULL CHECK (length(exchange_pub)=32)" - ",h_wire_method BYTEA NOT NULL CHECK (length(h_wire_method)=64)" - ",wire_fee_val INT8 NOT NULL" - ",wire_fee_frac INT4 NOT NULL" - ",closing_fee_val INT8 NOT NULL" - ",closing_fee_frac INT4 NOT NULL" - ",start_date INT8 NOT NULL" - ",end_date INT8 NOT NULL" - ",exchange_sig BYTEA NOT NULL CHECK (length(exchange_sig)=64)" - ",PRIMARY KEY (exchange_pub,h_wire_method,start_date,end_date)" - ");"), - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_refunds (" - " rtransaction_id BIGSERIAL UNIQUE" - ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)" - ",h_contract_terms BYTEA NOT NULL" - ",coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)" - ",reason VARCHAR NOT NULL" - ",refund_amount_val INT8 NOT NULL" - ",refund_amount_frac INT4 NOT NULL" - ",refund_fee_val INT8 NOT NULL" - ",refund_fee_frac INT4 NOT NULL" - ");"), - /* balances of the reserves available for tips */ - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_tip_reserves (" - " reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32)" - ",expiration INT8 NOT NULL" - ",balance_val INT8 NOT NULL" - ",balance_frac INT4 NOT NULL" - ",PRIMARY KEY (reserve_priv)" - ");"), - /* table where we remember when tipping reserves where established / enabled */ - GNUNET_PQ_make_execute ( - "CREATE TABLE IF NOT EXISTS merchant_tip_reserve_credits (" - " reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32)" - ",credit_uuid BYTEA UNIQUE NOT NULL CHECK (LENGTH(credit_uuid)=64)" - ",timestamp INT8 NOT NULL" - ",amount_val INT8 NOT NULL" - ",amount_frac INT4 NOT NULL" - ",PRIMARY KEY (credit_uuid)" - ");"), - /* tips that have been authorized */ - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_tips (" - " reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32)" - ",tip_id BYTEA NOT NULL CHECK (LENGTH(tip_id)=64)" - ",exchange_url VARCHAR NOT NULL" - ",justification VARCHAR NOT NULL" - ",extra BYTEA NOT NULL" - ",timestamp INT8 NOT NULL" - ",amount_val INT8 NOT NULL" /* overall tip amount */ - ",amount_frac INT4 NOT NULL" - ",left_val INT8 NOT NULL" /* tip amount not yet picked up */ - ",left_frac INT4 NOT NULL" - ",PRIMARY KEY (tip_id)" - ");"), - /* tips that have been picked up */ - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_tip_pickups (" - " tip_id BYTEA NOT NULL REFERENCES merchant_tips (tip_id) ON DELETE CASCADE" - ",pickup_id BYTEA NOT NULL CHECK (LENGTH(pickup_id)=64)" - ",amount_val INT8 NOT NULL" - ",amount_frac INT4 NOT NULL" - ",PRIMARY KEY (pickup_id)" - ");"), - /* sessions and their order_id/fulfillment_url mapping */ - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_session_info (" - " session_id VARCHAR NOT NULL" - ",fulfillment_url VARCHAR NOT NULL" - ",order_id VARCHAR NOT NULL" - ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)" - ",timestamp INT8 NOT NULL" - ",PRIMARY KEY (session_id, fulfillment_url, merchant_pub)" - ",UNIQUE (session_id, fulfillment_url, order_id, merchant_pub)" - ");"), - GNUNET_PQ_EXECUTE_STATEMENT_END - }; struct GNUNET_PQ_PreparedStatement ps[] = { GNUNET_PQ_make_prepare ("insert_deposit", "INSERT INTO merchant_deposits" @@ -3635,6 +3478,19 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) }; 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; + } ec = getenv ("TALER_MERCHANTDB_POSTGRES_CONFIG"); if (NULL != ec) { @@ -3653,16 +3509,19 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "merchantdb-postgres", "CONFIG"); + GNUNET_free (pg->sql_dir); + GNUNET_free (pg); return NULL; } } - pg->cfg = cfg; pg->conn = GNUNET_PQ_connect_with_cfg (cfg, "merchantdb-postgres", - es, + "", + NULL, ps); if (NULL == pg->conn) { + GNUNET_free (pg->sql_dir); GNUNET_free (pg); return NULL; } @@ -3676,6 +3535,7 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) "taler", "CURRENCY"); GNUNET_PQ_disconnect (pg->conn); + GNUNET_free (pg->sql_dir); GNUNET_free (pg); return NULL; } diff --git a/src/backenddb/test-merchantdb-postgres.conf b/src/backenddb/test-merchantdb-postgres.conf index eccf4213..03bbc35a 100644 --- a/src/backenddb/test-merchantdb-postgres.conf +++ b/src/backenddb/test-merchantdb-postgres.conf @@ -4,5 +4,9 @@ DB = postgres [merchantdb-postgres] CONFIG = postgres:///talercheck +# Where are the SQL files to setup our tables? +# Important: this MUST end with a "/"! +SQL_DIR = $DATADIR/sql/merchant/ + [taler] CURRENCY = "EUR"
\ No newline at end of file diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c index 99f64344..f7baf1ef 100644 --- a/src/backenddb/test_merchantdb.c +++ b/src/backenddb/test_merchantdb.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014-2017 INRIA + (C) 2014-2017 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 @@ -1065,7 +1065,8 @@ main (int argc, GNUNET_free (testname); return 2; } - GNUNET_SCHEDULER_run (&run, cfg); + GNUNET_SCHEDULER_run (&run, + cfg); GNUNET_CONFIGURATION_destroy (cfg); GNUNET_free (config_filename); GNUNET_free (testname); diff --git a/src/include/taler_merchantdb_lib.h b/src/include/taler_merchantdb_lib.h index ad8d2699..eba702ba 100644 --- a/src/include/taler_merchantdb_lib.h +++ b/src/include/taler_merchantdb_lib.h @@ -33,11 +33,11 @@ struct TALER_MERCHANTDB_Plugin; /** * Connect to postgresql database * - * @param cfg the configuration handle + * @param[in,out] cfg the configuration handle * @return connection to the database; NULL upon error */ struct TALER_MERCHANTDB_Plugin * -TALER_MERCHANTDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg); +TALER_MERCHANTDB_plugin_load (struct GNUNET_CONFIGURATION_Handle *cfg); /** diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c index 26371996..3af1ab92 100644 --- a/src/lib/merchant_api_pay.c +++ b/src/lib/merchant_api_pay.c @@ -338,6 +338,7 @@ handle_pay_finished (void *cls, { struct TALER_MERCHANT_Pay *ph = cls; const json_t *json = response; + enum TALER_ErrorCode ec; ph->job = NULL; GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -349,19 +350,25 @@ handle_pay_finished (void *cls, switch (response_code) { case 0: + ec = TALER_JSON_get_error_code (json); break; case MHD_HTTP_OK: + ec = TALER_EC_NONE; + break; /* Tolerating Not Acceptable because sometimes * - especially in tests - we might want to POST * coins one at a time. */ case MHD_HTTP_NOT_ACCEPTABLE: + ec = TALER_JSON_get_error_code (json); break; case MHD_HTTP_BAD_REQUEST: + ec = TALER_JSON_get_error_code (json); /* This should never happen, either us * or the merchant is buggy (or API version conflict); * just pass JSON reply to the application */ break; case MHD_HTTP_CONFLICT: + ec = TALER_JSON_get_error_code (json); if (GNUNET_OK != check_conflict (ph, json)) { @@ -370,25 +377,30 @@ handle_pay_finished (void *cls, } break; case MHD_HTTP_FORBIDDEN: + ec = TALER_JSON_get_error_code (json); /* Nothing really to verify, merchant says one of the * signatures is invalid; as we checked them, this * should never happen, we should pass the JSON reply * to the application */ break; case MHD_HTTP_NOT_FOUND: + ec = TALER_JSON_get_error_code (json); /* Nothing really to verify, this should never happen, we should pass the JSON reply to the application */ break; case MHD_HTTP_INTERNAL_SERVER_ERROR: + ec = TALER_JSON_get_error_code (json); /* Server had an internal issue; we should retry, but this API leaves this to the application */ break; case MHD_HTTP_SERVICE_UNAVAILABLE: + ec = TALER_JSON_get_error_code (json); /* Exchange couldn't respond properly; the retry is left to the application */ break; default: + ec = TALER_JSON_get_error_code (json); /* unexpected response code */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u\n", @@ -399,7 +411,7 @@ handle_pay_finished (void *cls, } ph->pay_cb (ph->pay_cb_cls, response_code, - TALER_JSON_get_error_code (json), + ec, json); } else @@ -410,6 +422,7 @@ handle_pay_finished (void *cls, switch (response_code) { case 0: + ec = TALER_JSON_get_error_code (json); break; case MHD_HTTP_OK: if (GNUNET_OK == @@ -420,30 +433,37 @@ handle_pay_finished (void *cls, return; } response_code = 0; + ec = TALER_EC_NONE; break; case MHD_HTTP_BAD_REQUEST: + ec = TALER_JSON_get_error_code (json); /* This should never happen, either us or the merchant is buggy (or API version conflict); just pass JSON reply to the application */ break; case MHD_HTTP_CONFLICT: + ec = TALER_JSON_get_error_code (json); break; case MHD_HTTP_FORBIDDEN: + ec = TALER_JSON_get_error_code (json); /* Nothing really to verify, merchant says one of the signatures is invalid; as we checked them, this should never happen, we should pass the JSON reply to the application */ break; case MHD_HTTP_NOT_FOUND: + ec = TALER_JSON_get_error_code (json); /* Nothing really to verify, this should never happen, we should pass the JSON reply to the application */ break; case MHD_HTTP_INTERNAL_SERVER_ERROR: + ec = TALER_JSON_get_error_code (json); /* Server had an internal issue; we should retry, but this API leaves this to the application */ break; default: + ec = TALER_JSON_get_error_code (json); /* unexpected response code */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u\n", @@ -454,14 +474,13 @@ handle_pay_finished (void *cls, } ph->abort_cb (ph->abort_cb_cls, response_code, - TALER_JSON_get_error_code (json), + ec, NULL, NULL, 0, NULL, json); } - TALER_MERCHANT_pay_cancel (ph); } diff --git a/src/lib/merchant_api_proposal.c b/src/lib/merchant_api_proposal.c index 0909c1d4..73138ad3 100644 --- a/src/lib/merchant_api_proposal.c +++ b/src/lib/merchant_api_proposal.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2017 GNUnet e.V. and INRIA + Copyright (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 @@ -131,7 +131,7 @@ handle_proposal_finished (void *cls, struct TALER_MERCHANT_ProposalOperation *po = cls; const char *order_id; const json_t *json = response; - + enum TALER_ErrorCode ec; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("order_id", &order_id), @@ -158,30 +158,40 @@ handle_proposal_finished (void *cls, { GNUNET_break_op (0); response_code = 0; + ec = TALER_JSON_get_error_code (json); break; } + else + { + ec = TALER_EC_NONE; + } } break; case MHD_HTTP_BAD_REQUEST: + ec = TALER_JSON_get_error_code (json); /* This should never happen, either us or the merchant is buggy (or API version conflict); just pass JSON reply to the application */ break; case MHD_HTTP_CONFLICT: + ec = TALER_JSON_get_error_code (json); break; case MHD_HTTP_FORBIDDEN: /* Nothing really to verify, merchant says one of the signatures is invalid; as we checked them, this should never happen, we should pass the JSON reply to the application */ + ec = TALER_JSON_get_error_code (json); break; case MHD_HTTP_NOT_FOUND: /* Nothing really to verify, this should never happen, we should pass the JSON reply to the application */ + ec = TALER_JSON_get_error_code (json); break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ + ec = TALER_JSON_get_error_code (json); break; default: /* unexpected response code */ @@ -190,10 +200,12 @@ handle_proposal_finished (void *cls, (unsigned int) response_code); GNUNET_break (0); response_code = 0; + ec = TALER_JSON_get_error_code (json); + break; } po->cb (po->cb_cls, response_code, - TALER_JSON_get_error_code (json), + ec, json, order_id); GNUNET_JSON_parse_free (spec); @@ -290,7 +302,6 @@ handle_proposal_lookup_finished (void *cls, "Proposal lookup failed with HTTP status code %u\n", (unsigned int) response_code); GNUNET_break (0); - plo->cb (plo->cb_cls, response_code, json, diff --git a/src/lib/merchant_api_refund.c b/src/lib/merchant_api_refund.c index 9d8187f7..9cf4d5f5 100644 --- a/src/lib/merchant_api_refund.c +++ b/src/lib/merchant_api_refund.c @@ -108,6 +108,11 @@ handle_refund_lookup_finished (void *cls, NULL); break; case MHD_HTTP_OK: + rlo->cb (rlo->cb_cls, + response_code, + TALER_EC_NONE, + json); + break; case MHD_HTTP_NOT_FOUND: rlo->cb (rlo->cb_cls, response_code, diff --git a/src/lib/merchant_api_refund_increase.c b/src/lib/merchant_api_refund_increase.c index 7464502c..3adb9881 100644 --- a/src/lib/merchant_api_refund_increase.c +++ b/src/lib/merchant_api_refund_increase.c @@ -91,6 +91,11 @@ handle_refund_increase_finished (void *cls, NULL); break; case MHD_HTTP_OK: + rio->cb (rio->cb_cls, + response_code, + TALER_EC_NONE, + json); + break; case MHD_HTTP_CONFLICT: case MHD_HTTP_NOT_FOUND: rio->cb (rio->cb_cls, diff --git a/src/lib/merchant_api_tip_pickup.c b/src/lib/merchant_api_tip_pickup.c index 683c7d04..668eb4bc 100644 --- a/src/lib/merchant_api_tip_pickup.c +++ b/src/lib/merchant_api_tip_pickup.c @@ -136,7 +136,7 @@ check_ok (struct TALER_MERCHANT_TipPickupOperation *tpo, } tpo->cb (tpo->cb_cls, MHD_HTTP_OK, - TALER_JSON_get_error_code (json), + TALER_EC_NONE, &reserve_pub, ja_len, reserve_sigs, diff --git a/src/lib/merchant_api_tip_query.c b/src/lib/merchant_api_tip_query.c index d2a98a99..65aece65 100644 --- a/src/lib/merchant_api_tip_query.c +++ b/src/lib/merchant_api_tip_query.c @@ -109,7 +109,7 @@ check_ok (struct TALER_MERCHANT_TipQueryOperation *tqo, } tqo->cb (tqo->cb_cls, MHD_HTTP_OK, - TALER_JSON_get_error_code (json), + TALER_EC_NONE, json, reserve_expiration, &reserve_pub, diff --git a/src/merchant-tools/taler-merchant-dbinit.c b/src/merchant-tools/taler-merchant-dbinit.c index b5e85ee6..d5f2da6b 100644 --- a/src/merchant-tools/taler-merchant-dbinit.c +++ b/src/merchant-tools/taler-merchant-dbinit.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015 GNUnet e.V. + Copyright (C) 2014, 2015 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -41,22 +41,25 @@ static int reset_db; * @param cls closure * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be NULL!) - * @param cfg configuration + * @param config configuration */ static void run (void *cls, char *const *args, const char *cfgfile, - const struct GNUNET_CONFIGURATION_Handle *cfg) + const struct GNUNET_CONFIGURATION_Handle *config) { struct TALER_MERCHANTDB_Plugin *plugin; + struct GNUNET_CONFIGURATION_Handle *cfg; + cfg = GNUNET_CONFIGURATION_dup (config); if (NULL == (plugin = TALER_MERCHANTDB_plugin_load (cfg))) { fprintf (stderr, "Failed to initialize database plugin.\n"); global_ret = 1; + GNUNET_CONFIGURATION_destroy (cfg); return; } if (reset_db) @@ -66,6 +69,7 @@ run (void *cls, plugin = TALER_MERCHANTDB_plugin_load (cfg); } TALER_MERCHANTDB_plugin_unload (plugin); + GNUNET_CONFIGURATION_destroy (cfg); } |