diff options
Diffstat (limited to 'src/backenddb')
37 files changed, 2616 insertions, 2022 deletions
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am index 240b1cc1..3d7d7827 100644 --- a/src/backenddb/Makefile.am +++ b/src/backenddb/Makefile.am @@ -56,6 +56,15 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \ pg_select_wirewatch_accounts.h pg_select_wirewatch_accounts.c \ pg_insert_account.h pg_insert_account.c \ pg_update_account.h pg_update_account.c \ + pg_insert_deposit_to_transfer.h pg_insert_deposit_to_transfer.c \ + pg_increase_refund.h pg_increase_refund.c \ + pg_insert_transfer.h pg_insert_transfer.c \ + pg_insert_transfer_details.h pg_insert_transfer_details.c \ + pg_store_wire_fee_by_exchange.h pg_store_wire_fee_by_exchange.c \ + pg_insert_reserve.h pg_insert_reserve.c \ + pg_activate_reserve.h pg_activate_reserve.c \ + pg_authorize_reward.h pg_authorize_reward.c \ + pg_insert_pickup.h pg_insert_pickup.c \ pg_select_open_transfers.h pg_select_open_transfers.c \ pg_lookup_instances.h pg_lookup_instances.c \ pg_lookup_transfers.h pg_lookup_transfers.c \ @@ -105,7 +114,8 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \ pg_lookup_order_status.h pg_lookup_order_status.c \ pg_lookup_order_status_by_serial.h pg_lookup_order_status_by_serial.c \ pg_lookup_payment_status.h pg_lookup_payment_status.c \ - plugin_merchantdb_postgres.c pg_helper.h + plugin_merchantdb_postgres.c \ + pg_helper.h pg_helper.c libtaler_plugin_merchantdb_postgres_la_LIBADD = \ $(LTLIBINTL) libtaler_plugin_merchantdb_postgres_la_LDFLAGS = \ diff --git a/src/backenddb/merchant-0001.sql b/src/backenddb/merchant-0001.sql index 7bd8d737..e959a071 100644 --- a/src/backenddb/merchant-0001.sql +++ b/src/backenddb/merchant-0001.sql @@ -30,6 +30,14 @@ COMMENT ON SCHEMA merchant IS 'taler-merchant data'; SET search_path TO merchant; +CREATE TYPE taler_amount + AS + (val INT8 + ,frac INT4 + ); +COMMENT ON TYPE taler_amount + IS 'Stores an amount, fraction is in units of 1/100000000 of the base value'; + ---------------- Exchange information --------------------------- CREATE TABLE IF NOT EXISTS merchant_exchange_wire_fees @@ -38,10 +46,8 @@ CREATE TABLE IF NOT EXISTS merchant_exchange_wire_fees ,h_wire_method BYTEA NOT NULL CHECK (LENGTH(h_wire_method)=64) ,start_date INT8 NOT NULL ,end_date INT8 NOT NULL - ,wire_fee_val INT8 NOT NULL - ,wire_fee_frac INT4 NOT NULL - ,closing_fee_val INT8 NOT NULL - ,closing_fee_frac INT4 NOT NULL + ,wire_fee taler_amount NOT NULL + ,closing_fee taler_amount NOT NULL ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) ,UNIQUE (master_pub,h_wire_method,start_date) ); @@ -80,10 +86,8 @@ CREATE TABLE IF NOT EXISTS merchant_instances ,logo BYTEA ,address BYTEA NOT NULL ,jurisdiction BYTEA NOT NULL - ,default_max_deposit_fee_val INT8 NOT NULL - ,default_max_deposit_fee_frac INT4 NOT NULL - ,default_max_wire_fee_val INT8 NOT NULL - ,default_max_wire_fee_frac INT4 NOT NULL + ,default_max_deposit_fee taler_amount NOT NULL + ,default_max_wire_fee taler_amount NOT NULL ,default_wire_fee_amortization INT4 NOT NULL ,default_wire_transfer_delay INT8 NOT NULL ,default_pay_delay INT8 NOT NULL @@ -166,8 +170,7 @@ CREATE TABLE IF NOT EXISTS merchant_inventory ,unit VARCHAR NOT NULL ,image BYTEA NOT NULL ,taxes BYTEA NOT NULL - ,price_val INT8 NOT NULL - ,price_frac INT4 NOT NULL + ,price taler_amount NOT NULL ,total_stock BIGINT NOT NULL ,total_sold BIGINT NOT NULL DEFAULT 0 ,total_lost BIGINT NOT NULL DEFAULT 0 @@ -188,7 +191,7 @@ COMMENT ON COLUMN merchant_inventory.image IS 'NOT NULL, but can be 0 bytes; must contain an ImageDataUrl'; COMMENT ON COLUMN merchant_inventory.taxes IS 'JSON array containing taxes the merchant pays, must be JSON, but can be just "[]"'; -COMMENT ON COLUMN merchant_inventory.price_val +COMMENT ON COLUMN merchant_inventory.price IS 'Current price of one unit of the product'; COMMENT ON COLUMN merchant_inventory.total_stock IS 'A value of -1 is used for unlimited (electronic good), may never be lowered'; @@ -358,14 +361,10 @@ CREATE TABLE IF NOT EXISTS merchant_deposits ,deposit_timestamp INT8 NOT NULL ,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 + ,amount_with_fee taler_amount NOT NULL + ,deposit_fee taler_amount NOT NULL + ,refund_fee taler_amount NOT NULL + ,wire_fee taler_amount NOT NULL ,signkey_serial BIGINT NOT NULL REFERENCES merchant_exchange_signing_keys (signkey_serial) ON DELETE CASCADE ,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64) @@ -381,7 +380,7 @@ COMMENT ON COLUMN merchant_deposits.deposit_timestamp IS 'Time when the exchange generated the deposit confirmation'; COMMENT ON COLUMN merchant_deposits.exchange_sig IS 'Signature of the exchange over the deposit confirmation'; -COMMENT ON COLUMN merchant_deposits.wire_fee_val +COMMENT ON COLUMN merchant_deposits.wire_fee IS 'We MAY want to see if we should try to get this via merchant_exchange_wire_fees (not sure, may be too complicated with the date range, etc.)'; CREATE TABLE IF NOT EXISTS merchant_refunds @@ -392,8 +391,7 @@ CREATE TABLE IF NOT EXISTS merchant_refunds ,refund_timestamp INT8 NOT NULL ,coin_pub BYTEA NOT NULL ,reason VARCHAR NOT NULL - ,refund_amount_val INT8 NOT NULL - ,refund_amount_frac INT4 NOT NULL + ,refund_amount taler_amount NOT NULL ,UNIQUE (order_serial, coin_pub, rtransaction_id) ); COMMENT ON TABLE merchant_deposits @@ -422,8 +420,7 @@ CREATE TABLE IF NOT EXISTS merchant_transfers (credit_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY ,exchange_url VARCHAR NOT NULL ,wtid BYTEA CHECK (LENGTH(wtid)=32) - ,credit_amount_val INT8 NOT NULL - ,credit_amount_frac INT4 NOT NULL + ,credit_amount taler_amount NOT NULL ,account_serial INT8 NOT NULL REFERENCES merchant_accounts (account_serial) ON DELETE CASCADE ,ready_time INT8 NOT NULL DEFAULT (0) @@ -439,7 +436,7 @@ COMMENT ON COLUMN merchant_transfers.verified IS 'true once we got an acceptable response from the exchange for this transfer'; COMMENT ON COLUMN merchant_transfers.confirmed IS 'true once the merchant confirmed that this transfer was received'; -COMMENT ON COLUMN merchant_transfers.credit_amount_val +COMMENT ON COLUMN merchant_transfers.credit_amount IS 'actual value of the (aggregated) wire transfer, excluding the wire fee, according to the merchant'; COMMENT ON COLUMN merchant_transfers.failed IS 'set to true on permanent verification failures'; @@ -459,10 +456,8 @@ CREATE TABLE IF NOT EXISTS merchant_transfer_signatures REFERENCES merchant_transfers (credit_serial) ON DELETE CASCADE ,signkey_serial BIGINT NOT NULL REFERENCES merchant_exchange_signing_keys (signkey_serial) ON DELETE CASCADE - ,wire_fee_val INT8 NOT NULL - ,wire_fee_frac INT4 NOT NULL - ,credit_amount_val INT8 NOT NULL - ,credit_amount_frac INT4 NOT NULL + ,wire_fee taler_amount NOT NULL + ,credit_amount taler_amount NOT NULL ,execution_time INT8 NOT NULL ,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64) ); @@ -470,7 +465,7 @@ COMMENT ON TABLE merchant_transfer_signatures IS 'table represents the main information returned from the /transfer request to the exchange.'; COMMENT ON COLUMN merchant_transfer_signatures.execution_time IS 'Execution time as claimed by the exchange, roughly matches time seen by merchant'; -COMMENT ON COLUMN merchant_transfer_signatures.credit_amount_val +COMMENT ON COLUMN merchant_transfer_signatures.credit_amount IS 'actual value of the (aggregated) wire transfer, excluding the wire fee, according to the exchange'; @@ -480,26 +475,23 @@ CREATE TABLE IF NOT EXISTS merchant_transfer_to_coin ,credit_serial BIGINT NOT NULL REFERENCES merchant_transfers (credit_serial) ON DELETE CASCADE ,offset_in_exchange_list INT8 NOT NULL - ,exchange_deposit_value_val INT8 NOT NULL - ,exchange_deposit_value_frac INT4 NOT NULL - ,exchange_deposit_fee_val INT8 NOT NULL - ,exchange_deposit_fee_frac INT4 NOT NULL + ,exchange_deposit_value taler_amount NOT NULL + ,exchange_deposit_fee taler_amount NOT NULL ); CREATE INDEX IF NOT EXISTS merchant_transfers_by_credit ON merchant_transfer_to_coin (credit_serial); COMMENT ON TABLE merchant_transfer_to_coin IS 'Mapping of (credit) transfers to (deposited) coins'; -COMMENT ON COLUMN merchant_transfer_to_coin.exchange_deposit_value_val +COMMENT ON COLUMN merchant_transfer_to_coin.exchange_deposit_value IS 'Deposit value as claimed by the exchange, should match our values in merchant_deposits minus refunds'; -COMMENT ON COLUMN merchant_transfer_to_coin.exchange_deposit_fee_val +COMMENT ON COLUMN merchant_transfer_to_coin.exchange_deposit_fee IS 'Deposit value as claimed by the exchange, should match our values in merchant_deposits'; CREATE TABLE IF NOT EXISTS merchant_deposit_to_transfer (deposit_serial BIGINT NOT NULL REFERENCES merchant_deposits (deposit_serial) ON DELETE CASCADE - ,coin_contribution_value_val INT8 NOT NULL - ,coin_contribution_value_frac INT4 NOT NULL + ,coin_contribution_value taler_amount NOT NULL ,credit_serial BIGINT NOT NULL REFERENCES merchant_transfers (credit_serial) ,execution_time INT8 NOT NULL @@ -523,26 +515,22 @@ CREATE TABLE IF NOT EXISTS merchant_reward_reserves REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE ,creation_time INT8 NOT NULL ,expiration INT8 NOT NULL - ,merchant_initial_balance_val INT8 NOT NULL - ,merchant_initial_balance_frac INT4 NOT NULL - ,exchange_initial_balance_val INT8 NOT NULL DEFAULT 0 - ,exchange_initial_balance_frac INT4 NOT NULL DEFAULT 0 - ,rewards_committed_val INT8 NOT NULL DEFAULT 0 - ,rewards_committed_frac INT4 NOT NULL DEFAULT 0 - ,rewards_picked_up_val INT8 NOT NULL DEFAULT 0 - ,rewards_picked_up_frac INT4 NOT NULL DEFAULT 0 + ,merchant_initial_balance taler_amount NOT NULL + ,exchange_initial_balance taler_amount NOT NULL DEFAULT (0,0) + ,rewards_committed taler_amount NOT NULL DEFAULT (0,0) + ,rewards_picked_up taler_amount NOT NULL DEFAULT (0,0) ); COMMENT ON TABLE merchant_reward_reserves IS 'balances of the reserves available for rewards'; COMMENT ON COLUMN merchant_reward_reserves.expiration IS 'FIXME: EXCHANGE API needs to tell us when reserves close if we are to compute this'; -COMMENT ON COLUMN merchant_reward_reserves.merchant_initial_balance_val +COMMENT ON COLUMN merchant_reward_reserves.merchant_initial_balance IS 'Set to the initial balance the merchant told us when creating the reserve'; -COMMENT ON COLUMN merchant_reward_reserves.exchange_initial_balance_val +COMMENT ON COLUMN merchant_reward_reserves.exchange_initial_balance IS 'Set to the initial balance the exchange told us when we queried the reserve status'; -COMMENT ON COLUMN merchant_reward_reserves.rewards_committed_val +COMMENT ON COLUMN merchant_reward_reserves.rewards_committed IS 'Amount of outstanding approved rewards that have not been picked up'; -COMMENT ON COLUMN merchant_reward_reserves.rewards_picked_up_val +COMMENT ON COLUMN merchant_reward_reserves.rewards_picked_up IS 'Total amount rewards that have been picked up from this reserve'; CREATE INDEX IF NOT EXISTS merchant_reward_reserves_by_reserve_pub_and_merchant_serial @@ -553,7 +541,7 @@ CREATE INDEX IF NOT EXISTS merchant_reward_reserves_by_merchant_serial_and_creat (merchant_serial,creation_time); CREATE INDEX IF NOT EXISTS merchant_reward_reserves_by_exchange_balance ON merchant_reward_reserves - (exchange_initial_balance_val,exchange_initial_balance_frac); + (exchange_initial_balance); @@ -578,10 +566,8 @@ CREATE TABLE IF NOT EXISTS merchant_rewards ,justification VARCHAR NOT NULL ,next_url VARCHAR NOT NULL ,expiration INT8 NOT NULL - ,amount_val INT8 NOT NULL - ,amount_frac INT4 NOT NULL - ,picked_up_val INT8 NOT NULL DEFAULT 0 - ,picked_up_frac INT4 NOT NULL DEFAULT 0 + ,amount taler_amount NOT NULL + ,picked_up taler_amount NOT NULL DEFAULT (0, 0) ,was_picked_up BOOLEAN NOT NULL DEFAULT FALSE ); CREATE INDEX IF NOT EXISTS merchant_rewards_by_pickup_and_expiration @@ -589,9 +575,9 @@ CREATE INDEX IF NOT EXISTS merchant_rewards_by_pickup_and_expiration (was_picked_up,expiration); COMMENT ON TABLE merchant_rewards IS 'rewards that have been authorized'; -COMMENT ON COLUMN merchant_rewards.amount_val +COMMENT ON COLUMN merchant_rewards.amount IS 'Overall reward amount'; -COMMENT ON COLUMN merchant_rewards.picked_up_val +COMMENT ON COLUMN merchant_rewards.picked_up IS 'Reward amount left to be picked up'; COMMENT ON COLUMN merchant_rewards.reserve_serial IS 'Reserve from which this reward is funded'; @@ -603,12 +589,11 @@ CREATE TABLE IF NOT EXISTS merchant_reward_pickups ,reward_serial BIGINT NOT NULL REFERENCES merchant_rewards (reward_serial) ON DELETE CASCADE ,pickup_id BYTEA NOT NULL UNIQUE CHECK (LENGTH(pickup_id)=64) - ,amount_val INT8 NOT NULL - ,amount_frac INT4 NOT NULL + ,amount taler_amount NOT NULL ); COMMENT ON TABLE merchant_reward_pickups IS 'rewards that have been picked up'; -COMMENT ON COLUMN merchant_rewards.amount_val +COMMENT ON COLUMN merchant_rewards.amount IS 'total transaction cost for all coins including withdraw fees'; CREATE TABLE IF NOT EXISTS merchant_reward_pickup_signatures diff --git a/src/backenddb/pg_activate_reserve.c b/src/backenddb/pg_activate_reserve.c new file mode 100644 index 00000000..14a945d5 --- /dev/null +++ b/src/backenddb/pg_activate_reserve.c @@ -0,0 +1,56 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_activate_reserve.c + * @brief Implementation of the activate_reserve function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_activate_reserve.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TMH_PG_activate_reserve (void *cls, + const char *instance_id, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *initial_exchange_balance) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (reserve_pub), + TALER_PQ_query_param_amount_tuple (pg->conn, + initial_exchange_balance), + GNUNET_PQ_query_param_end + }; + + PREPARE (pg, + "activate_reserve", + "UPDATE merchant_reward_reserves SET" + " exchange_initial_balance=$3" + " WHERE reserve_pub=$2" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "activate_reserve", + params); +} diff --git a/src/backenddb/pg_activate_reserve.h b/src/backenddb/pg_activate_reserve.h new file mode 100644 index 00000000..3b7c8a3d --- /dev/null +++ b/src/backenddb/pg_activate_reserve.h @@ -0,0 +1,48 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_activate_reserve.h + * @brief implementation of the activate_reserve function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_ACTIVATE_RESERVE_H +#define PG_ACTIVATE_RESERVE_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + + +/** + * Confirms @a credit as the amount the exchange claims to have received and + * thus really 'activates' the reserve. This has to happen before rewards can + * be authorized. + * + * @param cls closure, typically a connection to the db + * @param instance_id which instance is the reserve tied to + * @param reserve_pub which reserve is topped up or created + * @param initial_exchange_balance how much money was be added to the reserve + * according to the exchange + * @return transaction status, usually + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success + */ +enum GNUNET_DB_QueryStatus +TMH_PG_activate_reserve (void *cls, + const char *instance_id, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *initial_exchange_balance); + +#endif diff --git a/src/backenddb/pg_authorize_reward.c b/src/backenddb/pg_authorize_reward.c new file mode 100644 index 00000000..771796e3 --- /dev/null +++ b/src/backenddb/pg_authorize_reward.c @@ -0,0 +1,422 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_authorize_reward.c + * @brief Implementation of the authorize_reward function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_authorize_reward.h" +#include "pg_helper.h" + + +/** + * How often do we re-try if we run into a DB serialization error? + */ +#define MAX_RETRIES 3 + + +/** + * Closure for #lookup_reserve_for_reward_cb(). + */ +struct LookupReserveForRewardContext +{ + /** + * Postgres context. + */ + struct PostgresClosure *pg; + + /** + * Public key of the reserve we found. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * How much money must be left in the reserve. + */ + struct TALER_Amount required_amount; + + /** + * Set to the expiration time of the reserve we found. + * #GNUNET_TIME_UNIT_FOREVER_ABS if we found none. + */ + struct GNUNET_TIME_Timestamp expiration; + + /** + * Error status. + */ + enum TALER_ErrorCode ec; + + /** + * Did we find a good reserve? + */ + bool ok; +}; + + +/** + * How long must a reserve be at least still valid before we use + * it for a reward? + */ +#define MIN_EXPIRATION GNUNET_TIME_UNIT_HOURS + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results about accounts. + * + * @param[in,out] cls of type `struct LookupReserveForRewardContext *` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +lookup_reserve_for_reward_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct LookupReserveForRewardContext *lac = cls; + struct PostgresClosure *pg = lac->pg; + + for (unsigned int i = 0; i < num_results; i++) + { + struct TALER_ReservePublicKeyP reserve_pub; + struct TALER_Amount committed_amount; + struct TALER_Amount remaining; + struct TALER_Amount initial_balance; + struct GNUNET_TIME_Timestamp expiration; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("reserve_pub", + &reserve_pub), + TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance", + &initial_balance), + TALER_PQ_RESULT_SPEC_AMOUNT ("rewards_committed", + &committed_amount), + GNUNET_PQ_result_spec_timestamp ("expiration", + &expiration), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + lac->ec = TALER_EC_GENERIC_DB_FETCH_FAILED; + return; + } + if (0 > + TALER_amount_subtract (&remaining, + &initial_balance, + &committed_amount)) + { + GNUNET_break (0); + continue; + } + if (0 > + TALER_amount_cmp (&remaining, + &lac->required_amount)) + { + /* insufficient balance */ + if (lac->ok) + continue; /* got another reserve */ + lac->ec = + TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_INSUFFICIENT_FUNDS; + continue; + } + if ( (! GNUNET_TIME_absolute_is_never (lac->expiration.abs_time)) && + GNUNET_TIME_timestamp_cmp (expiration, + >, + lac->expiration) && + GNUNET_TIME_relative_cmp ( + GNUNET_TIME_absolute_get_remaining (lac->expiration.abs_time), + >, + MIN_EXPIRATION) ) + { + /* reserve expired */ + if (lac->ok) + continue; /* got another reserve */ + lac->ec = TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_EXPIRED; + continue; + } + lac->ok = true; + lac->ec = TALER_EC_NONE; + lac->expiration = expiration; + lac->reserve_pub = reserve_pub; + } +} + + +enum TALER_ErrorCode +TMH_PG_authorize_reward (void *cls, + const char *instance_id, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *amount, + const char *justification, + const char *next_url, + struct TALER_RewardIdentifierP *reward_id, + struct GNUNET_TIME_Timestamp *expiration) +{ + struct PostgresClosure *pg = cls; + unsigned int retries = 0; + enum GNUNET_DB_QueryStatus qs; + struct TALER_Amount rewards_committed; + struct TALER_Amount exchange_initial_balance; + const struct TALER_ReservePublicKeyP *reserve_pubp; + struct LookupReserveForRewardContext lac = { + .pg = pg, + .required_amount = *amount, + .expiration = GNUNET_TIME_UNIT_FOREVER_TS + }; + + check_connection (pg); + PREPARE (pg, + "lookup_reserve_for_reward", + "SELECT" + " reserve_pub" + ",expiration" + ",exchange_initial_balance" + ",rewards_committed" + " FROM merchant_reward_reserves" + " WHERE" + " merchant_serial =" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)"); + PREPARE (pg, + "lookup_reserve_status", + "SELECT" + " expiration" + ",exchange_initial_balance" + ",rewards_committed" + " FROM merchant_reward_reserves" + " WHERE reserve_pub = $2" + " AND merchant_serial =" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)"); + PREPARE (pg, + "update_reserve_rewards_committed", + "UPDATE merchant_reward_reserves SET" + " rewards_committed=$3" + " WHERE reserve_pub=$2" + " AND merchant_serial =" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)"); + PREPARE (pg, + "insert_reward", + "INSERT INTO merchant_rewards" + "(reserve_serial" + ",reward_id" + ",justification" + ",next_url" + ",expiration" + ",amount" + ") " + "SELECT" + " reserve_serial, $3, $4, $5, $6, $7" + " FROM merchant_reward_reserves" + " WHERE reserve_pub=$2" + " AND merchant_serial = " + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)"); +RETRY: + reserve_pubp = reserve_pub; + if (MAX_RETRIES < ++retries) + { + GNUNET_break (0); + return + TALER_EC_GENERIC_DB_SOFT_FAILURE; + } + if (GNUNET_OK != + TMH_PG_start (pg, + "authorize reward")) + { + GNUNET_break (0); + return TALER_EC_GENERIC_DB_START_FAILED; + } + if (NULL == reserve_pubp) + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "lookup_reserve_for_reward", + params, + &lookup_reserve_for_reward_cb, + &lac); + switch (qs) + { + case GNUNET_DB_STATUS_SOFT_ERROR: + TMH_PG_rollback (pg); + goto RETRY; + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + TMH_PG_rollback (pg); + return TALER_EC_GENERIC_DB_FETCH_FAILED; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + TMH_PG_rollback (pg); + return TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_NOT_FOUND; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + default: + break; + } + if (TALER_EC_NONE != lac.ec) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Enabling reward reserved failed with status %d\n", + lac.ec); + TMH_PG_rollback (pg); + return lac.ec; + } + GNUNET_assert (lac.ok); + reserve_pubp = &lac.reserve_pub; + } + + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (reserve_pubp), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_timestamp ("expiration", + expiration), + TALER_PQ_RESULT_SPEC_AMOUNT ("rewards_committed", + &rewards_committed), + TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance", + &exchange_initial_balance), + GNUNET_PQ_result_spec_end + }; + + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_reserve_status", + params, + rs); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + TMH_PG_rollback (pg); + goto RETRY; + } + if (qs < 0) + { + GNUNET_break (0); + TMH_PG_rollback (pg); + return TALER_EC_GENERIC_DB_FETCH_FAILED; + } + if (0 == qs) + { + TMH_PG_rollback (pg); + return TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_NOT_FOUND; + } + } + { + struct TALER_Amount remaining; + + if (0 > + TALER_amount_subtract (&remaining, + &exchange_initial_balance, + &rewards_committed)) + { + GNUNET_break (0); + TMH_PG_rollback (pg); + return TALER_EC_GENERIC_DB_INVARIANT_FAILURE; + } + if (0 > + TALER_amount_cmp (&remaining, + amount)) + { + TMH_PG_rollback (pg); + return TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_INSUFFICIENT_FUNDS; + } + } + GNUNET_assert (0 <= + TALER_amount_add (&rewards_committed, + &rewards_committed, + amount)); + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (reserve_pubp), + TALER_PQ_query_param_amount_tuple (pg->conn, + &rewards_committed), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "update_reserve_rewards_committed", + params); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + TMH_PG_rollback (pg); + goto RETRY; + } + if (qs < 0) + { + GNUNET_break (0); + TMH_PG_rollback (pg); + return TALER_EC_GENERIC_DB_STORE_FAILED; + } + } + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + reward_id, + sizeof (*reward_id)); + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (reserve_pubp), + GNUNET_PQ_query_param_auto_from_type (reward_id), + GNUNET_PQ_query_param_string (justification), + GNUNET_PQ_query_param_string (next_url), + GNUNET_PQ_query_param_timestamp (expiration), + TALER_PQ_query_param_amount_tuple (pg->conn, + amount), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_reward", + params); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + TMH_PG_rollback (pg); + goto RETRY; + } + if (qs < 0) + { + GNUNET_break (0); + TMH_PG_rollback (pg); + return TALER_EC_GENERIC_DB_STORE_FAILED; + } + } + qs = TMH_PG_commit (pg); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + if (qs < 0) + { + GNUNET_break (0); + TMH_PG_rollback (pg); + return TALER_EC_GENERIC_DB_COMMIT_FAILED; + } + return TALER_EC_NONE; +} diff --git a/src/backenddb/pg_authorize_reward.h b/src/backenddb/pg_authorize_reward.h new file mode 100644 index 00000000..4ef56bf3 --- /dev/null +++ b/src/backenddb/pg_authorize_reward.h @@ -0,0 +1,64 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_authorize_reward.h + * @brief implementation of the authorize_reward function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_AUTHORIZE_REWARD_H +#define PG_AUTHORIZE_REWARD_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + + +/** + * Authorize a reward over @a amount from reserve @a reserve_pub. Remember + * the authorization under @a reward_id for later, together with the + * @a justification. + * + * @param cls closure, typically a connection to the db + * @param instance_id which instance should generate the reward + * @param reserve_pub which reserve is debited, NULL to pick one in the DB + * @param amount how high is the reward (with fees) + * @param justification why was the reward approved + * @param next_url where to send the URL post reward pickup + * @param[out] reward_id set to the unique ID for the reward + * @param[out] expiration set to when the reward expires + * @return transaction status, + * #TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but has expired + * #TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_NOT_FOUND if the reserve is not known + * #TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has insufficient funds left + * #TALER_EC_GENERIC_DB_START_FAILED on hard DB errors + * #TALER_EC_GENERIC_DB_FETCH_FAILED on hard DB errors + * #TALER_EC_GENERIC_DB_STORE_FAILED on hard DB errors + * #TALER_EC_GENERIC_DB_INVARIANT_FAILURE on hard DB errors + * #TALER_EC_GENERIC_DB_SOFT_FAILURE soft DB errors (client should retry) + * #TALER_EC_NONE upon success + */ +enum TALER_ErrorCode +TMH_PG_authorize_reward (void *cls, + const char *instance_id, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *amount, + const char *justification, + const char *next_url, + struct TALER_RewardIdentifierP *reward_id, + struct GNUNET_TIME_Timestamp *expiration); + + +#endif diff --git a/src/backenddb/pg_helper.c b/src/backenddb/pg_helper.c new file mode 100644 index 00000000..2ac047b1 --- /dev/null +++ b/src/backenddb/pg_helper.c @@ -0,0 +1,121 @@ +/* + This file is part of TALER + Copyright (C) 2023 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/> + */ +/** + * @file pg_helper.c + * @brief shared internal definitions for postgres DB plugin + * @author Christian Grothoff + */ +#include "platform.h" +#include "pg_helper.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_pq_lib.h> +#include <taler/taler_util.h> +#include <taler/taler_pq_lib.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_mhd_lib.h> + + +enum GNUNET_GenericReturnValue +TMH_PG_start (void *cls, + const char *name) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + + check_connection (pg); + postgres_preflight (pg); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Starting merchant DB transaction `%s'\n", + name); + if (GNUNET_OK != + GNUNET_PQ_exec_statements (pg->conn, + es)) + { + TALER_LOG_ERROR ("Failed to start transaction\n"); + GNUNET_break (0); + return GNUNET_SYSERR; + } + pg->transaction_name = name; + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TMH_PG_start_read_committed (void *cls, + const char *name) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL READ COMMITTED"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + + check_connection (pg); + postgres_preflight (pg); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Starting merchant DB transaction %s (READ COMMITTED)\n", + name); + if (GNUNET_OK != + GNUNET_PQ_exec_statements (pg->conn, + es)) + { + TALER_LOG_ERROR ("Failed to start transaction\n"); + GNUNET_break (0); + return GNUNET_SYSERR; + } + pg->transaction_name = name; + return GNUNET_OK; +} + + +void +TMH_PG_rollback (void *cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute ("ROLLBACK"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Rolling back merchant DB transaction `%s'\n", + pg->transaction_name); + GNUNET_break (GNUNET_OK == + GNUNET_PQ_exec_statements (pg->conn, + es)); + pg->transaction_name = NULL; +} + + +enum GNUNET_DB_QueryStatus +TMH_PG_commit (void *cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_end + }; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Committing merchant DB transaction %s\n", + pg->transaction_name); + pg->transaction_name = NULL; + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "end_transaction", + params); +} diff --git a/src/backenddb/pg_helper.h b/src/backenddb/pg_helper.h index bb8e7255..83fede8f 100644 --- a/src/backenddb/pg_helper.h +++ b/src/backenddb/pg_helper.h @@ -102,7 +102,8 @@ struct PostgresClosure * @param field name of the database field to fetch amount from * @param[out] amountp pointer to amount to set */ -#define TALER_PQ_RESULT_SPEC_AMOUNT(field,amountp) TALER_PQ_result_spec_amount ( \ +#define TALER_PQ_RESULT_SPEC_AMOUNT(field, \ + amountp) TALER_PQ_result_spec_amount_tuple ( \ field,pg->currency,amountp) @@ -114,7 +115,8 @@ struct PostgresClosure * @param[out] amountp pointer to amount to set */ #define TALER_PQ_RESULT_SPEC_AMOUNT_NBO(field, \ - amountp) TALER_PQ_result_spec_amount_nbo ( \ + amountp) \ + TALER_PQ_result_spec_amount_tuple_nbo ( \ field,pg->currency,amountp) @@ -139,5 +141,48 @@ check_connection (struct PostgresClosure *pg); void postgres_preflight (void *cls); +/** + * 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 + */ +enum GNUNET_GenericReturnValue +TMH_PG_start (void *cls, + const char *name); + + +/** + * Start a transaction in 'read committed' mode. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param name unique name identifying the transaction (for debugging), + * must point to a constant + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +TMH_PG_start_read_committed (void *cls, + const char *name); + +/** + * Roll back the current transaction of a database connection. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + */ +void +TMH_PG_rollback (void *cls); + + +/** + * Commit the current transaction of a database connection. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @return transaction status code + */ +enum GNUNET_DB_QueryStatus +TMH_PG_commit (void *cls); + #endif diff --git a/src/backenddb/pg_increase_refund.c b/src/backenddb/pg_increase_refund.c new file mode 100644 index 00000000..3b0842d6 --- /dev/null +++ b/src/backenddb/pg_increase_refund.c @@ -0,0 +1,491 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_increase_refund.c + * @brief Implementation of the increase_refund function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_increase_refund.h" +#include "pg_helper.h" + + +/** + * Closure for #process_refund_cb(). + */ +struct FindRefundContext +{ + + /** + * Plugin context. + */ + struct PostgresClosure *pg; + + /** + * Updated to reflect total amount refunded so far. + */ + struct TALER_Amount refunded_amount; + + /** + * Set to the largest refund transaction ID encountered. + */ + uint64_t max_rtransaction_id; + + /** + * Set to true on hard errors. + */ + bool err; +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure, our `struct FindRefundContext` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +process_refund_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct FindRefundContext *ictx = cls; + struct PostgresClosure *pg = ictx->pg; + + for (unsigned int i = 0; i<num_results; i++) + { + /* Sum up existing refunds */ + struct TALER_Amount acc; + uint64_t rtransaction_id; + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", + &acc), + GNUNET_PQ_result_spec_uint64 ("rtransaction_id", + &rtransaction_id), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ictx->err = true; + return; + } + if (0 > + TALER_amount_add (&ictx->refunded_amount, + &ictx->refunded_amount, + &acc)) + { + GNUNET_break (0); + ictx->err = true; + return; + } + ictx->max_rtransaction_id = GNUNET_MAX (ictx->max_rtransaction_id, + rtransaction_id); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Found refund of %s\n", + TALER_amount2s (&acc)); + } +} + + +/** + * Closure for #process_deposits_for_refund_cb(). + */ +struct InsertRefundContext +{ + /** + * Used to provide a connection to the db + */ + struct PostgresClosure *pg; + + /** + * Amount to which increase the refund for this contract + */ + const struct TALER_Amount *refund; + + /** + * Human-readable reason behind this refund + */ + const char *reason; + + /** + * Transaction status code. + */ + enum TALER_MERCHANTDB_RefundStatus rs; +}; + + +/** + * Data extracted per coin. + */ +struct RefundCoinData +{ + + /** + * Public key of a coin. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Amount deposited for this coin. + */ + struct TALER_Amount deposited_with_fee; + + /** + * Amount refunded already for this coin. + */ + struct TALER_Amount refund_amount; + + /** + * Order serial (actually not really per-coin). + */ + uint64_t order_serial; + + /** + * Maximum rtransaction_id for this coin so far. + */ + uint64_t max_rtransaction_id; + +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure, our `struct InsertRefundContext` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +process_deposits_for_refund_cb ( + void *cls, + PGresult *result, + unsigned int num_results) +{ + struct InsertRefundContext *ctx = cls; + struct PostgresClosure *pg = ctx->pg; + struct TALER_Amount current_refund; + struct RefundCoinData rcd[GNUNET_NZL (num_results)]; + struct GNUNET_TIME_Timestamp now; + + now = GNUNET_TIME_timestamp_get (); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (ctx->refund->currency, + ¤t_refund)); + memset (rcd, 0, sizeof (rcd)); + /* Pass 1: Collect amount of existing refunds into current_refund. + * Also store existing refunded amount for each deposit in deposit_refund. */ + for (unsigned int i = 0; i<num_results; i++) + { + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("coin_pub", + &rcd[i].coin_pub), + GNUNET_PQ_result_spec_uint64 ("order_serial", + &rcd[i].order_serial), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", + &rcd[i].deposited_with_fee), + GNUNET_PQ_result_spec_end + }; + struct FindRefundContext ictx = { + .pg = pg + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; + return; + } + + { + enum GNUNET_DB_QueryStatus ires; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub), + GNUNET_PQ_query_param_uint64 (&rcd[i].order_serial), + GNUNET_PQ_query_param_end + }; + + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (ctx->refund->currency, + &ictx.refunded_amount)); + ires = GNUNET_PQ_eval_prepared_multi_select (ctx->pg->conn, + "find_refunds_by_coin", + params, + &process_refund_cb, + &ictx); + if ( (ictx.err) || + (GNUNET_DB_STATUS_HARD_ERROR == ires) ) + { + GNUNET_break (0); + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; + return; + } + if (GNUNET_DB_STATUS_SOFT_ERROR == ires) + { + ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR; + return; + } + } + if (0 > + TALER_amount_add (¤t_refund, + ¤t_refund, + &ictx.refunded_amount)) + { + GNUNET_break (0); + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; + return; + } + rcd[i].refund_amount = ictx.refunded_amount; + rcd[i].max_rtransaction_id = ictx.max_rtransaction_id; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Existing refund for coin %s is %s\n", + TALER_B2S (&rcd[i].coin_pub), + TALER_amount2s (&ictx.refunded_amount)); + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Total existing refund is %s\n", + TALER_amount2s (¤t_refund)); + + /* stop immediately if we are 'done' === amount already + * refunded. */ + if (0 >= TALER_amount_cmp (ctx->refund, + ¤t_refund)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Existing refund of %s at or above requested refund. Finished early.\n", + TALER_amount2s (¤t_refund)); + ctx->rs = TALER_MERCHANTDB_RS_SUCCESS; + return; + } + + /* Phase 2: Try to increase current refund until it matches desired refund */ + for (unsigned int i = 0; i<num_results; i++) + { + const struct TALER_Amount *increment; + struct TALER_Amount left; + struct TALER_Amount remaining_refund; + + /* How much of the coin is left after the existing refunds? */ + if (0 > + TALER_amount_subtract (&left, + &rcd[i].deposited_with_fee, + &rcd[i].refund_amount)) + { + GNUNET_break (0); + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; + return; + } + + if ( (0 == left.value) && + (0 == left.fraction) ) + { + /* coin was fully refunded, move to next coin */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Coin %s fully refunded, moving to next coin\n", + TALER_B2S (&rcd[i].coin_pub)); + continue; + } + + rcd[i].max_rtransaction_id++; + /* How much of the refund is still to be paid back? */ + if (0 > + TALER_amount_subtract (&remaining_refund, + ctx->refund, + ¤t_refund)) + { + GNUNET_break (0); + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; + return; + } + + /* By how much will we increase the refund for this coin? */ + if (0 >= TALER_amount_cmp (&remaining_refund, + &left)) + { + /* remaining_refund <= left */ + increment = &remaining_refund; + } + else + { + increment = &left; + } + + if (0 > + TALER_amount_add (¤t_refund, + ¤t_refund, + increment)) + { + GNUNET_break (0); + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; + return; + } + + /* actually run the refund */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Coin %s deposit amount is %s\n", + TALER_B2S (&rcd[i].coin_pub), + TALER_amount2s (&rcd[i].deposited_with_fee)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Coin %s refund will be incremented by %s\n", + TALER_B2S (&rcd[i].coin_pub), + TALER_amount2s (increment)); + { + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&rcd[i].order_serial), + GNUNET_PQ_query_param_uint64 (&rcd[i].max_rtransaction_id), /* already inc'ed */ + GNUNET_PQ_query_param_timestamp (&now), + GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub), + GNUNET_PQ_query_param_string (ctx->reason), + TALER_PQ_query_param_amount_tuple (pg->conn, + increment), + GNUNET_PQ_query_param_end + }; + + check_connection (pg); + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_refund", + params); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; + return; + case GNUNET_DB_STATUS_SOFT_ERROR: + ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR; + return; + default: + ctx->rs = (enum TALER_MERCHANTDB_RefundStatus) qs; + break; + } + } + + /* stop immediately if we are done */ + if (0 == TALER_amount_cmp (ctx->refund, + ¤t_refund)) + { + ctx->rs = TALER_MERCHANTDB_RS_SUCCESS; + return; + } + } + + /** + * We end up here if not all of the refund has been covered. + * Although this should be checked as the business should never + * issue a refund bigger than the contract's actual price, we cannot + * rely upon the frontend being correct. + */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "The refund of %s is bigger than the order's value\n", + TALER_amount2s (ctx->refund)); + ctx->rs = TALER_MERCHANTDB_RS_TOO_HIGH; +} + + +enum TALER_MERCHANTDB_RefundStatus +TMH_PG_increase_refund (void *cls, + const char *instance_id, + const char *order_id, + const struct TALER_Amount *refund, + const char *reason) +{ + struct PostgresClosure *pg = cls; + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_end + }; + struct InsertRefundContext ctx = { + .pg = pg, + .refund = refund, + .reason = reason + }; + + PREPARE (pg, + "insert_refund", + "INSERT INTO merchant_refunds" + "(order_serial" + ",rtransaction_id" + ",refund_timestamp" + ",coin_pub" + ",reason" + ",refund_amount" + ") VALUES" + "($1, $2, $3, $4, $5, $6)"); + PREPARE (pg, + "find_refunds_by_coin", + "SELECT" + " refund_amount" + ",rtransaction_id" + " FROM merchant_refunds" + " WHERE coin_pub=$1" + " AND order_serial=$2"); + PREPARE (pg, + "find_deposits_for_refund", + "SELECT" + " coin_pub" + ",order_serial" + ",amount_with_fee" + " FROM merchant_deposits" + " WHERE order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " WHERE order_id=$2" + " AND paid=TRUE" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1))"); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Asked to refund %s on order %s\n", + TALER_amount2s (refund), + order_id); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "find_deposits_for_refund", + params, + &process_deposits_for_refund_cb, + &ctx); + switch (qs) + { + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* never paid, means we clearly cannot refund anything */ + return TALER_MERCHANTDB_RS_NO_SUCH_ORDER; + case GNUNET_DB_STATUS_SOFT_ERROR: + return TALER_MERCHANTDB_RS_SOFT_ERROR; + case GNUNET_DB_STATUS_HARD_ERROR: + return TALER_MERCHANTDB_RS_HARD_ERROR; + default: + /* Got one or more deposits */ + return ctx.rs; + } +} diff --git a/src/backenddb/pg_increase_refund.h b/src/backenddb/pg_increase_refund.h new file mode 100644 index 00000000..0fe8a470 --- /dev/null +++ b/src/backenddb/pg_increase_refund.h @@ -0,0 +1,56 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_increase_refund.h + * @brief implementation of the increase_refund function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_INCREASE_REFUND_H +#define PG_INCREASE_REFUND_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + + +/** + * 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 mulrewardle SQL statements. + * + * @param cls closure + * @param instance_id instance identifier + * @param order_id the order to increase the refund for + * @param refund maximum refund to return to the customer for this contract + * @param reason 0-terminated UTF-8 string giving the reason why the customer + * got a refund (free form, business-specific) + * @return transaction status + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a refund is ABOVE the amount we + * were originally paid and thus the transaction failed; + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid, + * regardless of whether it actually increased the refund beyond + * what was already refunded (idempotency!) + */ +enum TALER_MERCHANTDB_RefundStatus +TMH_PG_increase_refund (void *cls, + const char *instance_id, + const char *order_id, + const struct TALER_Amount *refund, + const char *reason); + + +#endif diff --git a/src/backenddb/pg_insert_deposit.c b/src/backenddb/pg_insert_deposit.c index f7f20d47..2c1d1dd4 100644 --- a/src/backenddb/pg_insert_deposit.c +++ b/src/backenddb/pg_insert_deposit.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022, 2023 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 @@ -16,6 +16,7 @@ /** * @file backenddb/pg_insert_deposit.c * @brief Implementation of the insert_deposit function for Postgres + * @author Christian Grothoff * @author Iván Ávalos */ #include "platform.h" @@ -25,6 +26,7 @@ #include "pg_insert_deposit.h" #include "pg_helper.h" + enum GNUNET_DB_QueryStatus TMH_PG_insert_deposit ( void *cls, @@ -48,13 +50,17 @@ TMH_PG_insert_deposit ( GNUNET_PQ_query_param_timestamp (&deposit_timestamp), /* $3 */ GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_string (exchange_url), - TALER_PQ_query_param_amount (amount_with_fee), /* $6/$7 */ - TALER_PQ_query_param_amount (deposit_fee), /* $8, $9 */ - TALER_PQ_query_param_amount (refund_fee), /* $10, $11 */ - TALER_PQ_query_param_amount (wire_fee), /* $12, $13 */ - GNUNET_PQ_query_param_auto_from_type (h_wire), /* $14 */ - GNUNET_PQ_query_param_auto_from_type (exchange_sig), /* $15 */ - GNUNET_PQ_query_param_auto_from_type (exchange_pub), /* $16 */ + TALER_PQ_query_param_amount_tuple (pg->conn, + amount_with_fee), /* $6 */ + TALER_PQ_query_param_amount_tuple (pg->conn, + deposit_fee), /* $7 */ + TALER_PQ_query_param_amount_tuple (pg->conn, + refund_fee), /* $8 */ + TALER_PQ_query_param_amount_tuple (pg->conn, + wire_fee), /* $9 */ + GNUNET_PQ_query_param_auto_from_type (h_wire), /* $10 */ + GNUNET_PQ_query_param_auto_from_type (exchange_sig), /* $11 */ + GNUNET_PQ_query_param_auto_from_type (exchange_pub), /* $12 */ GNUNET_PQ_query_param_end }; @@ -71,7 +77,7 @@ TMH_PG_insert_deposit ( "WITH md AS" " (SELECT account_serial, merchant_serial" " FROM merchant_accounts" - " WHERE h_wire=$14" + " WHERE h_wire=$10" " AND merchant_serial=" " (SELECT merchant_serial" " FROM merchant_instances" @@ -79,7 +85,7 @@ TMH_PG_insert_deposit ( ", ed AS" " (SELECT signkey_serial" " FROM merchant_exchange_signing_keys" - " WHERE exchange_pub=$16" + " WHERE exchange_pub=$12" " ORDER BY start_date DESC" " LIMIT 1)" "INSERT INTO merchant_deposits" @@ -87,20 +93,16 @@ TMH_PG_insert_deposit ( ",deposit_timestamp" ",coin_pub" ",exchange_url" - ",amount_with_fee_val" - ",amount_with_fee_frac" - ",deposit_fee_val" - ",deposit_fee_frac" - ",refund_fee_val" - ",refund_fee_frac" - ",wire_fee_val" - ",wire_fee_frac" + ",amount_with_fee" + ",deposit_fee" + ",refund_fee" + ",wire_fee" ",exchange_sig" ",signkey_serial" ",account_serial)" " SELECT " " order_serial" - " ,$3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $15" + " ,$3, $4, $5, $6, $7, $8, $9, $11" " ,ed.signkey_serial" " ,md.account_serial" " FROM merchant_contract_terms" @@ -110,5 +112,4 @@ TMH_PG_insert_deposit ( return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_deposit", params); - } diff --git a/src/backenddb/pg_insert_deposit_to_transfer.c b/src/backenddb/pg_insert_deposit_to_transfer.c new file mode 100644 index 00000000..718d78fa --- /dev/null +++ b/src/backenddb/pg_insert_deposit_to_transfer.c @@ -0,0 +1,64 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_postgres_insert_deposit_to_transfer.c + * @brief Implementation of the insert_deposit_to_transfer function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_insert_deposit_to_transfer.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TMH_PG_insert_deposit_to_transfer ( + void *cls, + uint64_t deposit_serial, + const struct TALER_EXCHANGE_DepositData *dd) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&deposit_serial), + TALER_PQ_query_param_amount_tuple (pg->conn, + &dd->coin_contribution), + GNUNET_PQ_query_param_timestamp (&dd->execution_time), + GNUNET_PQ_query_param_auto_from_type (&dd->exchange_sig), + GNUNET_PQ_query_param_auto_from_type (&dd->exchange_pub), + GNUNET_PQ_query_param_auto_from_type (&dd->wtid), + GNUNET_PQ_query_param_end + }; + + PREPARE (pg, + "insert_deposit_to_transfer", + "INSERT INTO merchant_deposit_to_transfer" + "(deposit_serial" + ",coin_contribution_value" + ",credit_serial" + ",execution_time" + ",signkey_serial" + ",exchange_sig" + ") SELECT $1, $2, credit_serial, $3, signkey_serial, $4" + " FROM merchant_transfers" + " CROSS JOIN merchant_exchange_signing_keys" + " WHERE exchange_pub=$5" + " AND wtid=$6"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_deposit_to_transfer", + params); +} diff --git a/src/backenddb/pg_insert_deposit_to_transfer.h b/src/backenddb/pg_insert_deposit_to_transfer.h new file mode 100644 index 00000000..049cbcac --- /dev/null +++ b/src/backenddb/pg_insert_deposit_to_transfer.h @@ -0,0 +1,44 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_insert_deposit_to_transfer.h + * @brief implementation of the insert_deposit_to_transfer function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_INSERT_DEPOSIT_TO_TRANSFER_H +#define PG_INSERT_DEPOSIT_TO_TRANSFER_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + + +/** + * Insert wire transfer details for a deposit. + * + * @param cls closure + * @param deposit_serial serial number of the deposit + * @param dd deposit transfer data from the exchange to store + * @return transaction status + */ +enum GNUNET_DB_QueryStatus +TMH_PG_insert_deposit_to_transfer ( + void *cls, + uint64_t deposit_serial, + const struct TALER_EXCHANGE_DepositData *dd); + + +#endif diff --git a/src/backenddb/pg_insert_instance.c b/src/backenddb/pg_insert_instance.c index 3777f743..82344ff4 100644 --- a/src/backenddb/pg_insert_instance.c +++ b/src/backenddb/pg_insert_instance.c @@ -16,6 +16,7 @@ /** * @file backenddb/pg_insert_instance.c * @brief Implementation of the insert_instance function for Postgres + * @author Christian Grothoff * @author Iván Ávalos */ #include "platform.h" @@ -44,8 +45,10 @@ TMH_PG_insert_instance ( GNUNET_PQ_query_param_uint32 (&ut32), 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), + TALER_PQ_query_param_amount_tuple (pg->conn, + &is->default_max_deposit_fee), + TALER_PQ_query_param_amount_tuple (pg->conn, + &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), @@ -80,10 +83,8 @@ TMH_PG_insert_instance ( ",user_type" ",address" ",jurisdiction" - ",default_max_deposit_fee_val" - ",default_max_deposit_fee_frac" - ",default_max_wire_fee_val" - ",default_max_wire_fee_frac" + ",default_max_deposit_fee" + ",default_max_wire_fee" ",default_wire_fee_amortization" ",default_wire_transfer_delay" ",default_pay_delay" @@ -91,7 +92,7 @@ TMH_PG_insert_instance ( ",email" ",logo)" "VALUES" - "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)"); + "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)"); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_instance", params); diff --git a/src/backenddb/pg_insert_pickup.c b/src/backenddb/pg_insert_pickup.c new file mode 100644 index 00000000..5f1f91fa --- /dev/null +++ b/src/backenddb/pg_insert_pickup.c @@ -0,0 +1,162 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_insert_pickup.c + * @brief Implementation of the insert_pickup function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_insert_pickup.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TMH_PG_insert_pickup ( + void *cls, + const char *instance_id, + const struct TALER_RewardIdentifierP *reward_id, + const struct TALER_Amount *total_picked_up, + const struct TALER_PickupIdentifierP *pickup_id, + const struct TALER_Amount *total_requested) +{ + struct PostgresClosure *pg = cls; + enum GNUNET_DB_QueryStatus qs; + + PREPARE (pg, + "insert_pickup", + "INSERT INTO merchant_reward_pickups" + "(reward_serial" + ",pickup_id" + ",amount" + ") " + "SELECT" + " reward_serial, $3, $4" + " FROM merchant_rewards" + " JOIN merchant_reward_reserves USING (reserve_serial)" + " WHERE reward_id=$2" + " AND merchant_serial = " + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)"); + PREPARE (pg, + "update_picked_up_reward", + "UPDATE merchant_rewards SET" + " picked_up=$2" + ",was_picked_up = (CAST($2 AS taler_amount) = amount)" + " WHERE reward_id = $1"); + PREPARE (pg, + "lookup_picked_up_reserve", + "SELECT" + " reserve_serial" + ",rewards_picked_up" + " FROM merchant_reward_reserves" + " JOIN merchant_rewards USING (reserve_serial)" + " WHERE reward_id=$2" + " AND merchant_serial =" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)"); + PREPARE (pg, + "update_picked_up_reserve", + "UPDATE merchant_reward_reserves SET" + " rewards_picked_up=$2" + " WHERE reserve_serial = $1"); + + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (reward_id), + GNUNET_PQ_query_param_auto_from_type (pickup_id), + TALER_PQ_query_param_amount_tuple (pg->conn, + total_requested), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_pickup", + params); + if (0 > qs) + return qs; + } + + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (reward_id), + TALER_PQ_query_param_amount_tuple (pg->conn, + total_picked_up), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "update_picked_up_reward", + params); + if (0 > qs) + return qs; + } + { + uint64_t reserve_serial; + struct TALER_Amount reserve_picked_up; + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (reward_id), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("reserve_serial", + &reserve_serial), + TALER_PQ_RESULT_SPEC_AMOUNT ("rewards_picked_up", + &reserve_picked_up), + GNUNET_PQ_result_spec_end + + }; + + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_picked_up_reserve", + params, + rs); + if (0 > qs) + return qs; + } + if (0 >= + TALER_amount_add (&reserve_picked_up, + &reserve_picked_up, + total_requested)) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&reserve_serial), + TALER_PQ_query_param_amount_tuple (pg->conn, + &reserve_picked_up), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "update_picked_up_reserve", + params); + if (0 > qs) + return qs; + } + } + return qs; +} diff --git a/src/backenddb/pg_insert_pickup.h b/src/backenddb/pg_insert_pickup.h new file mode 100644 index 00000000..73a0598d --- /dev/null +++ b/src/backenddb/pg_insert_pickup.h @@ -0,0 +1,57 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_postgres_insert_pickup.h + * @brief implementation of the postgres_insert_pickup function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_POSTGRES_INSERT_PICKUP_H +#define PG_POSTGRES_INSERT_PICKUP_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + + +/** + * Insert details about a reward pickup operation. The @a total_picked_up + * UPDATES the total amount under the @a reward_id, while the @a + * total_requested is the amount to be associated with this @a pickup_id. + * While there is usually only one pickup event that picks up the entire + * amount, our schema allows for wallets to pick up the amount incrementally + * over mulrewardle pick up operations. + * + * @param cls closure, typically a connection to the db + * @param instance_id which instance gave the reward + * @param reward_id the unique ID for the reward + * @param total_picked_up how much was picked up overall at this + * point (includes @a total_requested) + * @param pickup_id unique ID for the operation + * @param total_requested how much is being picked up in this operation + * @return transaction status, usually + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a credit_uuid already known + */ +enum GNUNET_DB_QueryStatus +TMH_PG_insert_pickup ( + void *cls, + const char *instance_id, + const struct TALER_RewardIdentifierP *reward_id, + const struct TALER_Amount *total_picked_up, + const struct TALER_PickupIdentifierP *pickup_id, + const struct TALER_Amount *total_requested); + +#endif diff --git a/src/backenddb/pg_insert_product.c b/src/backenddb/pg_insert_product.c index 8ee2b274..3767accc 100644 --- a/src/backenddb/pg_insert_product.c +++ b/src/backenddb/pg_insert_product.c @@ -16,6 +16,7 @@ /** * @file backenddb/pg_insert_product.c * @brief Implementation of the insert_product function for Postgres + * @author Christian Grothoff * @author Iván Ávalos */ #include "platform.h" @@ -40,7 +41,8 @@ TMH_PG_insert_product (void *cls, GNUNET_PQ_query_param_string (pd->unit), GNUNET_PQ_query_param_string (pd->image), TALER_PQ_query_param_json (pd->taxes), - TALER_PQ_query_param_amount (&pd->price), + TALER_PQ_query_param_amount_tuple (pg->conn, + &pd->price), GNUNET_PQ_query_param_uint64 (&pd->total_stock), TALER_PQ_query_param_json (pd->address), GNUNET_PQ_query_param_timestamp (&pd->next_restock), @@ -59,15 +61,14 @@ TMH_PG_insert_product (void *cls, ",unit" ",image" ",taxes" - ",price_val" - ",price_frac" + ",price" ",total_stock" ",address" ",next_restock" ",minimum_age" ")" " SELECT merchant_serial," - " $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13" + " $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12" " FROM merchant_instances" " WHERE merchant_id=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, diff --git a/src/backenddb/pg_insert_reserve.c b/src/backenddb/pg_insert_reserve.c new file mode 100644 index 00000000..bc73b747 --- /dev/null +++ b/src/backenddb/pg_insert_reserve.c @@ -0,0 +1,126 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_insert_reserve.c + * @brief Implementation of the insert_reserve function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_insert_reserve.h" +#include "pg_helper.h" + +/** + * How often do we re-try if we run into a DB serialization error? + */ +#define MAX_RETRIES 3 + + +enum TALER_ErrorCode +TMH_PG_insert_reserve (void *cls, + const char *instance_id, + const struct TALER_ReservePrivateKeyP *reserve_priv, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_MasterPublicKeyP *master_pub, + const char *exchange_url, + const struct TALER_Amount *initial_balance, + struct GNUNET_TIME_Timestamp expiration) +{ + struct PostgresClosure *pg = cls; + unsigned int retries; + enum GNUNET_DB_QueryStatus qs; + + retries = 0; + check_connection (pg); + PREPARE (pg, + "insert_reserve", + "INSERT INTO merchant_reward_reserves" + "(reserve_pub" + ",merchant_serial" + ",creation_time" + ",expiration" + ",merchant_initial_balance" + ")" + "SELECT $2, merchant_serial, $3, $4, $5" + " FROM merchant_instances" + " WHERE merchant_id=$1"); +RETRY: + if (MAX_RETRIES < ++retries) + return TALER_EC_GENERIC_DB_SOFT_FAILURE; + if (GNUNET_OK != + TMH_PG_start (pg, + "insert reserve")) + { + GNUNET_break (0); + return TALER_EC_GENERIC_DB_START_FAILED; + } + + /* Setup reserve */ + { + struct GNUNET_TIME_Timestamp now; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (reserve_pub), + GNUNET_PQ_query_param_timestamp (&now), + GNUNET_PQ_query_param_timestamp (&expiration), + TALER_PQ_query_param_amount_tuple (pg->conn, + initial_balance), + GNUNET_PQ_query_param_end + }; + + now = GNUNET_TIME_timestamp_get (); + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_reserve", + params); + if (0 > qs) + { + TMH_PG_rollback (pg); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + return qs; + } + } + /* Store private key */ + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (reserve_pub), + GNUNET_PQ_query_param_auto_from_type (reserve_priv), + GNUNET_PQ_query_param_string (exchange_url), + GNUNET_PQ_query_param_auto_from_type (master_pub), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_reserve_key", + params); + if (0 > qs) + { + TMH_PG_rollback (pg); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + return qs; + } + } + qs = TMH_PG_commit (pg); + if (0 <= qs) + return TALER_EC_NONE; /* success */ + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + return qs; +} diff --git a/src/backenddb/pg_insert_reserve.h b/src/backenddb/pg_insert_reserve.h new file mode 100644 index 00000000..41bae2ce --- /dev/null +++ b/src/backenddb/pg_insert_reserve.h @@ -0,0 +1,56 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_insert_reserve.h + * @brief implementation of the insert_reserve function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_INSERT_RESERVE_H +#define PG_INSERT_RESERVE_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Add @a credit to a reserve to be used for rewardping. Note that + * this function does not actually perform any wire transfers to + * credit the reserve, it merely tells the merchant backend that + * a reserve now exists. This has to happen before rewards can be + * authorized. + * + * @param cls closure, typically a connection to the db + * @param instance_id which instance is the reserve tied to + * @param reserve_priv which reserve is topped up or created + * @param reserve_pub which reserve is topped up or created + * @param master_pub master public key of the exchange + * @param exchange_url what URL is the exchange reachable at where the reserve is located + * @param initial_balance how much money will be added to the reserve + * @param expiration when does the reserve expire? + * @return transaction status, usually + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success + */ +enum TALER_ErrorCode +TMH_PG_insert_reserve (void *cls, + const char *instance_id, + const struct TALER_ReservePrivateKeyP *reserve_priv, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_MasterPublicKeyP *master_pub, + const char *exchange_url, + const struct TALER_Amount *initial_balance, + struct GNUNET_TIME_Timestamp expiration); + +#endif diff --git a/src/backenddb/pg_insert_transfer.c b/src/backenddb/pg_insert_transfer.c new file mode 100644 index 00000000..872cd1ff --- /dev/null +++ b/src/backenddb/pg_insert_transfer.c @@ -0,0 +1,72 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_insert_transfer.c + * @brief Implementation of the insert_transfer function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_insert_transfer.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TMH_PG_insert_transfer ( + void *cls, + const char *instance_id, + const char *exchange_url, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_Amount *credit_amount, + const char *payto_uri, + bool confirmed) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (exchange_url), + GNUNET_PQ_query_param_auto_from_type (wtid), + TALER_PQ_query_param_amount_tuple (pg->conn, + credit_amount), + GNUNET_PQ_query_param_string (payto_uri), + GNUNET_PQ_query_param_bool (confirmed), + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_end + }; + + check_connection (pg); + PREPARE (pg, + "insert_transfer", + "INSERT INTO merchant_transfers" + "(exchange_url" + ",wtid" + ",credit_amount" + ",account_serial" + ",confirmed)" + "SELECT" + " $1, $2, $3, account_serial, $5" + " FROM merchant_accounts" + " WHERE payto_uri=$4" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$6)" + " ON CONFLICT DO NOTHING;"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_transfer", + params); +} diff --git a/src/backenddb/pg_insert_transfer.h b/src/backenddb/pg_insert_transfer.h new file mode 100644 index 00000000..b7bffa11 --- /dev/null +++ b/src/backenddb/pg_insert_transfer.h @@ -0,0 +1,52 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_postgres_insert_transfer.h + * @brief implementation of the postgres_insert_transfer function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_POSTGRES_INSERT_TRANSFER_H +#define PG_POSTGRES_INSERT_TRANSFER_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Insert information about a wire transfer the merchant has received. + * + * @param cls closure + * @param instance_id the instance that received the transfer + * @param exchange_url which exchange made the transfer + * @param wtid identifier of the wire transfer + * @param credit_amount how much did we receive + * @param payto_uri what is the merchant's bank account that received the transfer + * @param confirmed whether the transfer was confirmed by the merchant or + * was merely claimed by the exchange at this point + * @return transaction status + */ +enum GNUNET_DB_QueryStatus +TMH_PG_insert_transfer ( + void *cls, + const char *instance_id, + const char *exchange_url, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_Amount *credit_amount, + const char *payto_uri, + bool confirmed); + + +#endif diff --git a/src/backenddb/pg_insert_transfer_details.c b/src/backenddb/pg_insert_transfer_details.c new file mode 100644 index 00000000..d79dbe90 --- /dev/null +++ b/src/backenddb/pg_insert_transfer_details.c @@ -0,0 +1,290 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_insert_transfer_details.c + * @brief Implementation of the insert_transfer_details function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_insert_transfer_details.h" +#include "pg_helper.h" + + +/** + * How often do we re-try if we run into a DB serialization error? + */ +#define MAX_RETRIES 3 + + +enum GNUNET_DB_QueryStatus +TMH_PG_insert_transfer_details ( + void *cls, + const char *instance_id, + const char *exchange_url, + const char *payto_uri, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_EXCHANGE_TransferData *td) +{ + struct PostgresClosure *pg = cls; + enum GNUNET_DB_QueryStatus qs; + uint64_t credit_serial; + unsigned int retries; + + retries = 0; + check_connection (pg); + + PREPARE (pg, + "lookup_credit_serial", + "SELECT" + " credit_serial" + " FROM merchant_transfers" + " WHERE exchange_url=$1" + " AND wtid=$4" + " AND account_serial=" + " (SELECT account_serial" + " FROM merchant_accounts" + " WHERE payto_uri=$2" + " AND exchange_url=$1" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$3))"); + PREPARE (pg, + "insert_transfer_signature", + "INSERT INTO merchant_transfer_signatures" + "(credit_serial" + ",signkey_serial" + ",credit_amount" + ",wire_fee" + ",execution_time" + ",exchange_sig) " + "SELECT $1, signkey_serial, $2, $3, $4, $5" + " FROM merchant_exchange_signing_keys" + " WHERE exchange_pub=$6" + " ORDER BY start_date DESC" + " LIMIT 1"); + PREPARE (pg, + "insert_transfer_to_coin_mapping", + "INSERT INTO merchant_transfer_to_coin" + "(deposit_serial" + ",credit_serial" + ",offset_in_exchange_list" + ",exchange_deposit_value" + ",exchange_deposit_fee) " + "SELECT deposit_serial, $1, $2, $3, $4" + " FROM merchant_deposits" + " JOIN merchant_contract_terms USING (order_serial)" + " WHERE coin_pub=$5" + " AND h_contract_terms=$6" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$7)"); + PREPARE (pg, + "update_wired_by_coin_pub", + "WITH os AS" /* select orders affected by the coin */ + "(SELECT order_serial" + " FROM merchant_deposits" + " WHERE coin_pub=$1)" + "UPDATE merchant_contract_terms " + " SET wired=TRUE " + " WHERE order_serial IN " + " (SELECT order_serial FROM merchant_deposits" /* only orders for which NO un-wired coin exists*/ + " WHERE NOT EXISTS " + " (SELECT order_serial FROM merchant_deposits" /* orders for which ANY un-wired coin exists */ + " JOIN os USING (order_serial)" /* filter early */ + " WHERE deposit_serial NOT IN" + " (SELECT deposit_serial " /* all coins associated with order that WERE wired */ + " FROM merchant_deposits " + " JOIN os USING (order_serial)" /* filter early */ + " JOIN merchant_deposit_to_transfer USING (deposit_serial)" + " JOIN merchant_transfers USING (credit_serial)" + " WHERE confirmed=TRUE)))"); + +RETRY: + if (MAX_RETRIES < ++retries) + return GNUNET_DB_STATUS_SOFT_ERROR; + if (GNUNET_OK != + TMH_PG_start_read_committed (pg, + "insert transfer details")) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + /* lookup credit serial */ + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (exchange_url), + GNUNET_PQ_query_param_string (payto_uri), + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (wtid), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("credit_serial", + &credit_serial), + GNUNET_PQ_result_spec_end + }; + + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_credit_serial", + params, + rs); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + TMH_PG_rollback (pg); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "'lookup_credit_serial' for account %s and amount %s failed with status %d\n", + payto_uri, + TALER_amount2s (&td->total_amount), + qs); + return qs; + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + TMH_PG_rollback (pg); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "'lookup_credit_serial' for account %s failed with transfer unknown\n", + payto_uri); + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + } + } + + /* update merchant_transfer_signatures table */ + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&credit_serial), + TALER_PQ_query_param_amount_tuple (pg->conn, + &td->total_amount), + TALER_PQ_query_param_amount_tuple (pg->conn, + &td->wire_fee), + GNUNET_PQ_query_param_timestamp (&td->execution_time), + GNUNET_PQ_query_param_auto_from_type (&td->exchange_sig), + GNUNET_PQ_query_param_auto_from_type (&td->exchange_pub), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_transfer_signature", + params); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + TMH_PG_rollback (pg); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "'insert_transfer_signature' failed with status %d\n", + qs); + return qs; + } + if (0 == qs) + { + TMH_PG_rollback (pg); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "'insert_transfer_signature' failed with status %d\n", + qs); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } + + /* Update transfer-coin association table */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Updating transfer-coin association table\n"); + for (unsigned int i = 0; i<td->details_length; i++) + { + const struct TALER_TrackTransferDetails *d = &td->details[i]; + uint64_t i64 = (uint64_t) i; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&credit_serial), + GNUNET_PQ_query_param_uint64 (&i64), + TALER_PQ_query_param_amount_tuple (pg->conn, + &d->coin_value), + TALER_PQ_query_param_amount_tuple (pg->conn, + &d->coin_fee), /* deposit fee */ + GNUNET_PQ_query_param_auto_from_type (&d->coin_pub), + GNUNET_PQ_query_param_auto_from_type (&d->h_contract_terms), + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_transfer_to_coin_mapping", + params); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + TMH_PG_rollback (pg); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "'insert_transfer_to_coin_mapping' failed with status %d\n", + qs); + return qs; + } + if (0 == qs) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "'insert_transfer_to_coin_mapping' failed at %u: deposit unknown\n", + i); + } + } + /* Update merchant_contract_terms 'wired' status: for all coins + that were wired, set the respective order's "wired" status to + true, *if* all other deposited coins associated with that order + have also been wired (this time or earlier) */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Updating contract terms 'wired' status\n"); + for (unsigned int i = 0; i<td->details_length; i++) + { + const struct TALER_TrackTransferDetails *d = &td->details[i]; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (&d->coin_pub), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "update_wired_by_coin_pub", + params); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + TMH_PG_rollback (pg); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "'update_wired_by_coin_pub' failed with status %d\n", + qs); + return qs; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Committing transaction...\n"); + qs = TMH_PG_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; +} diff --git a/src/backenddb/pg_insert_transfer_details.h b/src/backenddb/pg_insert_transfer_details.h new file mode 100644 index 00000000..8980024e --- /dev/null +++ b/src/backenddb/pg_insert_transfer_details.h @@ -0,0 +1,51 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_insert_transfer_details.h + * @brief implementation of the insert_transfer_details function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_INSERT_TRANSFER_DETAILS_H +#define PG_INSERT_TRANSFER_DETAILS_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + + +/** + * Insert information about a wire transfer the merchant has received. + * + * @param cls closure + * @param instance_id instance to provide transfer details for + * @param exchange_url which exchange made the transfer + * @param payto_uri what is the merchant's bank account that received the transfer + * @param wtid identifier of the wire transfer + * @param td transfer details to store + * @return transaction status, + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the @a wtid and @a exchange_uri are not known for this @a instance_id + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT on success + */ +enum GNUNET_DB_QueryStatus +TMH_PG_insert_transfer_details ( + void *cls, + const char *instance_id, + const char *exchange_url, + const char *payto_uri, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_EXCHANGE_TransferData *td); + +#endif diff --git a/src/backenddb/pg_lookup_deposits.c b/src/backenddb/pg_lookup_deposits.c index 27647dda..01e35183 100644 --- a/src/backenddb/pg_lookup_deposits.c +++ b/src/backenddb/pg_lookup_deposits.c @@ -145,14 +145,10 @@ TMH_PG_lookup_deposits ( "SELECT" " exchange_url" ",coin_pub" - ",amount_with_fee_val" - ",amount_with_fee_frac" - ",deposit_fee_val" - ",deposit_fee_frac" - ",refund_fee_val" - ",refund_fee_frac" - ",wire_fee_val" - ",wire_fee_frac" + ",amount_with_fee" + ",deposit_fee" + ",refund_fee" + ",wire_fee" " FROM merchant_deposits" " WHERE order_serial=" " (SELECT order_serial" diff --git a/src/backenddb/pg_lookup_instances.c b/src/backenddb/pg_lookup_instances.c index 7dcbcb31..ada20134 100644 --- a/src/backenddb/pg_lookup_instances.c +++ b/src/backenddb/pg_lookup_instances.c @@ -392,10 +392,8 @@ TMH_PG_lookup_instances (void *cls, ",user_type" ",address" ",jurisdiction" - ",default_max_deposit_fee_val" - ",default_max_deposit_fee_frac" - ",default_max_wire_fee_val" - ",default_max_wire_fee_frac" + ",default_max_deposit_fee" + ",default_max_wire_fee" ",default_wire_fee_amortization" ",default_wire_transfer_delay" ",default_pay_delay" @@ -447,10 +445,8 @@ TMH_PG_lookup_instance (void *cls, ",user_type" ",address" ",jurisdiction" - ",default_max_deposit_fee_val" - ",default_max_deposit_fee_frac" - ",default_max_wire_fee_val" - ",default_max_wire_fee_frac" + ",default_max_deposit_fee" + ",default_max_wire_fee" ",default_wire_fee_amortization" ",default_wire_transfer_delay" ",default_pay_delay" diff --git a/src/backenddb/pg_lookup_product.c b/src/backenddb/pg_lookup_product.c index d9eb0915..04c27dff 100644 --- a/src/backenddb/pg_lookup_product.c +++ b/src/backenddb/pg_lookup_product.c @@ -87,8 +87,7 @@ TMH_PG_lookup_product (void *cls, " description" ",description_i18n" ",unit" - ",price_val" - ",price_frac" + ",price" ",taxes" ",total_stock" ",total_sold" diff --git a/src/backenddb/pg_lookup_refunds.c b/src/backenddb/pg_lookup_refunds.c index bc943162..e53860c2 100644 --- a/src/backenddb/pg_lookup_refunds.c +++ b/src/backenddb/pg_lookup_refunds.c @@ -128,8 +128,7 @@ TMH_PG_lookup_refunds ( "lookup_refunds", "SELECT" " coin_pub" - ",refund_amount_val" - ",refund_amount_frac" + ",refund_amount" " FROM merchant_refunds" " WHERE order_serial=" " (SELECT order_serial" diff --git a/src/backenddb/pg_lookup_reserves.c b/src/backenddb/pg_lookup_reserves.c index 6e27fa6f..2e503bb3 100644 --- a/src/backenddb/pg_lookup_reserves.c +++ b/src/backenddb/pg_lookup_reserves.c @@ -194,14 +194,10 @@ TMH_PG_lookup_reserves (void *cls, " reserve_pub" ",creation_time" ",expiration" - ",merchant_initial_balance_val" - ",merchant_initial_balance_frac" - ",exchange_initial_balance_val" - ",exchange_initial_balance_frac" - ",rewards_committed_val" - ",rewards_committed_frac" - ",rewards_picked_up_val" - ",rewards_picked_up_frac" + ",merchant_initial_balance" + ",exchange_initial_balance" + ",rewards_committed" + ",rewards_picked_up" ",reserve_priv IS NOT NULL AS active" " FROM merchant_reward_reserves" " FULL OUTER JOIN merchant_reward_reserve_keys USING (reserve_serial)" diff --git a/src/backenddb/pg_lookup_transfers.c b/src/backenddb/pg_lookup_transfers.c index 02faca2b..9fb5cdcb 100644 --- a/src/backenddb/pg_lookup_transfers.c +++ b/src/backenddb/pg_lookup_transfers.c @@ -180,8 +180,7 @@ TMH_PG_lookup_transfers (void *cls, PREPARE (pg, "lookup_transfers_time_payto_asc", "SELECT" - " mt.credit_amount_val" - ",mt.credit_amount_frac" + " mt.credit_amount" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -205,8 +204,7 @@ TMH_PG_lookup_transfers (void *cls, PREPARE (pg, "lookup_transfers_time_payto_desc", "SELECT" - " mt.credit_amount_val" - ",mt.credit_amount_frac" + " mt.credit_amount" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -250,8 +248,7 @@ TMH_PG_lookup_transfers (void *cls, PREPARE (pg, "lookup_transfers_time_asc", "SELECT" - " mt.credit_amount_val" - ",mt.credit_amount_frac" + " mt.credit_amount" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -274,8 +271,7 @@ TMH_PG_lookup_transfers (void *cls, PREPARE (pg, "lookup_transfers_time_desc", "SELECT" - " mt.credit_amount_val" - ",mt.credit_amount_frac" + " mt.credit_amount" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -320,8 +316,7 @@ TMH_PG_lookup_transfers (void *cls, PREPARE (pg, "lookup_transfers_payto_asc", "SELECT" - " mt.credit_amount_val" - ",mt.credit_amount_frac" + " mt.credit_amount" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -346,8 +341,7 @@ TMH_PG_lookup_transfers (void *cls, PREPARE (pg, "lookup_transfers_payto_desc", "SELECT" - " mt.credit_amount_val" - ",mt.credit_amount_frac" + " mt.credit_amount" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -390,8 +384,7 @@ TMH_PG_lookup_transfers (void *cls, PREPARE (pg, "lookup_transfers_desc", "SELECT" - " mt.credit_amount_val" - ",mt.credit_amount_frac" + " mt.credit_amount" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -415,8 +408,7 @@ TMH_PG_lookup_transfers (void *cls, PREPARE (pg, "lookup_transfers_asc", "SELECT" - " mt.credit_amount_val" - ",mt.credit_amount_frac" + " mt.credit_amount" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" diff --git a/src/backenddb/pg_refund_coin.c b/src/backenddb/pg_refund_coin.c index 3b124612..afbcebcc 100644 --- a/src/backenddb/pg_refund_coin.c +++ b/src/backenddb/pg_refund_coin.c @@ -50,8 +50,7 @@ TMH_PG_refund_coin (void *cls, ",refund_timestamp" ",coin_pub" ",reason" - ",refund_amount_val" - ",refund_amount_frac" + ",refund_amount" ") " "SELECT " " order_serial" @@ -59,8 +58,7 @@ TMH_PG_refund_coin (void *cls, ",$3" ",coin_pub" ",$5" - ",amount_with_fee_val" - ",amount_with_fee_frac" + ",amount_with_fee" " FROM merchant_deposits" " WHERE coin_pub=$4" " AND order_serial=" diff --git a/src/backenddb/pg_select_open_transfers.c b/src/backenddb/pg_select_open_transfers.c index ac8b2b63..44fdd695 100644 --- a/src/backenddb/pg_select_open_transfers.c +++ b/src/backenddb/pg_select_open_transfers.c @@ -144,8 +144,7 @@ TMH_PG_select_open_transfers (void *cls, ",exchange_url" ",payto_uri" ",wtid" - ",credit_amount_val" - ",credit_amount_frac" + ",credit_amount" ",ready_time AS next_attempt" " FROM merchant_transfers" " JOIN merchant_accounts" diff --git a/src/backenddb/pg_set_transfer_status_to_confirmed.c b/src/backenddb/pg_set_transfer_status_to_confirmed.c index 700965a1..767cd0d6 100644 --- a/src/backenddb/pg_set_transfer_status_to_confirmed.c +++ b/src/backenddb/pg_set_transfer_status_to_confirmed.c @@ -39,7 +39,8 @@ TMH_PG_set_transfer_status_to_confirmed ( GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_auto_from_type (wtid), GNUNET_PQ_query_param_string (exchange_url), - TALER_PQ_query_param_amount (amount), + TALER_PQ_query_param_amount_tuple (pg->conn, + amount), GNUNET_PQ_query_param_end }; @@ -49,8 +50,7 @@ TMH_PG_set_transfer_status_to_confirmed ( "UPDATE merchant_transfers SET" " confirmed=TRUE" " WHERE wtid=$2" - " AND credit_amount_val=$4" - " AND credit_amount_frac=$5" + " AND credit_amount=cast($4 AS taler_amount)" " AND exchange_url=$3" " AND account_serial IN" " (SELECT account_serial" diff --git a/src/backenddb/pg_store_wire_fee_by_exchange.c b/src/backenddb/pg_store_wire_fee_by_exchange.c new file mode 100644 index 00000000..f552b1fe --- /dev/null +++ b/src/backenddb/pg_store_wire_fee_by_exchange.c @@ -0,0 +1,76 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_store_wire_fee_by_exchange.c + * @brief Implementation of the store_wire_fee_by_exchange function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_store_wire_fee_by_exchange.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TMH_PG_store_wire_fee_by_exchange ( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub, + const struct GNUNET_HashCode *h_wire_method, + const struct TALER_WireFeeSet *fees, + struct GNUNET_TIME_Timestamp start_date, + struct GNUNET_TIME_Timestamp end_date, + const struct TALER_MasterSignatureP *master_sig) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (master_pub), + GNUNET_PQ_query_param_auto_from_type (h_wire_method), + TALER_PQ_query_param_amount_tuple (pg->conn, + &fees->wire), + TALER_PQ_query_param_amount_tuple (pg->conn, + &fees->closing), + GNUNET_PQ_query_param_timestamp (&start_date), + GNUNET_PQ_query_param_timestamp (&end_date), + GNUNET_PQ_query_param_auto_from_type (master_sig), + GNUNET_PQ_query_param_end + }; + + /* no preflight check here, run in its own transaction by the caller */ + PREPARE (pg, + "insert_wire_fee", + "INSERT INTO merchant_exchange_wire_fees" + "(master_pub" + ",h_wire_method" + ",wire_fee" + ",closing_fee" + ",start_date" + ",end_date" + ",master_sig)" + " VALUES " + "($1, $2, $3, $4, $5, $6, $7)" + " ON CONFLICT DO NOTHING"); + check_connection (pg); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Storing wire fee for %s starting at %s of %s\n", + TALER_B2S (master_pub), + GNUNET_TIME_timestamp2s (start_date), + TALER_amount2s (&fees->wire)); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_wire_fee", + params); +} diff --git a/src/backenddb/pg_store_wire_fee_by_exchange.h b/src/backenddb/pg_store_wire_fee_by_exchange.h new file mode 100644 index 00000000..53ff4c3d --- /dev/null +++ b/src/backenddb/pg_store_wire_fee_by_exchange.h @@ -0,0 +1,52 @@ +/* + This file is part of TALER + Copyright (C) 2022 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/> + */ +/** + * @file backenddb/pg_postgres_store_wire_fee_by_exchange.h + * @brief implementation of the postgres_store_wire_fee_by_exchange function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_POSTGRES_STORE_WIRE_FEE_BY_EXCHANGE_H +#define PG_POSTGRES_STORE_WIRE_FEE_BY_EXCHANGE_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + + +/** + * Store information about wire fees charged by an exchange, + * including signature (so we have proof). + * + * @param cls closure + * @param master_pub public key of the exchange + * @param h_wire_method hash of wire method + * @param fees the fee charged + * @param start_date start of fee being used + * @param end_date end of fee being used + * @param master_sig signature of exchange over fee structure + * @return transaction status code + */ +enum GNUNET_DB_QueryStatus +TMH_PG_store_wire_fee_by_exchange ( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub, + const struct GNUNET_HashCode *h_wire_method, + const struct TALER_WireFeeSet *fees, + struct GNUNET_TIME_Timestamp start_date, + struct GNUNET_TIME_Timestamp end_date, + const struct TALER_MasterSignatureP *master_sig); + +#endif diff --git a/src/backenddb/pg_update_instance.c b/src/backenddb/pg_update_instance.c index 5de6032d..b94fda08 100644 --- a/src/backenddb/pg_update_instance.c +++ b/src/backenddb/pg_update_instance.c @@ -16,6 +16,7 @@ /** * @file backenddb/pg_update_instance.c * @brief Implementation of the update_instance function for Postgres + * @author Christian Grothoff * @author Iván Ávalos */ #include "platform.h" @@ -36,8 +37,10 @@ TMH_PG_update_instance (void *cls, 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), + TALER_PQ_query_param_amount_tuple (pg->conn, + &is->default_max_deposit_fee), + TALER_PQ_query_param_amount_tuple (pg->conn, + &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), @@ -62,17 +65,15 @@ TMH_PG_update_instance (void *cls, " merchant_name=$2" ",address=$3" ",jurisdiction=$4" - ",default_max_deposit_fee_val=$5" - ",default_max_deposit_fee_frac=$6" - ",default_max_wire_fee_val=$7" - ",default_max_wire_fee_frac=$8" - ",default_wire_fee_amortization=$9" - ",default_wire_transfer_delay=$10" - ",default_pay_delay=$11" - ",website=$12" - ",email=$13" - ",logo=$14" - ",user_type=$15" + ",default_max_deposit_fee=$5" + ",default_max_wire_fee=$6" + ",default_wire_fee_amortization=$7" + ",default_wire_transfer_delay=$8" + ",default_pay_delay=$9" + ",website=$10" + ",email=$11" + ",logo=$12" + ",user_type=$13" " WHERE merchant_id=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "update_instance", diff --git a/src/backenddb/pg_update_product.c b/src/backenddb/pg_update_product.c index b571a8fe..ea845781 100644 --- a/src/backenddb/pg_update_product.c +++ b/src/backenddb/pg_update_product.c @@ -16,6 +16,7 @@ /** * @file backenddb/pg_update_product.c * @brief Implementation of the update_product function for Postgres + * @author Christian Grothoff * @author Iván Ávalos */ #include "platform.h" @@ -40,8 +41,9 @@ TMH_PG_update_product (void *cls, GNUNET_PQ_query_param_string (pd->unit), GNUNET_PQ_query_param_string (pd->image), /* $6 */ TALER_PQ_query_param_json (pd->taxes), - TALER_PQ_query_param_amount (&pd->price), /* $8+$9 */ - GNUNET_PQ_query_param_uint64 (&pd->total_stock), /* $10 */ + TALER_PQ_query_param_amount_tuple (pg->conn, + &pd->price), /* $8 */ + GNUNET_PQ_query_param_uint64 (&pd->total_stock), /* $9 */ GNUNET_PQ_query_param_uint64 (&pd->total_lost), TALER_PQ_query_param_json (pd->address), GNUNET_PQ_query_param_timestamp (&pd->next_restock), @@ -65,20 +67,19 @@ TMH_PG_update_product (void *cls, ",unit=$5" ",image=$6" ",taxes=$7" - ",price_val=$8" - ",price_frac=$9" - ",total_stock=$10" - ",total_lost=$11" - ",address=$12" - ",next_restock=$13" - ",minimum_age=$14" + ",price=$8" + ",total_stock=$9" + ",total_lost=$10" + ",address=$11" + ",next_restock=$12" + ",minimum_age=$13" " WHERE merchant_serial=" " (SELECT merchant_serial" " FROM merchant_instances" " WHERE merchant_id=$1)" " AND product_id=$2" - " AND total_stock <= $10" - " AND total_lost <= $11"); + " AND total_stock <= $9" + " AND total_lost <= $10"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "update_product", params); diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index f129b90b..016cb4e2 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -84,6 +84,15 @@ #include "pg_set_transfer_status_to_confirmed.h" #include "pg_insert_exchange_keys.h" #include "pg_select_exchange_keys.h" +#include "pg_insert_deposit_to_transfer.h" +#include "pg_increase_refund.h" +#include "pg_insert_transfer.h" +#include "pg_insert_transfer_details.h" +#include "pg_store_wire_fee_by_exchange.h" +#include "pg_insert_reserve.h" +#include "pg_activate_reserve.h" +#include "pg_authorize_reward.h" +#include "pg_insert_pickup.h" /** @@ -234,126 +243,6 @@ check_connection (struct PostgresClosure *pg) /** - * Start a transaction. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param name unique name identifying the transaction (for debugging), - * must point to a constant - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -postgres_start (void *cls, - const char *name) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_ExecuteStatement es[] = { - GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"), - GNUNET_PQ_EXECUTE_STATEMENT_END - }; - - check_connection (pg); - postgres_preflight (pg); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Starting merchant DB transaction `%s'\n", - name); - if (GNUNET_OK != - GNUNET_PQ_exec_statements (pg->conn, - es)) - { - TALER_LOG_ERROR ("Failed to start transaction\n"); - GNUNET_break (0); - return GNUNET_SYSERR; - } - pg->transaction_name = name; - return GNUNET_OK; -} - - -/** - * Start a transaction in 'read committed' mode. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param name unique name identifying the transaction (for debugging), - * must point to a constant - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -postgres_start_read_committed (void *cls, - const char *name) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_ExecuteStatement es[] = { - GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL READ COMMITTED"), - GNUNET_PQ_EXECUTE_STATEMENT_END - }; - - check_connection (pg); - postgres_preflight (pg); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Starting merchant DB transaction %s (READ COMMITTED)\n", - name); - if (GNUNET_OK != - GNUNET_PQ_exec_statements (pg->conn, - es)) - { - TALER_LOG_ERROR ("Failed to start transaction\n"); - GNUNET_break (0); - return GNUNET_SYSERR; - } - pg->transaction_name = name; - return GNUNET_OK; -} - - -/** - * Roll back the current transaction of a database connection. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - */ -static void -postgres_rollback (void *cls) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_ExecuteStatement es[] = { - GNUNET_PQ_make_execute ("ROLLBACK"), - GNUNET_PQ_EXECUTE_STATEMENT_END - }; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Rolling back merchant DB transaction `%s'\n", - pg->transaction_name); - GNUNET_break (GNUNET_OK == - GNUNET_PQ_exec_statements (pg->conn, - es)); - pg->transaction_name = NULL; -} - - -/** - * Commit the current transaction of a database connection. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -postgres_commit (void *cls) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_end - }; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Committing merchant DB transaction %s\n", - pg->transaction_name); - pg->transaction_name = NULL; - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "end_transaction", - params); -} - - -/** * Closure for lookup_deposits_by_order_cb(). */ struct LookupDepositsByOrderContext @@ -616,37 +505,6 @@ postgres_lookup_transfer_details_by_order ( /** - * Insert wire transfer details for a deposit. - * - * @param cls closure - * @param deposit_serial serial number of the deposit - * @param dd deposit transfer data from the exchange to store - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_insert_deposit_to_transfer ( - void *cls, - uint64_t deposit_serial, - const struct TALER_EXCHANGE_DepositData *dd) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&deposit_serial), - TALER_PQ_query_param_amount (&dd->coin_contribution), - GNUNET_PQ_query_param_timestamp (&dd->execution_time), - GNUNET_PQ_query_param_auto_from_type (&dd->exchange_sig), - GNUNET_PQ_query_param_auto_from_type (&dd->exchange_pub), - GNUNET_PQ_query_param_auto_from_type (&dd->wtid), - GNUNET_PQ_query_param_end - }; - - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_deposit_to_transfer", - params); -} - - -/** * Set 'wired' status for an order to 'true'. * * @param cls closure @@ -670,452 +528,6 @@ postgres_mark_order_wired (void *cls, /** - * Closure for #process_refund_cb(). - */ -struct FindRefundContext -{ - - /** - * Plugin context. - */ - struct PostgresClosure *pg; - - /** - * Updated to reflect total amount refunded so far. - */ - struct TALER_Amount refunded_amount; - - /** - * Set to the largest refund transaction ID encountered. - */ - uint64_t max_rtransaction_id; - - /** - * Set to true on hard errors. - */ - bool err; -}; - - -/** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. - * - * @param cls closure, our `struct FindRefundContext` - * @param result the postgres result - * @param num_results the number of results in @a result - */ -static void -process_refund_cb (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct FindRefundContext *ictx = cls; - struct PostgresClosure *pg = ictx->pg; - - for (unsigned int i = 0; i<num_results; i++) - { - /* Sum up existing refunds */ - struct TALER_Amount acc; - uint64_t rtransaction_id; - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", - &acc), - GNUNET_PQ_result_spec_uint64 ("rtransaction_id", - &rtransaction_id), - GNUNET_PQ_result_spec_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - ictx->err = true; - return; - } - if (0 > - TALER_amount_add (&ictx->refunded_amount, - &ictx->refunded_amount, - &acc)) - { - GNUNET_break (0); - ictx->err = true; - return; - } - ictx->max_rtransaction_id = GNUNET_MAX (ictx->max_rtransaction_id, - rtransaction_id); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Found refund of %s\n", - TALER_amount2s (&acc)); - } -} - - -/** - * Closure for #process_deposits_for_refund_cb(). - */ -struct InsertRefundContext -{ - /** - * Used to provide a connection to the db - */ - struct PostgresClosure *pg; - - /** - * Amount to which increase the refund for this contract - */ - const struct TALER_Amount *refund; - - /** - * Human-readable reason behind this refund - */ - const char *reason; - - /** - * Transaction status code. - */ - enum TALER_MERCHANTDB_RefundStatus rs; -}; - - -/** - * Data extracted per coin. - */ -struct RefundCoinData -{ - - /** - * Public key of a coin. - */ - struct TALER_CoinSpendPublicKeyP coin_pub; - - /** - * Amount deposited for this coin. - */ - struct TALER_Amount deposited_with_fee; - - /** - * Amount refunded already for this coin. - */ - struct TALER_Amount refund_amount; - - /** - * Order serial (actually not really per-coin). - */ - uint64_t order_serial; - - /** - * Maximum rtransaction_id for this coin so far. - */ - uint64_t max_rtransaction_id; - -}; - - -/** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. - * - * @param cls closure, our `struct InsertRefundContext` - * @param result the postgres result - * @param num_results the number of results in @a result - */ -static void -process_deposits_for_refund_cb (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct InsertRefundContext *ctx = cls; - struct PostgresClosure *pg = ctx->pg; - struct TALER_Amount current_refund; - struct RefundCoinData rcd[GNUNET_NZL (num_results)]; - struct GNUNET_TIME_Timestamp now; - - now = GNUNET_TIME_timestamp_get (); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (ctx->refund->currency, - ¤t_refund)); - memset (rcd, 0, sizeof (rcd)); - /* Pass 1: Collect amount of existing refunds into current_refund. - * Also store existing refunded amount for each deposit in deposit_refund. */ - for (unsigned int i = 0; i<num_results; i++) - { - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("coin_pub", - &rcd[i].coin_pub), - GNUNET_PQ_result_spec_uint64 ("order_serial", - &rcd[i].order_serial), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", - &rcd[i].deposited_with_fee), - GNUNET_PQ_result_spec_end - }; - struct FindRefundContext ictx = { - .pg = pg - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; - return; - } - - { - enum GNUNET_DB_QueryStatus ires; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub), - GNUNET_PQ_query_param_uint64 (&rcd[i].order_serial), - GNUNET_PQ_query_param_end - }; - - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (ctx->refund->currency, - &ictx.refunded_amount)); - ires = GNUNET_PQ_eval_prepared_multi_select (ctx->pg->conn, - "find_refunds_by_coin", - params, - &process_refund_cb, - &ictx); - if ( (ictx.err) || - (GNUNET_DB_STATUS_HARD_ERROR == ires) ) - { - GNUNET_break (0); - ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; - return; - } - if (GNUNET_DB_STATUS_SOFT_ERROR == ires) - { - ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR; - return; - } - } - if (0 > - TALER_amount_add (¤t_refund, - ¤t_refund, - &ictx.refunded_amount)) - { - GNUNET_break (0); - ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; - return; - } - rcd[i].refund_amount = ictx.refunded_amount; - rcd[i].max_rtransaction_id = ictx.max_rtransaction_id; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Existing refund for coin %s is %s\n", - TALER_B2S (&rcd[i].coin_pub), - TALER_amount2s (&ictx.refunded_amount)); - } - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Total existing refund is %s\n", - TALER_amount2s (¤t_refund)); - - /* stop immediately if we are 'done' === amount already - * refunded. */ - if (0 >= TALER_amount_cmp (ctx->refund, - ¤t_refund)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Existing refund of %s at or above requested refund. Finished early.\n", - TALER_amount2s (¤t_refund)); - ctx->rs = TALER_MERCHANTDB_RS_SUCCESS; - return; - } - - /* Phase 2: Try to increase current refund until it matches desired refund */ - for (unsigned int i = 0; i<num_results; i++) - { - const struct TALER_Amount *increment; - struct TALER_Amount left; - struct TALER_Amount remaining_refund; - - /* How much of the coin is left after the existing refunds? */ - if (0 > - TALER_amount_subtract (&left, - &rcd[i].deposited_with_fee, - &rcd[i].refund_amount)) - { - GNUNET_break (0); - ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; - return; - } - - if ( (0 == left.value) && - (0 == left.fraction) ) - { - /* coin was fully refunded, move to next coin */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Coin %s fully refunded, moving to next coin\n", - TALER_B2S (&rcd[i].coin_pub)); - continue; - } - - rcd[i].max_rtransaction_id++; - /* How much of the refund is still to be paid back? */ - if (0 > - TALER_amount_subtract (&remaining_refund, - ctx->refund, - ¤t_refund)) - { - GNUNET_break (0); - ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; - return; - } - - /* By how much will we increase the refund for this coin? */ - if (0 >= TALER_amount_cmp (&remaining_refund, - &left)) - { - /* remaining_refund <= left */ - increment = &remaining_refund; - } - else - { - increment = &left; - } - - if (0 > - TALER_amount_add (¤t_refund, - ¤t_refund, - increment)) - { - GNUNET_break (0); - ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; - return; - } - - /* actually run the refund */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Coin %s deposit amount is %s\n", - TALER_B2S (&rcd[i].coin_pub), - TALER_amount2s (&rcd[i].deposited_with_fee)); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Coin %s refund will be incremented by %s\n", - TALER_B2S (&rcd[i].coin_pub), - TALER_amount2s (increment)); - { - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&rcd[i].order_serial), - GNUNET_PQ_query_param_uint64 (&rcd[i].max_rtransaction_id), /* already inc'ed */ - GNUNET_PQ_query_param_timestamp (&now), - GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub), - GNUNET_PQ_query_param_string (ctx->reason), - TALER_PQ_query_param_amount (increment), - GNUNET_PQ_query_param_end - }; - - check_connection (pg); - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_refund", - params); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - GNUNET_break (0); - ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; - return; - case GNUNET_DB_STATUS_SOFT_ERROR: - ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR; - return; - default: - ctx->rs = (enum TALER_MERCHANTDB_RefundStatus) qs; - break; - } - } - - /* stop immediately if we are done */ - if (0 == TALER_amount_cmp (ctx->refund, - ¤t_refund)) - { - ctx->rs = TALER_MERCHANTDB_RS_SUCCESS; - return; - } - } - - /** - * We end up here if not all of the refund has been covered. - * Although this should be checked as the business should never - * issue a refund bigger than the contract's actual price, we cannot - * rely upon the frontend being correct. - */ - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "The refund of %s is bigger than the order's value\n", - TALER_amount2s (ctx->refund)); - ctx->rs = TALER_MERCHANTDB_RS_TOO_HIGH; -} - - -/** - * Function called when some backoffice staff decides to award or - * increase the refund on an existing contract. This function - * MUST be called from within a transaction scope setup by the - * caller as it executes mulrewardle SQL statements. - * - * @param cls closure - * @param instance_id instance identifier - * @param order_id the order to increase the refund for - * @param refund maximum refund to return to the customer for this contract - * @param reason 0-terminated UTF-8 string giving the reason why the customer - * got a refund (free form, business-specific) - * @return transaction status - * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a refund is ABOVE the amount we - * were originally paid and thus the transaction failed; - * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid, - * regardless of whether it actually increased the refund beyond - * what was already refunded (idempotency!) - */ -static enum TALER_MERCHANTDB_RefundStatus -postgres_increase_refund (void *cls, - const char *instance_id, - const char *order_id, - const struct TALER_Amount *refund, - const char *reason) -{ - struct PostgresClosure *pg = cls; - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (order_id), - GNUNET_PQ_query_param_end - }; - struct InsertRefundContext ctx = { - .pg = pg, - .refund = refund, - .reason = reason - }; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Asked to refund %s on order %s\n", - TALER_amount2s (refund), - order_id); - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "find_deposits_for_refund", - params, - &process_deposits_for_refund_cb, - &ctx); - switch (qs) - { - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - /* never paid, means we clearly cannot refund anything */ - return TALER_MERCHANTDB_RS_NO_SUCH_ORDER; - case GNUNET_DB_STATUS_SOFT_ERROR: - return TALER_MERCHANTDB_RS_SOFT_ERROR; - case GNUNET_DB_STATUS_HARD_ERROR: - return TALER_MERCHANTDB_RS_HARD_ERROR; - default: - /* Got one or more deposits */ - return ctx.rs; - } -} - - -/** * Closure for #lookup_refunds_detailed_cb(). */ struct LookupRefundsDetailedContext @@ -1365,47 +777,6 @@ postgres_lookup_order_by_fulfillment (void *cls, /** - * Insert information about a wire transfer the merchant has received. - * - * @param cls closure - * @param instance_id the instance that received the transfer - * @param exchange_url which exchange made the transfer - * @param wtid identifier of the wire transfer - * @param credit_amount how much did we receive - * @param payto_uri what is the merchant's bank account that received the transfer - * @param confirmed whether the transfer was confirmed by the merchant or - * was merely claimed by the exchange at this point - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_insert_transfer ( - void *cls, - const char *instance_id, - const char *exchange_url, - const struct TALER_WireTransferIdentifierRawP *wtid, - const struct TALER_Amount *credit_amount, - const char *payto_uri, - bool confirmed) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (exchange_url), - GNUNET_PQ_query_param_auto_from_type (wtid), - TALER_PQ_query_param_amount (credit_amount), - GNUNET_PQ_query_param_string (payto_uri), - GNUNET_PQ_query_param_bool (confirmed), - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_end - }; - - check_connection (pg); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_transfer", - params); -} - - -/** * Delete information about a transfer. Note that transfers * confirmed by the exchange cannot be deleted anymore. * @@ -1503,204 +874,6 @@ postgres_lookup_account (void *cls, /** - * Insert information about a wire transfer the merchant has received. - * - * @param cls closure - * @param instance_id instance to provide transfer details for - * @param exchange_url which exchange made the transfer - * @param payto_uri what is the merchant's bank account that received the transfer - * @param wtid identifier of the wire transfer - * @param td transfer details to store - * @return transaction status, - * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the @a wtid and @a exchange_uri are not known for this @a instance_id - * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT on success - */ -static enum GNUNET_DB_QueryStatus -postgres_insert_transfer_details ( - void *cls, - const char *instance_id, - const char *exchange_url, - const char *payto_uri, - const struct TALER_WireTransferIdentifierRawP *wtid, - const struct TALER_EXCHANGE_TransferData *td) -{ - struct PostgresClosure *pg = cls; - enum GNUNET_DB_QueryStatus qs; - uint64_t credit_serial; - unsigned int retries; - - retries = 0; - check_connection (pg); -RETRY: - if (MAX_RETRIES < ++retries) - return GNUNET_DB_STATUS_SOFT_ERROR; - if (GNUNET_OK != - postgres_start_read_committed (pg, - "insert transfer details")) - { - GNUNET_break (0); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - /* lookup credit serial */ - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (exchange_url), - GNUNET_PQ_query_param_string (payto_uri), - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (wtid), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("credit_serial", - &credit_serial), - GNUNET_PQ_result_spec_end - }; - - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_credit_serial", - params, - rs); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - postgres_rollback (pg); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "'lookup_credit_serial' for account %s and amount %s failed with status %d\n", - payto_uri, - TALER_amount2s (&td->total_amount), - qs); - return qs; - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - postgres_rollback (pg); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "'lookup_credit_serial' for account %s failed with transfer unknown\n", - payto_uri); - return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; - } - } - - /* update merchant_transfer_signatures table */ - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&credit_serial), - TALER_PQ_query_param_amount (&td->total_amount), - TALER_PQ_query_param_amount (&td->wire_fee), - GNUNET_PQ_query_param_timestamp (&td->execution_time), - GNUNET_PQ_query_param_auto_from_type (&td->exchange_sig), - GNUNET_PQ_query_param_auto_from_type (&td->exchange_pub), - GNUNET_PQ_query_param_end - }; - - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_transfer_signature", - params); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - postgres_rollback (pg); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "'insert_transfer_signature' failed with status %d\n", - qs); - return qs; - } - if (0 == qs) - { - postgres_rollback (pg); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "'insert_transfer_signature' failed with status %d\n", - qs); - return GNUNET_DB_STATUS_HARD_ERROR; - } - } - - /* Update transfer-coin association table */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Updating transfer-coin association table\n"); - for (unsigned int i = 0; i<td->details_length; i++) - { - const struct TALER_TrackTransferDetails *d = &td->details[i]; - uint64_t i64 = (uint64_t) i; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&credit_serial), - GNUNET_PQ_query_param_uint64 (&i64), - TALER_PQ_query_param_amount (&d->coin_value), - TALER_PQ_query_param_amount (&d->coin_fee), /* deposit fee */ - GNUNET_PQ_query_param_auto_from_type (&d->coin_pub), - GNUNET_PQ_query_param_auto_from_type (&d->h_contract_terms), - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_end - }; - - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_transfer_to_coin_mapping", - params); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - postgres_rollback (pg); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "'insert_transfer_to_coin_mapping' failed with status %d\n", - qs); - return qs; - } - if (0 == qs) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "'insert_transfer_to_coin_mapping' failed at %u: deposit unknown\n", - i); - } - } - /* Update merchant_contract_terms 'wired' status: for all coins - that were wired, set the respective order's "wired" status to - true, *if* all other deposited coins associated with that order - have also been wired (this time or earlier) */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Updating contract terms 'wired' status\n"); - for (unsigned int i = 0; i<td->details_length; i++) - { - const struct TALER_TrackTransferDetails *d = &td->details[i]; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (&d->coin_pub), - GNUNET_PQ_query_param_end - }; - - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_wired_by_coin_pub", - params); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - postgres_rollback (pg); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "'update_wired_by_coin_pub' failed with status %d\n", - qs); - return qs; - } - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Committing transaction...\n"); - qs = postgres_commit (pg); - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - return qs; -} - - -/** * Obtain information about wire fees charged by an exchange, * including signature (so we have proof). * @@ -2249,187 +1422,6 @@ postgres_lookup_transfer_details ( /** - * Store information about wire fees charged by an exchange, - * including signature (so we have proof). - * - * @param cls closure - * @param master_pub public key of the exchange - * @param h_wire_method hash of wire method - * @param fees the fee charged - * @param start_date start of fee being used - * @param end_date end of fee being used - * @param master_sig signature of exchange over fee structure - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -postgres_store_wire_fee_by_exchange ( - void *cls, - const struct TALER_MasterPublicKeyP *master_pub, - const struct GNUNET_HashCode *h_wire_method, - const struct TALER_WireFeeSet *fees, - struct GNUNET_TIME_Timestamp start_date, - struct GNUNET_TIME_Timestamp end_date, - const struct TALER_MasterSignatureP *master_sig) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (master_pub), - GNUNET_PQ_query_param_auto_from_type (h_wire_method), - TALER_PQ_query_param_amount (&fees->wire), - TALER_PQ_query_param_amount (&fees->closing), - GNUNET_PQ_query_param_timestamp (&start_date), - GNUNET_PQ_query_param_timestamp (&end_date), - GNUNET_PQ_query_param_auto_from_type (master_sig), - GNUNET_PQ_query_param_end - }; - - /* no preflight check here, run in its own transaction by the caller */ - check_connection (pg); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Storing wire fee for %s starting at %s of %s\n", - TALER_B2S (master_pub), - GNUNET_TIME_timestamp2s (start_date), - TALER_amount2s (&fees->wire)); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_wire_fee", - params); -} - - -/** - * Add @a credit to a reserve to be used for rewardping. Note that - * this function does not actually perform any wire transfers to - * credit the reserve, it merely tells the merchant backend that - * a reserve now exists. This has to happen before rewards can be - * authorized. - * - * @param cls closure, typically a connection to the db - * @param instance_id which instance is the reserve tied to - * @param reserve_priv which reserve is topped up or created - * @param reserve_pub which reserve is topped up or created - * @param master_pub master public key of the exchange - * @param exchange_url what URL is the exchange reachable at where the reserve is located - * @param initial_balance how much money will be added to the reserve - * @param expiration when does the reserve expire? - * @return transaction status, usually - * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success - */ -static enum TALER_ErrorCode -postgres_insert_reserve (void *cls, - const char *instance_id, - const struct TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_MasterPublicKeyP *master_pub, - const char *exchange_url, - const struct TALER_Amount *initial_balance, - struct GNUNET_TIME_Timestamp expiration) -{ - struct PostgresClosure *pg = cls; - unsigned int retries; - enum GNUNET_DB_QueryStatus qs; - - retries = 0; - check_connection (pg); -RETRY: - if (MAX_RETRIES < ++retries) - return TALER_EC_GENERIC_DB_SOFT_FAILURE; - if (GNUNET_OK != - postgres_start (pg, - "insert reserve")) - { - GNUNET_break (0); - return TALER_EC_GENERIC_DB_START_FAILED; - } - - /* Setup reserve */ - { - struct GNUNET_TIME_Timestamp now; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (reserve_pub), - GNUNET_PQ_query_param_timestamp (&now), - GNUNET_PQ_query_param_timestamp (&expiration), - TALER_PQ_query_param_amount (initial_balance), - GNUNET_PQ_query_param_end - }; - - now = GNUNET_TIME_timestamp_get (); - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_reserve", - params); - if (0 > qs) - { - postgres_rollback (pg); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - return qs; - } - } - /* Store private key */ - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (reserve_pub), - GNUNET_PQ_query_param_auto_from_type (reserve_priv), - GNUNET_PQ_query_param_string (exchange_url), - GNUNET_PQ_query_param_auto_from_type (master_pub), - GNUNET_PQ_query_param_end - }; - - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_reserve_key", - params); - if (0 > qs) - { - postgres_rollback (pg); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - return qs; - } - } - qs = postgres_commit (pg); - if (0 <= qs) - return TALER_EC_NONE; /* success */ - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - return qs; -} - - -/** - * Confirms @a credit as the amount the exchange claims to have received and - * thus really 'activates' the reserve. This has to happen before rewards can - * be authorized. - * - * @param cls closure, typically a connection to the db - * @param instance_id which instance is the reserve tied to - * @param reserve_pub which reserve is topped up or created - * @param initial_exchange_balance how much money was be added to the reserve - * according to the exchange - * @return transaction status, usually - * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success - */ -static enum GNUNET_DB_QueryStatus -postgres_activate_reserve (void *cls, - const char *instance_id, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_Amount *initial_exchange_balance) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (reserve_pub), - TALER_PQ_query_param_amount (initial_exchange_balance), - GNUNET_PQ_query_param_end - }; - - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "activate_reserve", - params); -} - - -/** * Closure for #lookup_pending_reserves_cb. */ struct LookupPendingReservesContext @@ -2795,366 +1787,6 @@ postgres_purge_reserve (void *cls, /** - * Closure for #lookup_reserve_for_reward_cb(). - */ -struct LookupReserveForRewardContext -{ - /** - * Postgres context. - */ - struct PostgresClosure *pg; - - /** - * Public key of the reserve we found. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * How much money must be left in the reserve. - */ - struct TALER_Amount required_amount; - - /** - * Set to the expiration time of the reserve we found. - * #GNUNET_TIME_UNIT_FOREVER_ABS if we found none. - */ - struct GNUNET_TIME_Timestamp expiration; - - /** - * Error status. - */ - enum TALER_ErrorCode ec; - - /** - * Did we find a good reserve? - */ - bool ok; -}; - - -/** - * How long must a reserve be at least still valid before we use - * it for a reward? - */ -#define MIN_EXPIRATION GNUNET_TIME_UNIT_HOURS - - -/** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results about accounts. - * - * @param[in,out] cls of type `struct LookupReserveForRewardContext *` - * @param result the postgres result - * @param num_results the number of results in @a result - */ -static void -lookup_reserve_for_reward_cb (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct LookupReserveForRewardContext *lac = cls; - struct PostgresClosure *pg = lac->pg; - - for (unsigned int i = 0; i < num_results; i++) - { - struct TALER_ReservePublicKeyP reserve_pub; - struct TALER_Amount committed_amount; - struct TALER_Amount remaining; - struct TALER_Amount initial_balance; - struct GNUNET_TIME_Timestamp expiration; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("reserve_pub", - &reserve_pub), - TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance", - &initial_balance), - TALER_PQ_RESULT_SPEC_AMOUNT ("rewards_committed", - &committed_amount), - GNUNET_PQ_result_spec_timestamp ("expiration", - &expiration), - GNUNET_PQ_result_spec_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - lac->ec = TALER_EC_GENERIC_DB_FETCH_FAILED; - return; - } - if (0 > - TALER_amount_subtract (&remaining, - &initial_balance, - &committed_amount)) - { - GNUNET_break (0); - continue; - } - if (0 > - TALER_amount_cmp (&remaining, - &lac->required_amount)) - { - /* insufficient balance */ - if (lac->ok) - continue; /* got another reserve */ - lac->ec = - TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_INSUFFICIENT_FUNDS; - continue; - } - if ( (! GNUNET_TIME_absolute_is_never (lac->expiration.abs_time)) && - GNUNET_TIME_timestamp_cmp (expiration, - >, - lac->expiration) && - GNUNET_TIME_relative_cmp ( - GNUNET_TIME_absolute_get_remaining (lac->expiration.abs_time), - >, - MIN_EXPIRATION) ) - { - /* reserve expired */ - if (lac->ok) - continue; /* got another reserve */ - lac->ec = TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_EXPIRED; - continue; - } - lac->ok = true; - lac->ec = TALER_EC_NONE; - lac->expiration = expiration; - lac->reserve_pub = reserve_pub; - } -} - - -/** - * Authorize a reward over @a amount from reserve @a reserve_pub. Remember - * the authorization under @a reward_id for later, together with the - * @a justification. - * - * @param cls closure, typically a connection to the db - * @param instance_id which instance should generate the reward - * @param reserve_pub which reserve is debited, NULL to pick one in the DB - * @param amount how high is the reward (with fees) - * @param justification why was the reward approved - * @param next_url where to send the URL post reward pickup - * @param[out] reward_id set to the unique ID for the reward - * @param[out] expiration set to when the reward expires - * @return transaction status, - * #TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but has expired - * #TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_NOT_FOUND if the reserve is not known - * #TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has insufficient funds left - * #TALER_EC_GENERIC_DB_START_FAILED on hard DB errors - * #TALER_EC_GENERIC_DB_FETCH_FAILED on hard DB errors - * #TALER_EC_GENERIC_DB_STORE_FAILED on hard DB errors - * #TALER_EC_GENERIC_DB_INVARIANT_FAILURE on hard DB errors - * #TALER_EC_GENERIC_DB_SOFT_FAILURE soft DB errors (client should retry) - * #TALER_EC_NONE upon success - */ -static enum TALER_ErrorCode -postgres_authorize_reward (void *cls, - const char *instance_id, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_Amount *amount, - const char *justification, - const char *next_url, - struct TALER_RewardIdentifierP *reward_id, - struct GNUNET_TIME_Timestamp *expiration) -{ - struct PostgresClosure *pg = cls; - unsigned int retries = 0; - enum GNUNET_DB_QueryStatus qs; - struct TALER_Amount rewards_committed; - struct TALER_Amount exchange_initial_balance; - const struct TALER_ReservePublicKeyP *reserve_pubp; - struct LookupReserveForRewardContext lac = { - .pg = pg, - .required_amount = *amount, - .expiration = GNUNET_TIME_UNIT_FOREVER_TS - }; - - check_connection (pg); -RETRY: - reserve_pubp = reserve_pub; - if (MAX_RETRIES < ++retries) - { - GNUNET_break (0); - return - TALER_EC_GENERIC_DB_SOFT_FAILURE; - } - if (GNUNET_OK != - postgres_start (pg, - "authorize reward")) - { - GNUNET_break (0); - return TALER_EC_GENERIC_DB_START_FAILED; - } - if (NULL == reserve_pubp) - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_end - }; - - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_reserve_for_reward", - params, - &lookup_reserve_for_reward_cb, - &lac); - switch (qs) - { - case GNUNET_DB_STATUS_SOFT_ERROR: - postgres_rollback (pg); - goto RETRY; - case GNUNET_DB_STATUS_HARD_ERROR: - GNUNET_break (0); - postgres_rollback (pg); - return TALER_EC_GENERIC_DB_FETCH_FAILED; - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - postgres_rollback (pg); - return TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_NOT_FOUND; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - default: - break; - } - if (TALER_EC_NONE != lac.ec) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Enabling reward reserved failed with status %d\n", - lac.ec); - postgres_rollback (pg); - return lac.ec; - } - GNUNET_assert (lac.ok); - reserve_pubp = &lac.reserve_pub; - } - - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (reserve_pubp), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_timestamp ("expiration", - expiration), - TALER_PQ_RESULT_SPEC_AMOUNT ("rewards_committed", - &rewards_committed), - TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance", - &exchange_initial_balance), - GNUNET_PQ_result_spec_end - }; - - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_reserve_status", - params, - rs); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - postgres_rollback (pg); - goto RETRY; - } - if (qs < 0) - { - GNUNET_break (0); - postgres_rollback (pg); - return TALER_EC_GENERIC_DB_FETCH_FAILED; - } - if (0 == qs) - { - postgres_rollback (pg); - return TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_NOT_FOUND; - } - } - { - struct TALER_Amount remaining; - - if (0 > - TALER_amount_subtract (&remaining, - &exchange_initial_balance, - &rewards_committed)) - { - GNUNET_break (0); - postgres_rollback (pg); - return TALER_EC_GENERIC_DB_INVARIANT_FAILURE; - } - if (0 > - TALER_amount_cmp (&remaining, - amount)) - { - postgres_rollback (pg); - return TALER_EC_MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_INSUFFICIENT_FUNDS; - } - } - GNUNET_assert (0 <= - TALER_amount_add (&rewards_committed, - &rewards_committed, - amount)); - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (reserve_pubp), - TALER_PQ_query_param_amount (&rewards_committed), - GNUNET_PQ_query_param_end - }; - - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_reserve_rewards_committed", - params); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - postgres_rollback (pg); - goto RETRY; - } - if (qs < 0) - { - GNUNET_break (0); - postgres_rollback (pg); - return TALER_EC_GENERIC_DB_STORE_FAILED; - } - } - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, - reward_id, - sizeof (*reward_id)); - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (reserve_pubp), - GNUNET_PQ_query_param_auto_from_type (reward_id), - GNUNET_PQ_query_param_string (justification), - GNUNET_PQ_query_param_string (next_url), - GNUNET_PQ_query_param_timestamp (expiration), - TALER_PQ_query_param_amount (amount), - GNUNET_PQ_query_param_end - }; - - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_reward", - params); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - postgres_rollback (pg); - goto RETRY; - } - if (qs < 0) - { - GNUNET_break (0); - postgres_rollback (pg); - return TALER_EC_GENERIC_DB_STORE_FAILED; - } - } - qs = postgres_commit (pg); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - if (qs < 0) - { - GNUNET_break (0); - postgres_rollback (pg); - return TALER_EC_GENERIC_DB_COMMIT_FAILED; - } - return TALER_EC_NONE; -} - - -/** * Closure for #lookup_signatures_cb(). */ struct LookupSignaturesContext @@ -3653,117 +2285,6 @@ postgres_lookup_reward_details (void *cls, /** - * Insert details about a reward pickup operation. The @a total_picked_up - * UPDATES the total amount under the @a reward_id, while the @a - * total_requested is the amount to be associated with this @a pickup_id. - * While there is usually only one pickup event that picks up the entire - * amount, our schema allows for wallets to pick up the amount incrementally - * over mulrewardle pick up operations. - * - * @param cls closure, typically a connection to the db - * @param instance_id which instance gave the reward - * @param reward_id the unique ID for the reward - * @param total_picked_up how much was picked up overall at this - * point (includes @a total_requested) - * @param pickup_id unique ID for the operation - * @param total_requested how much is being picked up in this operation - * @return transaction status, usually - * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success - * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a credit_uuid already known - */ -static enum GNUNET_DB_QueryStatus -postgres_insert_pickup (void *cls, - const char *instance_id, - const struct TALER_RewardIdentifierP *reward_id, - const struct TALER_Amount *total_picked_up, - const struct TALER_PickupIdentifierP *pickup_id, - const struct TALER_Amount *total_requested) -{ - struct PostgresClosure *pg = cls; - enum GNUNET_DB_QueryStatus qs; - - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (reward_id), - GNUNET_PQ_query_param_auto_from_type (pickup_id), - TALER_PQ_query_param_amount (total_requested), - GNUNET_PQ_query_param_end - }; - - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_pickup", - params); - if (0 > qs) - return qs; - } - - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (reward_id), - TALER_PQ_query_param_amount (total_picked_up), - GNUNET_PQ_query_param_end - }; - - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_picked_up_reward", - params); - if (0 > qs) - return qs; - } - { - uint64_t reserve_serial; - struct TALER_Amount reserve_picked_up; - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (reward_id), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("reserve_serial", - &reserve_serial), - TALER_PQ_RESULT_SPEC_AMOUNT ("rewards_picked_up", - &reserve_picked_up), - GNUNET_PQ_result_spec_end - - }; - - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_picked_up_reserve", - params, - rs); - if (0 > qs) - return qs; - } - if (0 >= - TALER_amount_add (&reserve_picked_up, - &reserve_picked_up, - total_requested)) - { - GNUNET_break (0); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&reserve_serial), - TALER_PQ_query_param_amount (&reserve_picked_up), - GNUNET_PQ_query_param_end - }; - - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_picked_up_reserve", - params); - if (0 > qs) - return qs; - } - } - return qs; -} - - -/** * Insert blind signature obtained from the exchange during a * reward pickup operation. * @@ -4812,10 +3333,8 @@ postgres_connect (void *cls) " deposit_serial" ",exchange_url" ",h_wire" - ",amount_with_fee_val" - ",amount_with_fee_frac" - ",deposit_fee_val" - ",deposit_fee_frac" + ",amount_with_fee" + ",deposit_fee" ",coin_pub" " FROM merchant_deposits" " JOIN merchant_accounts USING (account_serial)" @@ -4826,10 +3345,8 @@ postgres_connect (void *cls) " md.deposit_serial" ",md.exchange_url" ",mt.wtid" - ",exchange_deposit_value_val" - ",exchange_deposit_value_frac" - ",exchange_deposit_fee_val" - ",exchange_deposit_fee_frac" + ",exchange_deposit_value" + ",exchange_deposit_fee" ",deposit_timestamp" ",mt.confirmed AS transfer_confirmed" " FROM merchant_transfer_to_coin" @@ -4839,64 +3356,11 @@ postgres_connect (void *cls) " (SELECT deposit_serial" " FROM merchant_deposits" " WHERE order_serial=$1)"), - /* for postgres_insert_deposit_to_transfer() */ - GNUNET_PQ_make_prepare ("insert_deposit_to_transfer", - "INSERT INTO merchant_deposit_to_transfer" - "(deposit_serial" - ",coin_contribution_value_val" - ",coin_contribution_value_frac" - ",credit_serial" - ",execution_time" - ",signkey_serial" - ",exchange_sig" - ") SELECT $1, $2, $3, credit_serial, $4, signkey_serial, $5" - " FROM merchant_transfers" - " CROSS JOIN merchant_exchange_signing_keys" - " WHERE exchange_pub=$6" - " AND wtid=$7"), /* for postgres_mark_order_wired() */ GNUNET_PQ_make_prepare ("mark_order_wired", "UPDATE merchant_contract_terms SET" " wired=true" " WHERE order_serial=$1"), - /* for process_refund_cb() used in postgres_increase_refund() */ - GNUNET_PQ_make_prepare ("find_refunds_by_coin", - "SELECT" - " refund_amount_val" - ",refund_amount_frac" - ",rtransaction_id" - " FROM merchant_refunds" - " WHERE coin_pub=$1" - " AND order_serial=$2"), - /* for process_deposits_for_refund_cb() used in postgres_increase_refund() */ - GNUNET_PQ_make_prepare ("insert_refund", - "INSERT INTO merchant_refunds" - "(order_serial" - ",rtransaction_id" - ",refund_timestamp" - ",coin_pub" - ",reason" - ",refund_amount_val" - ",refund_amount_frac" - ") VALUES" - "($1, $2, $3, $4, $5, $6, $7)"), - /* for postgres_increase_refund() */ - GNUNET_PQ_make_prepare ("find_deposits_for_refund", - "SELECT" - " coin_pub" - ",order_serial" - ",amount_with_fee_val" - ",amount_with_fee_frac" - " FROM merchant_deposits" - " WHERE order_serial=" - " (SELECT order_serial" - " FROM merchant_contract_terms" - " WHERE order_id=$2" - " AND paid=TRUE" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1))"), /* for postgres_lookup_refunds_detailed() */ GNUNET_PQ_make_prepare ("lookup_refunds_detailed", "SELECT" @@ -4906,8 +3370,7 @@ postgres_connect (void *cls) ",merchant_deposits.exchange_url" ",rtransaction_id" ",reason" - ",refund_amount_val" - ",refund_amount_frac" + ",refund_amount" ",merchant_refund_proofs.exchange_sig IS NULL AS pending" " FROM merchant_refunds" " JOIN merchant_deposits USING (order_serial, coin_pub)" @@ -4952,24 +3415,6 @@ postgres_connect (void *cls) " (SELECT merchant_serial" " FROM merchant_instances" " WHERE merchant_id=$1)"), - /* for postgres_insert_transfer() */ - GNUNET_PQ_make_prepare ("insert_transfer", - "INSERT INTO merchant_transfers" - "(exchange_url" - ",wtid" - ",credit_amount_val" - ",credit_amount_frac" - ",account_serial" - ",confirmed)" - "SELECT" - " $1, $2, $3, $4, account_serial, $6" - " FROM merchant_accounts" - " WHERE payto_uri=$5" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$7)" - " ON CONFLICT DO NOTHING;"), /* for postgres_delete_transfer() */ GNUNET_PQ_make_prepare ("delete_transfer", "DELETE FROM merchant_transfers" @@ -5006,84 +3451,11 @@ postgres_connect (void *cls) " (SELECT merchant_serial" " FROM merchant_instances" " WHERE merchant_id=$1)"), - /* for postgres_insert_transfer_details() */ - GNUNET_PQ_make_prepare ("lookup_credit_serial", - "SELECT" - " credit_serial" - " FROM merchant_transfers" - " WHERE exchange_url=$1" - " AND wtid=$4" - " AND account_serial=" - " (SELECT account_serial" - " FROM merchant_accounts" - " WHERE payto_uri=$2" - " AND exchange_url=$1" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$3))"), - /* for postgres_insert_transfer_details() */ - GNUNET_PQ_make_prepare ("insert_transfer_signature", - "INSERT INTO merchant_transfer_signatures" - "(credit_serial" - ",signkey_serial" - ",credit_amount_val" - ",credit_amount_frac" - ",wire_fee_val" - ",wire_fee_frac" - ",execution_time" - ",exchange_sig) " - "SELECT $1, signkey_serial, $2, $3, $4, $5, $6, $7" - " FROM merchant_exchange_signing_keys" - " WHERE exchange_pub=$8" - " ORDER BY start_date DESC" - " LIMIT 1"), - /* for postgres_insert_transfer_details() */ - GNUNET_PQ_make_prepare ("insert_transfer_to_coin_mapping", - "INSERT INTO merchant_transfer_to_coin" - "(deposit_serial" - ",credit_serial" - ",offset_in_exchange_list" - ",exchange_deposit_value_val" - ",exchange_deposit_value_frac" - ",exchange_deposit_fee_val" - ",exchange_deposit_fee_frac) " - "SELECT deposit_serial, $1, $2, $3, $4, $5, $6" - " FROM merchant_deposits" - " JOIN merchant_contract_terms USING (order_serial)" - " WHERE coin_pub=$7" - " AND h_contract_terms=$8" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$9)"), - /* for postgres_insert_transfer_details() */ - GNUNET_PQ_make_prepare ("update_wired_by_coin_pub", - "WITH os AS" /* select orders affected by the coin */ - "(SELECT order_serial" - " FROM merchant_deposits" - " WHERE coin_pub=$1)" - "UPDATE merchant_contract_terms " - " SET wired=TRUE " - " WHERE order_serial IN " - " (SELECT order_serial FROM merchant_deposits" /* only orders for which NO un-wired coin exists*/ - " WHERE NOT EXISTS " - " (SELECT order_serial FROM merchant_deposits" /* orders for which ANY un-wired coin exists */ - " JOIN os USING (order_serial)" /* filter early */ - " WHERE deposit_serial NOT IN" - " (SELECT deposit_serial " /* all coins associated with order that WERE wired */ - " FROM merchant_deposits " - " JOIN os USING (order_serial)" /* filter early */ - " JOIN merchant_deposit_to_transfer USING (deposit_serial)" - " JOIN merchant_transfers USING (credit_serial)" - " WHERE confirmed=TRUE)))"), /* for postgres_lookup_wire_fee() */ GNUNET_PQ_make_prepare ("lookup_wire_fee", "SELECT" - " wire_fee_val" - ",wire_fee_frac" - ",closing_fee_val" - ",closing_fee_frac" + " wire_fee" + ",closing_fee" ",start_date" ",end_date" ",master_sig" @@ -5096,14 +3468,10 @@ postgres_connect (void *cls) GNUNET_PQ_make_prepare ("lookup_deposits_by_contract_and_coin", "SELECT" " exchange_url" - ",amount_with_fee_val" - ",amount_with_fee_frac" - ",deposit_fee_val" - ",deposit_fee_frac" - ",refund_fee_val" - ",refund_fee_frac" - ",wire_fee_val" - ",wire_fee_frac" + ",amount_with_fee" + ",deposit_fee" + ",refund_fee" + ",wire_fee" ",h_wire" ",deposit_timestamp" ",refund_deadline" @@ -5122,12 +3490,9 @@ postgres_connect (void *cls) /* for postgres_lookup_transfer() */ GNUNET_PQ_make_prepare ("lookup_transfer", "SELECT" - " mt.credit_amount_val AS credit_amount_val" - ",mt.credit_amount_frac AS credit_amount_frac" - ",mts.credit_amount_val AS exchange_amount_val" - ",mts.credit_amount_frac AS exchange_amount_frac" - ",wire_fee_val" - ",wire_fee_frac" + " mt.credit_amount AS credit_amount" + ",mts.credit_amount AS exchange_amount" + ",wire_fee" ",execution_time" ",verified" " FROM merchant_transfers mt" @@ -5141,10 +3506,8 @@ postgres_connect (void *cls) GNUNET_PQ_make_prepare ("lookup_transfer_summary", "SELECT" " order_id" - ",exchange_deposit_value_val" - ",exchange_deposit_value_frac" - ",exchange_deposit_fee_val" - ",exchange_deposit_fee_frac" + ",exchange_deposit_value" + ",exchange_deposit_fee" " FROM merchant_transfers" " JOIN merchant_transfer_to_coin USING (credit_serial)" " JOIN merchant_deposits USING (deposit_serial)" @@ -5157,54 +3520,14 @@ postgres_connect (void *cls) " merchant_contract_terms.h_contract_terms" ",merchant_transfer_to_coin.offset_in_exchange_list" ",merchant_deposits.coin_pub" - ",exchange_deposit_value_val" - ",exchange_deposit_value_frac" - ",exchange_deposit_fee_val" - ",exchange_deposit_fee_frac" + ",exchange_deposit_value" + ",exchange_deposit_fee" " FROM merchant_transfer_to_coin" " JOIN merchant_deposits USING (deposit_serial)" " JOIN merchant_contract_terms USING (order_serial)" " JOIN merchant_transfers USING (credit_serial)" " WHERE merchant_transfers.wtid=$2" " AND merchant_transfers.exchange_url=$1"), - /* For postgres_store_wire_fee_by_exchange() */ - GNUNET_PQ_make_prepare ("insert_wire_fee", - "INSERT INTO merchant_exchange_wire_fees" - "(master_pub" - ",h_wire_method" - ",wire_fee_val" - ",wire_fee_frac" - ",closing_fee_val" - ",closing_fee_frac" - ",start_date" - ",end_date" - ",master_sig)" - " VALUES " - "($1, $2, $3, $4, $5, $6, $7, $8, $9)" - " ON CONFLICT DO NOTHING"), - /* For postgres_insert_reserve() */ - GNUNET_PQ_make_prepare ("insert_reserve", - "INSERT INTO merchant_reward_reserves" - "(reserve_pub" - ",merchant_serial" - ",creation_time" - ",expiration" - ",merchant_initial_balance_val" - ",merchant_initial_balance_frac" - ")" - "SELECT $2, merchant_serial, $3, $4, $5, $6" - " FROM merchant_instances" - " WHERE merchant_id=$1"), - /* For postgres_activate_reserve() */ - GNUNET_PQ_make_prepare ("activate_reserve", - "UPDATE merchant_reward_reserves SET" - " exchange_initial_balance_val=$3" - ",exchange_initial_balance_frac=$4" - " WHERE reserve_pub=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"), /* For postgres_insert_reserve() */ GNUNET_PQ_make_prepare ("insert_reserve_key", "INSERT INTO merchant_reward_reserve_keys" @@ -5226,26 +3549,20 @@ postgres_connect (void *cls) " reserve_pub" ",merchant_id" ",exchange_url" - ",merchant_initial_balance_val" - ",merchant_initial_balance_frac" + ",merchant_initial_balance" " FROM merchant_reward_reserves" " JOIN merchant_instances USING (merchant_serial)" " JOIN merchant_reward_reserve_keys USING (reserve_serial)" - " WHERE exchange_initial_balance_val=0" - " AND exchange_initial_balance_frac=0"), + " WHERE exchange_initial_balance=CAST((0,0) AS taler_amount)"), /* For postgres_lookup_reserve() */ GNUNET_PQ_make_prepare ("lookup_reserve", "SELECT" " creation_time" ",expiration" - ",merchant_initial_balance_val" - ",merchant_initial_balance_frac" - ",exchange_initial_balance_val" - ",exchange_initial_balance_frac" - ",rewards_committed_val" - ",rewards_committed_frac" - ",rewards_picked_up_val" - ",rewards_picked_up_frac" + ",merchant_initial_balance" + ",exchange_initial_balance" + ",rewards_committed" + ",rewards_picked_up" ",reserve_priv IS NOT NULL AS active" ",exchange_url" ",master_pub" @@ -5261,8 +3578,7 @@ postgres_connect (void *cls) "SELECT" " justification" ",reward_id" - ",amount_val" - ",amount_frac" + ",amount" " FROM merchant_rewards" " WHERE reserve_serial =" " (SELECT reserve_serial" @@ -5293,65 +3609,6 @@ postgres_connect (void *cls) " (SELECT merchant_serial" " FROM merchant_instances" " WHERE merchant_id=$1)"), - /* For postgres_authorize_reward() */ - GNUNET_PQ_make_prepare ("lookup_reserve_for_reward", - "SELECT" - " reserve_pub" - ",expiration" - ",exchange_initial_balance_val" - ",exchange_initial_balance_frac" - ",rewards_committed_val" - ",rewards_committed_frac" - " FROM merchant_reward_reserves" - " WHERE" - " merchant_serial =" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"), - - /* For postgres_authorize_reward() */ - GNUNET_PQ_make_prepare ("lookup_reserve_status", - "SELECT" - " expiration" - ",exchange_initial_balance_val" - ",exchange_initial_balance_frac" - ",rewards_committed_val" - ",rewards_committed_frac" - " FROM merchant_reward_reserves" - " WHERE reserve_pub = $2" - " AND merchant_serial =" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"), - /* For postgres_authorize_reward() */ - GNUNET_PQ_make_prepare ("update_reserve_rewards_committed", - "UPDATE merchant_reward_reserves SET" - " rewards_committed_val=$3" - ",rewards_committed_frac=$4" - " WHERE reserve_pub = $2" - " AND merchant_serial =" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"), - /* For postgres_authorize_reward() */ - GNUNET_PQ_make_prepare ("insert_reward", - "INSERT INTO merchant_rewards" - "(reserve_serial" - ",reward_id" - ",justification" - ",next_url" - ",expiration" - ",amount_val" - ",amount_frac" - ") " - "SELECT" - " reserve_serial, $3, $4, $5, $6, $7, $8" - " FROM merchant_reward_reserves" - " WHERE reserve_pub=$2" - " AND merchant_serial = " - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"), /* For postgres_lookup_pickup() */ GNUNET_PQ_make_prepare ("lookup_pickup", "SELECT" @@ -5379,10 +3636,8 @@ postgres_connect (void *cls) /* For postgres_lookup_reward() */ GNUNET_PQ_make_prepare ("lookup_reward", "SELECT" - " amount_val" - ",amount_frac" - ",picked_up_val" - ",picked_up_frac" + " amount" + ",picked_up" ",merchant_rewards.expiration" ",exchange_url" ",next_url" @@ -5400,8 +3655,7 @@ postgres_connect (void *cls) "SELECT" " reward_serial" ",reward_id" - ",amount_val" - ",amount_frac" + ",amount" ",CAST($4 as BIGINT)" /* otherwise $4 is unused and Postgres unhappy */ ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ " FROM merchant_rewards" @@ -5418,8 +3672,7 @@ postgres_connect (void *cls) "SELECT" " reward_serial" ",reward_id" - ",amount_val" - ",amount_frac" + ",amount" ",CAST($4 as BIGINT)" /* otherwise $4 is unused and Postgres unhappy */ ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ " FROM merchant_rewards" @@ -5436,8 +3689,7 @@ postgres_connect (void *cls) "SELECT" " reward_serial" ",reward_id" - ",amount_val" - ",amount_frac" + ",amount" " FROM merchant_rewards" " JOIN merchant_reward_reserves USING (reserve_serial)" " WHERE merchant_serial =" @@ -5454,8 +3706,7 @@ postgres_connect (void *cls) "SELECT" " reward_serial" ",reward_id" - ",amount_val" - ",amount_frac" + ",amount" " FROM merchant_rewards" " JOIN merchant_reward_reserves USING (reserve_serial)" " WHERE merchant_serial =" @@ -5472,10 +3723,8 @@ postgres_connect (void *cls) GNUNET_PQ_make_prepare ("lookup_reward_details", "SELECT" " reward_serial" - ",amount_val" - ",amount_frac" - ",picked_up_val" - ",picked_up_frac" + ",amount" + ",picked_up" ",justification" ",merchant_rewards.expiration" ",reserve_pub" @@ -5490,56 +3739,12 @@ postgres_connect (void *cls) GNUNET_PQ_make_prepare ("lookup_pickup_details", "SELECT" " pickup_id" - ",amount_val" - ",amount_frac" + ",amount" ",COUNT(blind_sig) AS num_planchets" " FROM merchant_reward_pickups" " JOIN merchant_reward_pickup_signatures USING (pickup_serial)" " WHERE reward_serial = $1" " GROUP BY pickup_serial"), - /* for postgres_insert_pickup() */ - GNUNET_PQ_make_prepare ("insert_pickup", - "INSERT INTO merchant_reward_pickups" - "(reward_serial" - ",pickup_id" - ",amount_val" - ",amount_frac" - ") " - "SELECT" - " reward_serial, $3, $4, $5" - " FROM merchant_rewards" - " JOIN merchant_reward_reserves USING (reserve_serial)" - " WHERE reward_id=$2" - " AND merchant_serial = " - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"), - /* for postgres_insert_pickup() */ - GNUNET_PQ_make_prepare ("update_picked_up_reward", - "UPDATE merchant_rewards SET" - " picked_up_val=$2" - ",picked_up_frac=$3" - ",was_picked_up = ($2 = amount_val AND $3 = amount_frac)" - " WHERE reward_id = $1"), - /* for postgres_insert_pickup() */ - GNUNET_PQ_make_prepare ("lookup_picked_up_reserve", - "SELECT" - " reserve_serial" - ",rewards_picked_up_val" - ",rewards_picked_up_frac" - " FROM merchant_reward_reserves" - " JOIN merchant_rewards USING (reserve_serial)" - " WHERE reward_id=$2" - " AND merchant_serial =" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"), - /* for postgres_insert_pickup() */ - GNUNET_PQ_make_prepare ("update_picked_up_reserve", - "UPDATE merchant_reward_reserves SET" - " rewards_picked_up_val=$2" - ",rewards_picked_up_frac=$3" - " WHERE reserve_serial = $1"), /* for postgres_insert_pickup_blind_signature() */ GNUNET_PQ_make_prepare ("insert_pickup_blind_signature", "INSERT INTO merchant_reward_pickup_signatures" @@ -5812,10 +4017,10 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) plugin->event_listen_cancel = &postgres_event_listen_cancel; plugin->event_notify = &postgres_event_notify; plugin->preflight = &postgres_preflight; - plugin->start = &postgres_start; - plugin->start_read_committed = &postgres_start_read_committed; - plugin->rollback = &postgres_rollback; - plugin->commit = &postgres_commit; + plugin->start = &TMH_PG_start; + plugin->start_read_committed = &TMH_PG_start_read_committed; + plugin->rollback = &TMH_PG_rollback; + plugin->commit = &TMH_PG_commit; plugin->lookup_instance_auth = &TMH_PG_lookup_instance_auth; plugin->insert_instance @@ -5901,18 +4106,16 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) plugin->lookup_deposits_by_order = &postgres_lookup_deposits_by_order; plugin->lookup_transfer_details_by_order = &postgres_lookup_transfer_details_by_order; - plugin->insert_deposit_to_transfer = &postgres_insert_deposit_to_transfer; plugin->mark_order_wired = &postgres_mark_order_wired; - plugin->increase_refund = &postgres_increase_refund; + plugin->increase_refund + = &TMH_PG_increase_refund; plugin->lookup_refunds_detailed = &postgres_lookup_refunds_detailed; plugin->insert_refund_proof = &postgres_insert_refund_proof; plugin->lookup_refund_proof = &postgres_lookup_refund_proof; plugin->lookup_order_by_fulfillment = &postgres_lookup_order_by_fulfillment; - plugin->insert_transfer = &postgres_insert_transfer; plugin->delete_transfer = &postgres_delete_transfer; plugin->check_transfer_exists = &postgres_check_transfer_exists; plugin->lookup_account = &postgres_lookup_account; - plugin->insert_transfer_details = &postgres_insert_transfer_details; plugin->lookup_wire_fee = &postgres_lookup_wire_fee; plugin->lookup_deposits_by_contract_and_coin = &postgres_lookup_deposits_by_contract_and_coin; @@ -5931,21 +4134,16 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) = &TMH_PG_update_wirewatch_progress; plugin->select_wirewatch_accounts = &TMH_PG_select_wirewatch_accounts; - plugin->store_wire_fee_by_exchange = &postgres_store_wire_fee_by_exchange; - plugin->insert_reserve = &postgres_insert_reserve; - plugin->activate_reserve = &postgres_activate_reserve; plugin->lookup_reserves = &TMH_PG_lookup_reserves; plugin->lookup_pending_reserves = &postgres_lookup_pending_reserves; plugin->lookup_reserve = &postgres_lookup_reserve; plugin->delete_reserve = &postgres_delete_reserve; plugin->purge_reserve = &postgres_purge_reserve; - plugin->authorize_reward = &postgres_authorize_reward; plugin->lookup_pickup = &postgres_lookup_pickup; plugin->lookup_reward = &postgres_lookup_reward; plugin->lookup_rewards = &postgres_lookup_rewards; plugin->lookup_reward_details = &postgres_lookup_reward_details; - plugin->insert_pickup = &postgres_insert_pickup; plugin->insert_pickup_blind_signature = &postgres_insert_pickup_blind_signature; plugin->select_open_transfers @@ -5954,6 +4152,22 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) = &TMH_PG_insert_exchange_keys; plugin->select_exchange_keys = &TMH_PG_select_exchange_keys; + plugin->insert_deposit_to_transfer + = &TMH_PG_insert_deposit_to_transfer; + plugin->insert_transfer + = &TMH_PG_insert_transfer; + plugin->insert_transfer_details + = &TMH_PG_insert_transfer_details; + plugin->store_wire_fee_by_exchange + = &TMH_PG_store_wire_fee_by_exchange; + plugin->insert_reserve + = &TMH_PG_insert_reserve; + plugin->activate_reserve + = &TMH_PG_activate_reserve; + plugin->authorize_reward + = &TMH_PG_authorize_reward; + plugin->insert_pickup + = &TMH_PG_insert_pickup; plugin->lookup_templates = &postgres_lookup_templates; plugin->lookup_template = &postgres_lookup_template; plugin->delete_template = &postgres_delete_template; |