merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit 594aa4576f9ec45315c4d253356b9962d59d46c2
parent 1925b6e26c0f68c36cd9fb32165638624021686c
Author: Iván Ávalos <avalos@disroot.org>
Date:   Fri, 12 May 2023 23:16:21 -0600

Factor out 13 new functions (shit job)

Diffstat:
Msrc/backenddb/Makefile.am | 13+++++++++++++
Asrc/backenddb/pg_delete_contract_terms.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_delete_contract_terms.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_delete_order.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_delete_order.h | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_expire_locks.c | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_expire_locks.h | 38++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_insert_contract_terms.c | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_insert_contract_terms.h | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_insert_order.c | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_insert_order.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_insert_order_lock.c | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_insert_order_lock.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_contract_terms.c | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_contract_terms.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_contract_terms2.c | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_contract_terms2.h | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_order.c | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_order.h | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_order_summary.c | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_order_summary.h | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_orders.c | 926+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_orders.h | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_unlock_inventory.c | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_unlock_inventory.h | 42++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_update_contract_terms.c | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_update_contract_terms.h | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backenddb/plugin_merchantdb_postgres.c | 5833++++++++++++++++++++++++++++---------------------------------------------------
28 files changed, 4623 insertions(+), 3786 deletions(-)

diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am @@ -81,6 +81,19 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \ pg_insert_product.h pg_insert_product.c \ pg_update_product.h pg_update_product.c \ pg_lock_product.h pg_lock_product.c \ + pg_expire_locks.h pg_expire_locks.c \ + pg_delete_order.h pg_delete_order.c \ + pg_lookup_order.h pg_lookup_order.c \ + pg_lookup_order_summary.h pg_lookup_order_summary.c \ + pg_lookup_orders.h pg_lookup_orders.c \ + pg_insert_order.h pg_insert_order.c \ + pg_unlock_inventory.h pg_unlock_inventory.c \ + pg_insert_order_lock.h pg_insert_order_lock.c \ + pg_lookup_contract_terms2.h pg_lookup_contract_terms2.c \ + pg_lookup_contract_terms.h pg_lookup_contract_terms.c \ + pg_insert_contract_terms.h pg_insert_contract_terms.c \ + pg_update_contract_terms.h pg_update_contract_terms.c \ + pg_delete_contract_terms.h pg_delete_contract_terms.c \ plugin_merchantdb_postgres.c pg_helper.h libtaler_plugin_merchantdb_postgres_la_LIBADD = \ $(LTLIBINTL) diff --git a/src/backenddb/pg_delete_contract_terms.c b/src/backenddb/pg_delete_contract_terms.c @@ -0,0 +1,59 @@ +/* + 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_delete_contract_terms.c + * @brief Implementation of the delete_contract_terms function for Postgres + * @author Iván Ávalos + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_delete_contract_terms.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +TMH_PG_delete_contract_terms (void *cls, + const char *instance_id, + const char *order_id, + struct GNUNET_TIME_Relative legal_expiration) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_relative_time (&legal_expiration), + GNUNET_PQ_query_param_absolute_time (&now), + GNUNET_PQ_query_param_end + }; + + check_connection (pg); + PREPARE (pg, + "delete_contract_terms", + "DELETE FROM merchant_contract_terms" + " WHERE order_id=$2" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND ( ( (pay_deadline < $4) AND" + " (NOT paid) ) OR" + " (creation_time + $3 < $4) )"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "delete_contract_terms", + params); +} diff --git a/src/backenddb/pg_delete_contract_terms.h b/src/backenddb/pg_delete_contract_terms.h @@ -0,0 +1,47 @@ +/* + 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_delete_contract_terms.h + * @brief implementation of the delete_contract_terms function for Postgres + * @author Iván Ávalos + */ +#ifndef PG_DELETE_CONTRACT_TERMS_H +#define PG_DELETE_CONTRACT_TERMS_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Delete information about a contract. Note that the transaction must + * enforce that the contract is not awaiting payment anymore AND was not + * paid, or is past the legal expiration. + * + * @param cls closure + * @param instance_id instance to delete order of + * @param order_id order to delete + * @param legal_expiration how long do we need to keep (paid) contracts on + * file for legal reasons (i.e. taxation) + * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS + * if locks prevent deletion OR order unknown + */ +enum GNUNET_DB_QueryStatus +TMH_PG_delete_contract_terms (void *cls, + const char *instance_id, + const char *order_id, + struct GNUNET_TIME_Relative legal_expiration); + +#endif diff --git a/src/backenddb/pg_delete_order.c b/src/backenddb/pg_delete_order.c @@ -0,0 +1,87 @@ +/* + 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_delete_order.c + * @brief Implementation of the delete_order function for Postgres + * @author Iván Ávalos + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_delete_order.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +TMH_PG_delete_order (void *cls, + const char *instance_id, + const char *order_id, + bool force) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_absolute_time (&now), + GNUNET_PQ_query_param_bool (force), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_QueryParam params2[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_end + }; + enum GNUNET_DB_QueryStatus qs; + + check_connection (pg); + PREPARE (pg, + "delete_order", + "WITH ms AS" + "(SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + ", mc AS" + "(SELECT paid" + " FROM merchant_contract_terms" + " JOIN ms USING (merchant_serial)" + " WHERE order_id=$2) " + "DELETE" + " FROM merchant_orders mo" + " WHERE order_id=$2" + " AND merchant_serial=(SELECT merchant_serial FROM ms)" + " AND ( (pay_deadline < $3)" + " OR (NOT EXISTS (SELECT paid FROM mc))" + " OR ($4 AND (FALSE=(SELECT paid FROM mc))) );"); + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "delete_order", + params); + if ( (qs <= 0) || (! force)) + return qs; + PREPARE (pg, + "delete_contract", + "DELETE" + " FROM merchant_contract_terms" + " WHERE order_id=$2 AND" + " merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND NOT paid;"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "delete_contract", + params2); +} diff --git a/src/backenddb/pg_delete_order.h b/src/backenddb/pg_delete_order.h @@ -0,0 +1,45 @@ +/* + 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_delete_order.h + * @brief implementation of the delete_order function for Postgres + * @author Iván Ávalos + */ +#ifndef PG_DELETE_ORDER_H +#define PG_DELETE_ORDER_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Delete information about an order. Note that the transaction must + * enforce that the order is not awaiting payment anymore. + * + * @param cls closure + * @param instance_id instance to delete order of + * @param order_id order to delete + * @param force delete claimed but unpaid orders as well + * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS + * if pending payment prevents deletion OR order unknown + */ +enum GNUNET_DB_QueryStatus +TMH_PG_delete_order (void *cls, + const char *instance_id, + const char *order_id, + bool force); + +#endif diff --git a/src/backenddb/pg_expire_locks.c b/src/backenddb/pg_expire_locks.c @@ -0,0 +1,84 @@ +/* + 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_expire_locks.c + * @brief Implementation of the expire_locks function for Postgres + * @author Iván Ávalos + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_expire_locks.h" +#include "pg_helper.h" + +void +TMH_PG_expire_locks (void *cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_absolute_time (&now), + GNUNET_PQ_query_param_end + }; + enum GNUNET_DB_QueryStatus qs1; + enum GNUNET_DB_QueryStatus qs2; + enum GNUNET_DB_QueryStatus qs3; + + check_connection (pg); + PREPARE (pg, + "unlock_products", + "DELETE FROM merchant_inventory_locks" + " WHERE expiration < $1"); + qs1 = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "unlock_products", + params); + if (qs1 < 0) + { + GNUNET_break (0); + return; + } + PREPARE (pg, + "unlock_orders", + "DELETE FROM merchant_orders" + " WHERE pay_deadline < $1"); + qs2 = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "unlock_orders", + params); + if (qs2 < 0) + { + GNUNET_break (0); + return; + } + PREPARE (pg, + "unlock_contracts", + "DELETE FROM merchant_contract_terms" + " WHERE NOT paid" + " AND pay_deadline < $1"); + qs3 = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "unlock_contracts", + params); + if (qs3 < 0) + { + GNUNET_break (0); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Released %d+%d+%d locks\n", + qs1, + qs2, + qs3); +} diff --git a/src/backenddb/pg_expire_locks.h b/src/backenddb/pg_expire_locks.h @@ -0,0 +1,38 @@ +/* + 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_expire_locks.h + * @brief implementation of the expire_locks function for Postgres + * @author Iván Ávalos + */ +#ifndef PG_EXPIRE_LOCKS_H +#define PG_EXPIRE_LOCKS_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Release all expired product locks, including + * those from expired offers -- across all + * instances. + * + * @param cls closure + */ +void +TMH_PG_expire_locks (void *cls); + +#endif diff --git a/src/backenddb/pg_insert_contract_terms.c b/src/backenddb/pg_insert_contract_terms.c @@ -0,0 +1,132 @@ +/* + 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_contract_terms.c + * @brief Implementation of the insert_contract_terms function for Postgres + * @author Iván Ávalos + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_insert_contract_terms.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +TMH_PG_insert_contract_terms ( + void *cls, + const char *instance_id, + const char *order_id, + json_t *contract_terms, + uint64_t *order_serial) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Timestamp pay_deadline; + struct GNUNET_TIME_Timestamp refund_deadline; + const char *fulfillment_url; + struct TALER_PrivateContractHashP h_contract_terms; + + if (GNUNET_OK != + TALER_JSON_contract_hash (contract_terms, + &h_contract_terms)) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_timestamp ("pay_deadline", + &pay_deadline), + GNUNET_JSON_spec_timestamp ("refund_deadline", + &refund_deadline), + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (NULL, + contract_terms, + spec); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } + + fulfillment_url = + json_string_value (json_object_get (contract_terms, + "fulfillment_url")); + check_connection (pg); + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), + TALER_PQ_query_param_json (contract_terms), + GNUNET_PQ_query_param_auto_from_type (&h_contract_terms), + GNUNET_PQ_query_param_timestamp (&pay_deadline), + GNUNET_PQ_query_param_timestamp (&refund_deadline), + (NULL == fulfillment_url) + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_string (fulfillment_url), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("order_serial", + order_serial), + GNUNET_PQ_result_spec_end + }; + PREPARE (pg, + "insert_contract_terms", + "INSERT INTO merchant_contract_terms" + "(order_serial" + ",merchant_serial" + ",order_id" + ",contract_terms" + ",h_contract_terms" + ",creation_time" + ",pay_deadline" + ",refund_deadline" + ",fulfillment_url" + ",claim_token" + ",pos_key" + ",pos_algorithm)" + "SELECT" + " mo.order_serial" + ",mo.merchant_serial" + ",mo.order_id" + ",$3" /* contract_terms */ + ",$4" /* h_contract_terms */ + ",mo.creation_time" + ",$5" /* pay_deadline */ + ",$6" /* refund_deadline */ + ",$7" /* fulfillment_url */ + ",mo.claim_token" + ",mo.pos_key" + ",mo.pos_algorithm" + " FROM merchant_orders mo" + " WHERE order_id=$2" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " RETURNING order_serial"); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "insert_contract_terms", + params, + rs); + } +} diff --git a/src/backenddb/pg_insert_contract_terms.h b/src/backenddb/pg_insert_contract_terms.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_insert_contract_terms.h + * @brief implementation of the insert_contract_terms function for Postgres + * @author Iván Ávalos + */ +#ifndef PG_INSERT_CONTRACT_TERMS_H +#define PG_INSERT_CONTRACT_TERMS_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Store contract terms given its @a order_id. Note that some attributes are + * expected to be calculated inside of the function, like the hash of the + * contract terms (to be hashed), the creation_time and pay_deadline (to be + * obtained from the merchant_orders table). The "session_id" should be + * initially set to the empty string. The "fulfillment_url" and "refund_deadline" + * must be extracted from @a contract_terms. + * + * @param cls closure + * @param instance_id instance's identifier + * @param order_id order_id used to store + * @param contract_terms contract terms to store + * @param[out] order_serial set to the serial of the order + * @return transaction status, #GNUNET_DB_STATUS_HARD_ERROR if @a contract_terms + * is malformed + */ +enum GNUNET_DB_QueryStatus +TMH_PG_insert_contract_terms ( + void *cls, + const char *instance_id, + const char *order_id, + json_t *contract_terms, + uint64_t *order_serial); + +#endif diff --git a/src/backenddb/pg_insert_order.c b/src/backenddb/pg_insert_order.c @@ -0,0 +1,82 @@ +/* + 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_order.c + * @brief Implementation of the insert_order function for Postgres + * @author Iván Ávalos + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_insert_order.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +TMH_PG_insert_order (void *cls, + const char *instance_id, + const char *order_id, + const struct TALER_MerchantPostDataHashP *h_post_data, + struct GNUNET_TIME_Timestamp pay_deadline, + const struct TALER_ClaimTokenP *claim_token, + const json_t *contract_terms, + const char *pos_key, + enum TALER_MerchantConfirmationAlgorithm pos_algorithm) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Timestamp now; + uint32_t pos32 = (uint32_t) pos_algorithm; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_timestamp (&pay_deadline), + GNUNET_PQ_query_param_auto_from_type (claim_token), + GNUNET_PQ_query_param_auto_from_type (h_post_data), + GNUNET_PQ_query_param_timestamp (&now), + TALER_PQ_query_param_json (contract_terms), + (NULL == pos_key) + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_string (pos_key), + GNUNET_PQ_query_param_uint32 (&pos32), + GNUNET_PQ_query_param_end + }; + + now = GNUNET_TIME_timestamp_get (); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "inserting order: order_id: %s, instance_id: %s.\n", + order_id, + instance_id); + check_connection (pg); + PREPARE (pg, + "insert_order", + "INSERT INTO merchant_orders" + "(merchant_serial" + ",order_id" + ",pay_deadline" + ",claim_token" + ",h_post_data" + ",creation_time" + ",contract_terms" + ",pos_key" + ",pos_algorithm)" + " SELECT merchant_serial," + " $2, $3, $4, $5, $6, $7, $8, $9" + " FROM merchant_instances" + " WHERE merchant_id=$1"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_order", + params); +} diff --git a/src/backenddb/pg_insert_order.h b/src/backenddb/pg_insert_order.h @@ -0,0 +1,53 @@ +/* + 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_order.h + * @brief implementation of the insert_order function for Postgres + * @author Iván Ávalos + */ +#ifndef PG_INSERT_ORDER_H +#define PG_INSERT_ORDER_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Insert order into the DB. + * + * @param cls closure + * @param instance_id identifies the instance responsible for the order + * @param order_id alphanumeric string that uniquely identifies the proposal + * @param h_post_data hash of the POST data for idempotency checks + * @param pay_deadline how long does the customer have to pay for the order + * @param claim_token token to use for access control + * @param contract_terms proposal data to store + * @param pos_key encoded key for payment verification + * @param pos_algorithm algorithm to compute the payment verification + * @return transaction status + */ +enum GNUNET_DB_QueryStatus +TMH_PG_insert_order (void *cls, + const char *instance_id, + const char *order_id, + const struct TALER_MerchantPostDataHashP *h_post_data, + struct GNUNET_TIME_Timestamp pay_deadline, + const struct TALER_ClaimTokenP *claim_token, + const json_t *contract_terms, + const char *pos_key, + enum TALER_MerchantConfirmationAlgorithm pos_algorithm); + +#endif diff --git a/src/backenddb/pg_insert_order_lock.c b/src/backenddb/pg_insert_order_lock.c @@ -0,0 +1,78 @@ +/* + 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_order_lock.c + * @brief Implementation of the insert_order_lock function for Postgres + * @author Iván Ávalos + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_insert_order_lock.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +TMH_PG_insert_order_lock (void *cls, + const char *instance_id, + const char *order_id, + const char *product_id, + uint64_t quantity) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_string (product_id), + GNUNET_PQ_query_param_uint64 (&quantity), + GNUNET_PQ_query_param_end + }; + + check_connection (pg); + PREPARE (pg, + "insert_order_lock", + "WITH tmp AS" + " (SELECT " + " product_serial" + " ,merchant_serial" + " ,total_stock" + " ,total_sold" + " ,total_lost" + " FROM merchant_inventory" + " WHERE product_id=$3" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1))" + " INSERT INTO merchant_order_locks" + " (product_serial" + " ,total_locked" + " ,order_serial)" + " SELECT tmp.product_serial, $4, order_serial" + " FROM merchant_orders" + " JOIN tmp USING(merchant_serial)" + " WHERE order_id=$2 AND" + " tmp.total_stock - tmp.total_sold - tmp.total_lost - $4 >= " + " (SELECT COALESCE(SUM(total_locked), 0)" + " FROM merchant_inventory_locks" + " WHERE product_serial=tmp.product_serial) + " + " (SELECT COALESCE(SUM(total_locked), 0)" + " FROM merchant_order_locks" + " WHERE product_serial=tmp.product_serial)"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_order_lock", + params); +} diff --git a/src/backenddb/pg_insert_order_lock.h b/src/backenddb/pg_insert_order_lock.h @@ -0,0 +1,47 @@ +/* + 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_order_lock.h + * @brief implementation of the insert_order_lock function for Postgres + * @author Iván Ávalos + */ +#ifndef PG_INSERT_ORDER_LOCK_H +#define PG_INSERT_ORDER_LOCK_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Lock inventory stock to a particular order. + * + * @param cls closure + * @param instance_id identifies the instance responsible for the order + * @param order_id alphanumeric string that uniquely identifies the order + * @param product_id uniquely identifies the product to be locked + * @param quantity how many units should be locked to the @a order_id + * @return transaction status, + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are insufficient stocks + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success + */ +enum GNUNET_DB_QueryStatus +TMH_PG_insert_order_lock (void *cls, + const char *instance_id, + const char *order_id, + const char *product_id, + uint64_t quantity); + +#endif diff --git a/src/backenddb/pg_lookup_contract_terms.c b/src/backenddb/pg_lookup_contract_terms.c @@ -0,0 +1,82 @@ +/* + 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_lookup_contract_terms.c + * @brief Implementation of the lookup_contract_terms function for Postgres + * @author Iván Ávalos + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_lookup_contract_terms.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_contract_terms ( + void *cls, + const char *instance_id, + const char *order_id, + json_t **contract_terms, + uint64_t *order_serial, + bool *paid, + struct TALER_ClaimTokenP *claim_token) +{ + struct PostgresClosure *pg = cls; + enum GNUNET_DB_QueryStatus qs; + struct TALER_ClaimTokenP ct; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + /* contract_terms must be first! */ + TALER_PQ_result_spec_json ("contract_terms", + contract_terms), + GNUNET_PQ_result_spec_uint64 ("order_serial", + order_serial), + GNUNET_PQ_result_spec_bool ("paid", + paid), + GNUNET_PQ_result_spec_auto_from_type ("claim_token", + &ct), + GNUNET_PQ_result_spec_end + }; + + check_connection (pg); + PREPARE (pg, + "lookup_contract_terms", + "SELECT" + " contract_terms" + ",order_serial" + ",claim_token" + ",paid" + " FROM merchant_contract_terms" + " WHERE order_id=$2" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)"); + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_contract_terms", + params, + (NULL != contract_terms) + ? rs + : &rs[1]); + if (NULL != claim_token) + *claim_token = ct; + return qs; +} diff --git a/src/backenddb/pg_lookup_contract_terms.h b/src/backenddb/pg_lookup_contract_terms.h @@ -0,0 +1,50 @@ +/* + 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_lookup_contract_terms.h + * @brief implementation of the lookup_contract_terms function for Postgres + * @author Iván Ávalos + */ +#ifndef PG_LOOKUP_CONTRACT_TERMS_H +#define PG_LOOKUP_CONTRACT_TERMS_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Retrieve contract terms given its @a order_id + * + * @param cls closure + * @param instance_id instance's identifier + * @param order_id order_id used to lookup. + * @param[out] contract_terms where to store the result, NULL to only check for existence + * @param[out] order_serial set to the order's serial number + * @param[out] paid set to true if the order is fully paid + * @param[out] claim_token set to token to use for access control + * @return transaction status + */ +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_contract_terms ( + void *cls, + const char *instance_id, + const char *order_id, + json_t **contract_terms, + uint64_t *order_serial, + bool *paid, + struct TALER_ClaimTokenP *claim_token); + +#endif diff --git a/src/backenddb/pg_lookup_contract_terms2.c b/src/backenddb/pg_lookup_contract_terms2.c @@ -0,0 +1,96 @@ +/* + 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_lookup_contract_terms2.c + * @brief Implementation of the lookup_contract_terms2 function for Postgres + * @author Iván Ávalos + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_lookup_contract_terms2.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_contract_terms2 ( + void *cls, + const char *instance_id, + const char *order_id, + json_t **contract_terms, + uint64_t *order_serial, + bool *paid, + struct TALER_ClaimTokenP *claim_token, + char **pos_key, + enum TALER_MerchantConfirmationAlgorithm *pos_algorithm) +{ + struct PostgresClosure *pg = cls; + enum GNUNET_DB_QueryStatus qs; + struct TALER_ClaimTokenP ct; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_end + }; + uint32_t pos32; + struct GNUNET_PQ_ResultSpec rs[] = { + /* contract_terms must be first! */ + TALER_PQ_result_spec_json ("contract_terms", + contract_terms), + GNUNET_PQ_result_spec_uint64 ("order_serial", + order_serial), + GNUNET_PQ_result_spec_bool ("paid", + paid), + GNUNET_PQ_result_spec_auto_from_type ("claim_token", + &ct), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_string ("pos_key", + pos_key), + NULL), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_uint32 ("pos_algorithm", + &pos32), + NULL), + GNUNET_PQ_result_spec_end + }; + + check_connection (pg); + PREPARE (pg, + "lookup_contract_terms2", + "SELECT" + " contract_terms" + ",order_serial" + ",claim_token" + ",paid" + ",pos_key" + ",pos_algorithm" + " FROM merchant_contract_terms" + " WHERE order_id=$2" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)"); + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_contract_terms2", + params, + (NULL != contract_terms) + ? rs + : &rs[1]); + *pos_algorithm = (enum TALER_MerchantConfirmationAlgorithm) pos32; + if (NULL != claim_token) + *claim_token = ct; + return qs; +} diff --git a/src/backenddb/pg_lookup_contract_terms2.h b/src/backenddb/pg_lookup_contract_terms2.h @@ -0,0 +1,54 @@ +/* + 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_lookup_contract_terms2.h + * @brief implementation of the lookup_contract_terms2 function for Postgres + * @author Iván Ávalos + */ +#ifndef PG_LOOKUP_CONTRACT_TERMS2_H +#define PG_LOOKUP_CONTRACT_TERMS2_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Retrieve contract terms given its @a order_id + * + * @param cls closure + * @param instance_id instance's identifier + * @param order_id order_id used to lookup. + * @param[out] contract_terms where to store the result, NULL to only check for existence + * @param[out] order_serial set to the order's serial number + * @param[out] paid set to true if the order is fully paid + * @param[out] claim_token set to the claim token, NULL to only check for existence + * @param[out] pos_key encoded key for payment verification + * @param[out] pos_algorithm algorithm to compute the payment verification + * @return transaction status + */ +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_contract_terms2 ( + void *cls, + const char *instance_id, + const char *order_id, + json_t **contract_terms, + uint64_t *order_serial, + bool *paid, + struct TALER_ClaimTokenP *claim_token, + char **pos_key, + enum TALER_MerchantConfirmationAlgorithm *pos_algorithm); + +#endif diff --git a/src/backenddb/pg_lookup_order.c b/src/backenddb/pg_lookup_order.c @@ -0,0 +1,96 @@ +/* + 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_lookup_order.c + * @brief Implementation of the lookup_order function for Postgres + * @author Iván Ávalos + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_lookup_order.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_order (void *cls, + const char *instance_id, + const char *order_id, + struct TALER_ClaimTokenP *claim_token, + struct TALER_MerchantPostDataHashP *h_post_data, + json_t **contract_terms) +{ + struct PostgresClosure *pg = cls; + json_t *j; + struct TALER_ClaimTokenP ct; + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_result_spec_json ("contract_terms", + &j), + GNUNET_PQ_result_spec_auto_from_type ("claim_token", + &ct), + GNUNET_PQ_result_spec_auto_from_type ("h_post_data", + h_post_data), + GNUNET_PQ_result_spec_end + }; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Finding contract term, order_id: '%s', instance_id: '%s'.\n", + order_id, + instance_id); + check_connection (pg); + PREPARE (pg, + "lookup_order", + "SELECT" + " contract_terms" + ",claim_token" + ",h_post_data" + ",pos_key" + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND merchant_orders.order_id=$2"); + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_order", + params, + rs); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + if (NULL != contract_terms) + *contract_terms = j; + else + json_decref (j); + if (NULL != claim_token) + *claim_token = ct; + } + else + { + /* just to be safe: NULL it */ + if (NULL != contract_terms) + *contract_terms = NULL; + if (NULL != claim_token) + *claim_token = (struct TALER_ClaimTokenP) { 0 } + ; + } + return qs; +} diff --git a/src/backenddb/pg_lookup_order.h b/src/backenddb/pg_lookup_order.h @@ -0,0 +1,49 @@ +/* + 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_lookup_order.h + * @brief implementation of the lookup_order function for Postgres + * @author Iván Ávalos + */ +#ifndef PG_LOOKUP_ORDER_H +#define PG_LOOKUP_ORDER_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Retrieve order given its @a order_id and the @a instance_id. + * + * @param cls closure + * @param instance_id instance to obtain order of + * @param order_id order id used to perform the lookup + * @param[out] claim_token the claim token generated for the order, + * NULL to only test if the order exists + * @param[out] h_post_data set to the hash of the POST data that created the order + * @param[out] contract_terms where to store the retrieved contract terms, + * NULL to only test if the order exists + * @return transaction status + */ +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_order (void *cls, + const char *instance_id, + const char *order_id, + struct TALER_ClaimTokenP *claim_token, + struct TALER_MerchantPostDataHashP *h_post_data, + json_t **contract_terms); + +#endif diff --git a/src/backenddb/pg_lookup_order_summary.c b/src/backenddb/pg_lookup_order_summary.c @@ -0,0 +1,75 @@ +/* + 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_lookup_order_summary.c + * @brief Implementation of the lookup_order_summary function for Postgres + * @author Iván Ávalos + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_lookup_order_summary.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_order_summary (void *cls, + const char *instance_id, + const char *order_id, + struct GNUNET_TIME_Timestamp *timestamp, + uint64_t *order_serial) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("order_serial", + order_serial), + GNUNET_PQ_result_spec_timestamp ("creation_time", + timestamp), + GNUNET_PQ_result_spec_end + }; + + check_connection (pg); + PREPARE (pg, + "lookup_order_summary", + "(SELECT" + " creation_time" + ",order_serial" + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND merchant_contract_terms.order_id=$2)" + "UNION" + "(SELECT" + " creation_time" + ",order_serial" + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND merchant_orders.order_id=$2)"); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_order_summary", + params, + rs); +} diff --git a/src/backenddb/pg_lookup_order_summary.h b/src/backenddb/pg_lookup_order_summary.h @@ -0,0 +1,45 @@ +/* + 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_lookup_order_summary.h + * @brief implementation of the lookup_order_summary function for Postgres + * @author Iván Ávalos + */ +#ifndef PG_LOOKUP_ORDER_SUMMARY_H +#define PG_LOOKUP_ORDER_SUMMARY_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Retrieve order summary given its @a order_id and the @a instance_id. + * + * @param cls closure + * @param instance_id instance to obtain order of + * @param order_id order id used to perform the lookup + * @param[out] timestamp when was the order created + * @param[out] order_serial under which serial do we keep this order + * @return transaction status + */ +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_order_summary (void *cls, + const char *instance_id, + const char *order_id, + struct GNUNET_TIME_Timestamp *timestamp, + uint64_t *order_serial); + +#endif diff --git a/src/backenddb/pg_lookup_orders.c b/src/backenddb/pg_lookup_orders.c @@ -0,0 +1,926 @@ +/* + 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_lookup_orders.c + * @brief Implementation of the lookup_orders function for Postgres + * @author Iván Ávalos + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_lookup_orders.h" +#include "pg_helper.h" + +/** + * Context used for TMH_PG_lookup_orders(). + */ +struct LookupOrdersContext +{ + /** + * Function to call with the results. + */ + TALER_MERCHANTDB_OrdersCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Did database result extraction fail? + */ + bool extract_failed; +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results about orders. + * + * @param[in,out] cls of type `struct LookupOrdersContext *` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +lookup_orders_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct LookupOrdersContext *plc = cls; + + for (unsigned int i = 0; i < num_results; i++) + { + char *order_id; + uint64_t order_serial; + struct GNUNET_TIME_Timestamp ts; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_string ("order_id", + &order_id), + GNUNET_PQ_result_spec_uint64 ("order_serial", + &order_serial), + GNUNET_PQ_result_spec_timestamp ("creation_time", + &ts), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + plc->extract_failed = true; + return; + } + plc->cb (plc->cb_cls, + order_id, + order_serial, + ts); + GNUNET_PQ_cleanup_result (rs); + } +} + + +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_orders (void *cls, + const char *instance_id, + const struct TALER_MERCHANTDB_OrderFilter *of, + TALER_MERCHANTDB_OrdersCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + struct LookupOrdersContext plc = { + .cb = cb, + .cb_cls = cb_cls + }; + uint64_t limit = (of->delta > 0) ? of->delta : -of->delta; + uint8_t paid; + uint8_t refunded; + uint8_t wired; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_uint64 (&limit), + GNUNET_PQ_query_param_uint64 (&of->start_row), + GNUNET_PQ_query_param_timestamp (&of->date), + GNUNET_PQ_query_param_auto_from_type (&paid), + GNUNET_PQ_query_param_auto_from_type (&refunded), + GNUNET_PQ_query_param_auto_from_type (&wired), + GNUNET_PQ_query_param_end + }; + enum GNUNET_DB_QueryStatus qs; + char stmt[128]; + + paid = (TALER_EXCHANGE_YNA_YES == of->paid); + refunded = (TALER_EXCHANGE_YNA_YES == of->refunded); + wired = (TALER_EXCHANGE_YNA_YES == of->wired); + /* painfully many cases..., note that "_xxx" being present in 'stmt' merely + means that we filter by that variable, the value we filter for is + computed above */ + GNUNET_snprintf (stmt, + sizeof (stmt), + "lookup_orders_%s%s%s%s", + (of->delta > 0) ? "inc" : "dec", + (TALER_EXCHANGE_YNA_ALL == of->paid) ? "" : "_paid", + (TALER_EXCHANGE_YNA_ALL == of->refunded) ? "" : + "_refunded", + (TALER_EXCHANGE_YNA_ALL == of->wired) ? "" : "_wired"); + PREPARE (pg, + "lookup_orders_inc", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " ORDER BY order_serial ASC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " ORDER BY order_serial ASC" + " LIMIT $2)" + " ORDER BY order_serial ASC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_inc_paid", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " AND" + " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " ORDER BY order_serial ASC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " AND" + " BOOL($5) = paid" + " ORDER BY order_serial ASC" + " LIMIT $2)" + " ORDER BY order_serial ASC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_inc_refunded", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " AND" + " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " ORDER BY order_serial ASC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " AND" + " CAST($6 as BOOL) = (order_serial IN" + " (SELECT order_serial " + " FROM merchant_refunds))" + " ORDER BY order_serial ASC" + " LIMIT $2)" + " ORDER BY order_serial ASC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_inc_wired", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " AND" + " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " ORDER BY order_serial ASC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " AND" + " BOOL($7) = wired" + " ORDER BY order_serial ASC" + " LIMIT $2)" + " ORDER BY order_serial ASC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_inc_paid_refunded", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " AND" + " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ + " AND" + " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " ORDER BY order_serial ASC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " AND" + " BOOL($5) = paid" + " AND" + " BOOL($6) = (order_serial IN" + " (SELECT order_serial " + " FROM merchant_refunds))" + " ORDER BY order_serial ASC" + " LIMIT $2)" + " ORDER BY order_serial ASC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_inc_paid_wired", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " AND" + " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ + " AND" + " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " ORDER BY order_serial ASC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " AND" + " BOOL($5) = paid" + " AND" + " BOOL($7) = wired" + " ORDER BY order_serial ASC" + " LIMIT $2)" + " ORDER BY order_serial ASC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_inc_refunded_wired", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " AND" + " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ + " AND" + " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " ORDER BY order_serial ASC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " AND" + " BOOL($6) = (order_serial IN" + " (SELECT order_serial " + " FROM merchant_refunds))" + " AND" + " BOOL($7) = wired" + " ORDER BY order_serial ASC" + " LIMIT $2)" + " ORDER BY order_serial ASC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_inc_paid_refunded_wired", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " AND" + " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ + " AND" + " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ + " AND" + " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " ORDER BY order_serial ASC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial > $3" + " AND" + " creation_time > $4" + " AND" + " BOOL($5) = paid" + " AND" + " BOOL($6) = (order_serial IN" + " (SELECT order_serial " + " FROM merchant_refunds))" + " AND" + " BOOL($7) = wired" + " ORDER BY order_serial ASC" + " LIMIT $2)" + " ORDER BY order_serial ASC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_dec", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " ORDER BY order_serial DESC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " ORDER BY order_serial DESC" + " LIMIT $2)" + " ORDER BY order_serial DESC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_dec_paid", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " AND" + " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " ORDER BY order_serial DESC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " AND" + " BOOL($5) = paid" + " ORDER BY order_serial DESC" + " LIMIT $2)" + " ORDER BY order_serial DESC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_dec_refunded", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " AND" + " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " ORDER BY order_serial DESC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " AND" + " BOOL($6) = (order_serial IN" + " (SELECT order_serial " + " FROM merchant_refunds))" + " ORDER BY order_serial DESC" + " LIMIT $2)" + " ORDER BY order_serial DESC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_dec_wired", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " AND" + " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " ORDER BY order_serial DESC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " AND" + " BOOL($7) = wired" + " ORDER BY order_serial DESC" + " LIMIT $2)" + " ORDER BY order_serial DESC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_dec_paid_refunded", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " AND" + " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ + " AND" + " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " ORDER BY order_serial DESC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " AND" + " BOOL($5) = paid" + " AND" + " BOOL($6) = (order_serial IN" + " (SELECT order_serial " + " FROM merchant_refunds))" + " ORDER BY order_serial DESC" + " LIMIT $2)" + " ORDER BY order_serial DESC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_dec_paid_wired", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " AND" + " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ + " AND" + " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " ORDER BY order_serial DESC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " AND" + " BOOL($5) = paid" + " AND" + " BOOL($7) = wired" + " ORDER BY order_serial DESC" + " LIMIT $2)" + " ORDER BY order_serial DESC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_dec_refunded_wired", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " AND" + " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ + " AND" + " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " ORDER BY order_serial DESC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " AND" + " BOOL($6) = (order_serial IN" + " (SELECT order_serial " + " FROM merchant_refunds))" + " AND" + " BOOL($7) = wired" + " ORDER BY order_serial DESC" + " LIMIT $2)" + " ORDER BY order_serial DESC" + " LIMIT $2"); + PREPARE (pg, + "lookup_orders_dec_paid_refunded_wired", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + " FROM merchant_orders" + " WHERE merchant_orders.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " AND" + " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ + " AND" + " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ + " AND" + " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " ORDER BY order_serial DESC" + " LIMIT $2)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + " FROM merchant_contract_terms" + " WHERE merchant_contract_terms.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND" + " order_serial < $3" + " AND" + " creation_time < $4" + " AND" + " BOOL($5) = paid" + " AND" + " BOOL($6) = (order_serial IN" + " (SELECT order_serial " + + " FROM merchant_refunds))" + " AND" + " BOOL($7) = wired" + " ORDER BY order_serial DESC" + " LIMIT $2)" + " ORDER BY order_serial DESC" + " LIMIT $2"); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + stmt, + params, + &lookup_orders_cb, + &plc); + if (plc.extract_failed) + return GNUNET_DB_STATUS_HARD_ERROR; + return qs; +} diff --git a/src/backenddb/pg_lookup_orders.h b/src/backenddb/pg_lookup_orders.h @@ -0,0 +1,45 @@ +/* + 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_lookup_orders.h + * @brief implementation of the lookup_orders function for Postgres + * @author Iván Ávalos + */ +#ifndef PG_LOOKUP_ORDERS_H +#define PG_LOOKUP_ORDERS_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Retrieve orders given the @a instance_id. + * + * @param cls closure + * @param instance_id instance to obtain order of + * @param of filter to apply when looking up orders + * @param cb callback to pass all the orders that are found + * @param cb_cls closure for @a cb + * @return transaction status + */ +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_orders (void *cls, + const char *instance_id, + const struct TALER_MERCHANTDB_OrderFilter *of, + TALER_MERCHANTDB_OrdersCallback cb, + void *cb_cls); + +#endif diff --git a/src/backenddb/pg_unlock_inventory.c b/src/backenddb/pg_unlock_inventory.c @@ -0,0 +1,47 @@ +/* + 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_unlock_inventory.c + * @brief Implementation of the unlock_inventory function for Postgres + * @author Iván Ávalos + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_unlock_inventory.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +TMH_PG_unlock_inventory (void *cls, + const struct GNUNET_Uuid *uuid) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (uuid), + GNUNET_PQ_query_param_end + }; + + check_connection (pg); + PREPARE (pg, + "unlock_inventory", + "DELETE" + " FROM merchant_inventory_locks" + " WHERE lock_uuid=$1"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "unlock_inventory", + params); +} diff --git a/src/backenddb/pg_unlock_inventory.h b/src/backenddb/pg_unlock_inventory.h @@ -0,0 +1,42 @@ +/* + 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_unlock_inventory.h + * @brief implementation of the unlock_inventory function for Postgres + * @author Iván Ávalos + */ +#ifndef PG_UNLOCK_INVENTORY_H +#define PG_UNLOCK_INVENTORY_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Release an inventory lock by UUID. Releases ALL stocks locked under + * the given UUID. + * + * @param cls closure + * @param uuid the UUID to release locks for + * @return transaction status, + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are no locks under @a uuid + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success + */ +enum GNUNET_DB_QueryStatus +TMH_PG_unlock_inventory (void *cls, + const struct GNUNET_Uuid *uuid); + +#endif diff --git a/src/backenddb/pg_update_contract_terms.c b/src/backenddb/pg_update_contract_terms.c @@ -0,0 +1,103 @@ +/* + 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_update_contract_terms.c + * @brief Implementation of the update_contract_terms function for Postgres + * @author Iván Ávalos + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_update_contract_terms.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +TMH_PG_update_contract_terms (void *cls, + const char *instance_id, + const char *order_id, + json_t *contract_terms) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Timestamp pay_deadline; + struct GNUNET_TIME_Timestamp refund_deadline; + const char *fulfillment_url = NULL; + struct TALER_PrivateContractHashP h_contract_terms; + + if (GNUNET_OK != + TALER_JSON_contract_hash (contract_terms, + &h_contract_terms)) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_timestamp ("pay_deadline", + &pay_deadline), + GNUNET_JSON_spec_timestamp ("refund_deadline", + &refund_deadline), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("fulfillment_url", + &fulfillment_url), + NULL), + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (NULL, + contract_terms, + spec); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } + + check_connection (pg); + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), + TALER_PQ_query_param_json (contract_terms), + GNUNET_PQ_query_param_auto_from_type (&h_contract_terms), + GNUNET_PQ_query_param_timestamp (&pay_deadline), + GNUNET_PQ_query_param_timestamp (&refund_deadline), + (NULL == fulfillment_url) + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_string (fulfillment_url), + GNUNET_PQ_query_param_end + }; + PREPARE (pg, + "update_contract_terms", + "UPDATE merchant_contract_terms SET" + " contract_terms=$3" + ",h_contract_terms=$4" + ",pay_deadline=$5" + ",refund_deadline=$6" + ",fulfillment_url=$7" + " WHERE order_id=$2" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "update_contract_terms", + params); + } +} diff --git a/src/backenddb/pg_update_contract_terms.h b/src/backenddb/pg_update_contract_terms.h @@ -0,0 +1,49 @@ +/* + 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_update_contract_terms.h + * @brief implementation of the update_contract_terms function for Postgres + * @author Iván Ávalos + */ +#ifndef PG_UPDATE_CONTRACT_TERMS_H +#define PG_UPDATE_CONTRACT_TERMS_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Update the contract terms stored for @a order_id. Note that some attributes are + * expected to be calculated inside of the function, like the hash of the + * contract terms (to be hashed), the creation_time and pay_deadline (to be + * obtained from the merchant_orders table). The "session_id" should be + * initially set to the empty string. The "fulfillment_url" and "refund_deadline" + * must be extracted from @a contract_terms. + * + * @param cls closure + * @param instance_id instance's identifier + * @param order_id order_id used to store + * @param contract_terms contract to store + * @return transaction status, #GNUNET_DB_STATUS_HARD_ERROR if @a contract_terms + * is malformed + */ +enum GNUNET_DB_QueryStatus +TMH_PG_update_contract_terms (void *cls, + const char *instance_id, + const char *order_id, + json_t *contract_terms); + +#endif diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c @@ -59,6 +59,19 @@ #include "pg_insert_product.h" #include "pg_update_product.h" #include "pg_lock_product.h" +#include "pg_expire_locks.h" +#include "pg_delete_order.h" +#include "pg_lookup_order.h" +#include "pg_lookup_order_summary.h" +#include "pg_lookup_orders.h" +#include "pg_insert_order.h" +#include "pg_unlock_inventory.h" +#include "pg_insert_order_lock.h" +#include "pg_lookup_contract_terms2.h" +#include "pg_lookup_contract_terms.h" +#include "pg_insert_contract_terms.h" +#include "pg_update_contract_terms.h" +#include "pg_delete_contract_terms.h" #include "pg_set_transfer_status_to_confirmed.h" @@ -337,262 +350,69 @@ postgres_commit (void *cls) /** - * Release all expired product locks, including - * those from expired offers -- across all - * instances. - * - * @param cls closure - */ -static void -postgres_expire_locks (void *cls) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_absolute_time (&now), - GNUNET_PQ_query_param_end - }; - enum GNUNET_DB_QueryStatus qs1; - enum GNUNET_DB_QueryStatus qs2; - enum GNUNET_DB_QueryStatus qs3; - - check_connection (pg); - qs1 = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "unlock_products", - params); - if (qs1 < 0) - { - GNUNET_break (0); - return; - } - qs2 = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "unlock_orders", - params); - if (qs2 < 0) - { - GNUNET_break (0); - return; - } - qs3 = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "unlock_contracts", - params); - if (qs3 < 0) - { - GNUNET_break (0); - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Released %d+%d+%d locks\n", - qs1, - qs2, - qs3); -} - - -/** - * Delete information about an order. Note that the transaction must - * enforce that the order is not awaiting payment anymore. - * - * @param cls closure - * @param instance_id instance to delete order of - * @param order_id order to delete - * @param force delete claimed but unpaid orders as well - * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS - * if pending payment prevents deletion OR order unknown - */ -static enum GNUNET_DB_QueryStatus -postgres_delete_order (void *cls, - const char *instance_id, - const char *order_id, - bool force) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (order_id), - GNUNET_PQ_query_param_absolute_time (&now), - GNUNET_PQ_query_param_bool (force), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_QueryParam params2[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (order_id), - GNUNET_PQ_query_param_end - }; - enum GNUNET_DB_QueryStatus qs; - - check_connection (pg); - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_order", - params); - if ( (qs <= 0) || (! force)) - return qs; - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_contract", - params2); -} - - -/** - * Retrieve order given its @a order_id and the @a instance_id. - * - * @param cls closure - * @param instance_id instance to obtain order of - * @param order_id order id used to perform the lookup - * @param[out] claim_token the claim token generated for the order, - * NULL to only test if the order exists - * @param[out] h_post_data set to the hash of the POST data that created the order - * @param[out] contract_terms where to store the retrieved contract terms, - * NULL to only test if the order exists - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_lookup_order (void *cls, - const char *instance_id, - const char *order_id, - struct TALER_ClaimTokenP *claim_token, - struct TALER_MerchantPostDataHashP *h_post_data, - json_t **contract_terms) -{ - struct PostgresClosure *pg = cls; - json_t *j; - struct TALER_ClaimTokenP ct; - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (order_id), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_result_spec_json ("contract_terms", - &j), - GNUNET_PQ_result_spec_auto_from_type ("claim_token", - &ct), - GNUNET_PQ_result_spec_auto_from_type ("h_post_data", - h_post_data), - GNUNET_PQ_result_spec_end - }; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Finding contract term, order_id: '%s', instance_id: '%s'.\n", - order_id, - instance_id); - check_connection (pg); - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_order", - params, - rs); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - if (NULL != contract_terms) - *contract_terms = j; - else - json_decref (j); - if (NULL != claim_token) - *claim_token = ct; - } - else - { - /* just to be safe: NULL it */ - if (NULL != contract_terms) - *contract_terms = NULL; - if (NULL != claim_token) - *claim_token = (struct TALER_ClaimTokenP) { 0 } - ; - } - return qs; -} - - -/** - * Retrieve order summary given its @a order_id and the @a instance_id. - * - * @param cls closure - * @param instance_id instance to obtain order of - * @param order_id order id used to perform the lookup - * @param[out] timestamp when was the order created - * @param[out] order_serial under which serial do we keep this order - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_lookup_order_summary (void *cls, - const char *instance_id, - const char *order_id, - struct GNUNET_TIME_Timestamp *timestamp, - uint64_t *order_serial) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (order_id), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("order_serial", - order_serial), - GNUNET_PQ_result_spec_timestamp ("creation_time", - timestamp), - GNUNET_PQ_result_spec_end - }; - - check_connection (pg); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_order_summary", - params, - rs); -} - - -/** - * Context used for postgres_lookup_orders(). + * Closure for #lookup_deposits_cb(). */ -struct LookupOrdersContext +struct LookupDepositsContext { /** - * Function to call with the results. + * Function to call with results. */ - TALER_MERCHANTDB_OrdersCallback cb; + TALER_MERCHANTDB_DepositsCallback cb; /** - * Closure for @a cb. + * Closure for @e cls. */ void *cb_cls; /** - * Did database result extraction fail? + * Plugin context. */ - bool extract_failed; + struct PostgresClosure *pg; + + /** + * Transaction status (set). + */ + enum GNUNET_DB_QueryStatus qs; }; /** * Function to be called with the results of a SELECT statement - * that has returned @a num_results results about orders. + * that has returned @a num_results results. * - * @param[in,out] cls of type `struct LookupOrdersContext *` + * @param[in,out] cls of type `struct LookupDepositsContext *` * @param result the postgres result * @param num_results the number of results in @a result */ static void -lookup_orders_cb (void *cls, - PGresult *result, - unsigned int num_results) +lookup_deposits_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct LookupOrdersContext *plc = cls; + struct LookupDepositsContext *ldc = cls; + struct PostgresClosure *pg = ldc->pg; - for (unsigned int i = 0; i < num_results; i++) + for (unsigned int i = 0; i<num_results; i++) { - char *order_id; - uint64_t order_serial; - struct GNUNET_TIME_Timestamp ts; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_Amount amount_with_fee; + struct TALER_Amount deposit_fee; + struct TALER_Amount refund_fee; + struct TALER_Amount wire_fee; + char *exchange_url; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_string ("order_id", - &order_id), - GNUNET_PQ_result_spec_uint64 ("order_serial", - &order_serial), - GNUNET_PQ_result_spec_timestamp ("creation_time", - &ts), + GNUNET_PQ_result_spec_string ("exchange_url", + &exchange_url), + GNUNET_PQ_result_spec_auto_from_type ("coin_pub", + &coin_pub), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", + &amount_with_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee", + &deposit_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee", + &refund_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", + &wire_fee), GNUNET_PQ_result_spec_end }; @@ -602,553 +422,583 @@ lookup_orders_cb (void *cls, i)) { GNUNET_break (0); - plc->extract_failed = true; + ldc->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } - plc->cb (plc->cb_cls, - order_id, - order_serial, - ts); + ldc->cb (ldc->cb_cls, + exchange_url, + &coin_pub, + &amount_with_fee, + &deposit_fee, + &refund_fee, + &wire_fee); GNUNET_PQ_cleanup_result (rs); } + ldc->qs = num_results; } /** - * Retrieve orders given the @a instance_id. + * Lookup information about coins that were successfully deposited for a + * given contract. * * @param cls closure - * @param instance_id instance to obtain order of - * @param of filter to apply when looking up orders - * @param cb callback to pass all the orders that are found + * @param instance_id instance to lookup deposits for + * @param h_contract_terms proposal data's hashcode + * @param cb function to call with payment data * @param cb_cls closure for @a cb * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_lookup_orders (void *cls, - const char *instance_id, - const struct TALER_MERCHANTDB_OrderFilter *of, - TALER_MERCHANTDB_OrdersCallback cb, - void *cb_cls) +postgres_lookup_deposits ( + void *cls, + const char *instance_id, + const struct TALER_PrivateContractHashP *h_contract_terms, + TALER_MERCHANTDB_DepositsCallback cb, + void *cb_cls) { struct PostgresClosure *pg = cls; - struct LookupOrdersContext plc = { - .cb = cb, - .cb_cls = cb_cls - }; - uint64_t limit = (of->delta > 0) ? of->delta : -of->delta; - uint8_t paid; - uint8_t refunded; - uint8_t wired; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_uint64 (&limit), - GNUNET_PQ_query_param_uint64 (&of->start_row), - GNUNET_PQ_query_param_timestamp (&of->date), - GNUNET_PQ_query_param_auto_from_type (&paid), - GNUNET_PQ_query_param_auto_from_type (&refunded), - GNUNET_PQ_query_param_auto_from_type (&wired), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_end }; + struct LookupDepositsContext ldc = { + .cb = cb, + .cb_cls = cb_cls, + .pg = pg + }; enum GNUNET_DB_QueryStatus qs; - char stmt[128]; - paid = (TALER_EXCHANGE_YNA_YES == of->paid); - refunded = (TALER_EXCHANGE_YNA_YES == of->refunded); - wired = (TALER_EXCHANGE_YNA_YES == of->wired); - /* painfully many cases..., note that "_xxx" being present in 'stmt' merely - means that we filter by that variable, the value we filter for is - computed above */ - GNUNET_snprintf (stmt, - sizeof (stmt), - "lookup_orders_%s%s%s%s", - (of->delta > 0) ? "inc" : "dec", - (TALER_EXCHANGE_YNA_ALL == of->paid) ? "" : "_paid", - (TALER_EXCHANGE_YNA_ALL == of->refunded) ? "" : - "_refunded", - (TALER_EXCHANGE_YNA_ALL == of->wired) ? "" : "_wired"); + /* no preflight check here, run in its own transaction by the caller! */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Finding deposits for h_contract_terms '%s'\n", + GNUNET_h2s (&h_contract_terms->hash)); + check_connection (pg); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - stmt, + "lookup_deposits", params, - &lookup_orders_cb, - &plc); - if (plc.extract_failed) - return GNUNET_DB_STATUS_HARD_ERROR; - return qs; + &lookup_deposits_cb, + &ldc); + if (qs <= 0) + return qs; + return ldc.qs; } /** - * Insert order into the DB. + * Insert an exchange signing key into our database. * * @param cls closure - * @param instance_id identifies the instance responsible for the order - * @param order_id alphanumeric string that uniquely identifies the proposal - * @param h_post_data hash of the POST data for idempotency checks - * @param pay_deadline how long does the customer have to pay for the order - * @param claim_token token to use for access control - * @param contract_terms proposal data to store - * @param pos_key encoded key for payment verification - * @param pos_algorithm algorithm to compute the payment verification - * @return transaction status + * @param master_pub exchange master public key used for @a master_sig + * @param exchange_pub exchange signing key to insert + * @param start_date when does the signing key become valid + * @param expire_date when does the signing key stop being used + * @param end_date when does the signing key become void as proof + * @param master_sig signature of @a master_pub over the @a exchange_pub and the dates */ static enum GNUNET_DB_QueryStatus -postgres_insert_order (void *cls, - const char *instance_id, - const char *order_id, - const struct TALER_MerchantPostDataHashP *h_post_data, - struct GNUNET_TIME_Timestamp pay_deadline, - const struct TALER_ClaimTokenP *claim_token, - const json_t *contract_terms, - const char *pos_key, - enum TALER_MerchantConfirmationAlgorithm pos_algorithm) +postgres_insert_exchange_signkey ( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub, + const struct TALER_ExchangePublicKeyP *exchange_pub, + struct GNUNET_TIME_Timestamp start_date, + struct GNUNET_TIME_Timestamp expire_date, + struct GNUNET_TIME_Timestamp end_date, + const struct TALER_MasterSignatureP *master_sig) { struct PostgresClosure *pg = cls; - struct GNUNET_TIME_Timestamp now; - uint32_t pos32 = (uint32_t) pos_algorithm; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (order_id), - GNUNET_PQ_query_param_timestamp (&pay_deadline), - GNUNET_PQ_query_param_auto_from_type (claim_token), - GNUNET_PQ_query_param_auto_from_type (h_post_data), - GNUNET_PQ_query_param_timestamp (&now), - TALER_PQ_query_param_json (contract_terms), - (NULL == pos_key) - ? GNUNET_PQ_query_param_null () - : GNUNET_PQ_query_param_string (pos_key), - GNUNET_PQ_query_param_uint32 (&pos32), + GNUNET_PQ_query_param_auto_from_type (master_pub), + GNUNET_PQ_query_param_auto_from_type (exchange_pub), + GNUNET_PQ_query_param_timestamp (&start_date), + GNUNET_PQ_query_param_timestamp (&expire_date), + GNUNET_PQ_query_param_timestamp (&end_date), + GNUNET_PQ_query_param_auto_from_type (master_sig), GNUNET_PQ_query_param_end }; - now = GNUNET_TIME_timestamp_get (); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "inserting order: order_id: %s, instance_id: %s.\n", - order_id, - instance_id); check_connection (pg); + postgres_preflight (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_order", + "insert_exchange_signkey", params); + } /** - * Release an inventory lock by UUID. Releases ALL stocks locked under - * the given UUID. + * Insert payment confirmation from the exchange into the database. * * @param cls closure - * @param uuid the UUID to release locks for - * @return transaction status, - * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are no locks under @a uuid - * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success - */ -static enum GNUNET_DB_QueryStatus -postgres_unlock_inventory (void *cls, - const struct GNUNET_Uuid *uuid) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (uuid), - GNUNET_PQ_query_param_end + * @param instance_id instance to lookup deposits for + * @param deposit_timestamp time when the exchange generated the deposit confirmation + * @param h_contract_terms proposal data's hashcode + * @param coin_pub public key of the coin + * @param exchange_url URL of the exchange that issued @a coin_pub + * @param amount_with_fee amount the exchange will deposit for this coin + * @param deposit_fee fee the exchange will charge for this coin + * @param wire_fee wire fee the exchange charges + * @param refund_fee fee the exchange charges to refund this coin + * @param h_wire hash of the wire details of the target account of the merchant + * @param exchange_sig signature from exchange that coin was accepted + * @param exchange_pub signgin key that was used for @a exchange_sig + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +postgres_insert_deposit ( + void *cls, + const char *instance_id, + struct GNUNET_TIME_Timestamp deposit_timestamp, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const char *exchange_url, + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *deposit_fee, + const struct TALER_Amount *refund_fee, + const struct TALER_Amount *wire_fee, + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_ExchangeSignatureP *exchange_sig, + const struct TALER_ExchangePublicKeyP *exchange_pub) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_timestamp (&deposit_timestamp), /* $3 */ + GNUNET_PQ_query_param_auto_from_type (coin_pub), + GNUNET_PQ_query_param_string (exchange_url), + TALER_PQ_query_param_amount (amount_with_fee), /* $6/$7 */ + TALER_PQ_query_param_amount (deposit_fee), /* $8, $9 */ + TALER_PQ_query_param_amount (refund_fee), /* $10, $11 */ + TALER_PQ_query_param_amount (wire_fee), /* $12, $13 */ + GNUNET_PQ_query_param_auto_from_type (h_wire), /* $14 */ + GNUNET_PQ_query_param_auto_from_type (exchange_sig), /* $15 */ + GNUNET_PQ_query_param_auto_from_type (exchange_pub), /* $16 */ + GNUNET_PQ_query_param_end }; + /* no preflight check here, run in transaction by caller! */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Storing deposit for instance `%s' h_contract_terms `%s', coin_pub: `%s', amount_with_fee: %s\n", + instance_id, + GNUNET_h2s (&h_contract_terms->hash), + TALER_B2S (coin_pub), + TALER_amount2s (amount_with_fee)); check_connection (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "unlock_inventory", + "insert_deposit", params); + } /** - * Lock inventory stock to a particular order. + * Closure for #lookup_refunds_cb(). + */ +struct LookupRefundsContext +{ + /** + * Function to call for each refund. + */ + TALER_MERCHANTDB_RefundCallback rc; + + /** + * Closure for @e rc. + */ + void *rc_cls; + + /** + * Plugin context. + */ + struct PostgresClosure *pg; + + /** + * Transaction result. + */ + enum GNUNET_DB_QueryStatus qs; +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. * - * @param cls closure - * @param instance_id identifies the instance responsible for the order - * @param order_id alphanumeric string that uniquely identifies the order - * @param product_id uniquely identifies the product to be locked - * @param quantity how many units should be locked to the @a order_id - * @return transaction status, - * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are insufficient stocks - * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success + * @param cls of type `struct LookupRefundsContext *` + * @param result the postgres result + * @param num_results the number of results in @a result */ -static enum GNUNET_DB_QueryStatus -postgres_insert_order_lock (void *cls, - const char *instance_id, - const char *order_id, - const char *product_id, - uint64_t quantity) +static void +lookup_refunds_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (order_id), - GNUNET_PQ_query_param_string (product_id), - GNUNET_PQ_query_param_uint64 (&quantity), - GNUNET_PQ_query_param_end - }; + struct LookupRefundsContext *lrc = cls; + struct PostgresClosure *pg = lrc->pg; - check_connection (pg); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_order_lock", - params); + for (unsigned int i = 0; i<num_results; i++) + { + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_Amount refund_amount; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("coin_pub", + &coin_pub), + TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", + &refund_amount), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + lrc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return; + } + lrc->rc (lrc->rc_cls, + &coin_pub, + &refund_amount); + GNUNET_PQ_cleanup_result (rs); /* technically useless here */ + } + lrc->qs = num_results; } /** - * Retrieve contract terms given its @a order_id + * Obtain refunds associated with a contract. * - * @param cls closure - * @param instance_id instance's identifier - * @param order_id order_id used to lookup. - * @param[out] contract_terms where to store the result, NULL to only check for existence - * @param[out] order_serial set to the order's serial number - * @param[out] paid set to true if the order is fully paid - * @param[out] claim_token set to the claim token, NULL to only check for existence - * @param[out] pos_key encoded key for payment verification - * @param[out] pos_algorithm algorithm to compute the payment verification + * @param cls closure, typically a connection to the db + * @param instance_id instance to lookup refunds for + * @param h_contract_terms hash code of the contract + * @param rc function to call for each coin on which there is a refund + * @param rc_cls closure for @a rc * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_lookup_contract_terms2 ( +postgres_lookup_refunds ( void *cls, const char *instance_id, - const char *order_id, - json_t **contract_terms, - uint64_t *order_serial, - bool *paid, - struct TALER_ClaimTokenP *claim_token, - char **pos_key, - enum TALER_MerchantConfirmationAlgorithm *pos_algorithm) + const struct TALER_PrivateContractHashP *h_contract_terms, + TALER_MERCHANTDB_RefundCallback rc, + void *rc_cls) { struct PostgresClosure *pg = cls; - enum GNUNET_DB_QueryStatus qs; - struct TALER_ClaimTokenP ct; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_end }; - uint32_t pos32; - struct GNUNET_PQ_ResultSpec rs[] = { - /* contract_terms must be first! */ - TALER_PQ_result_spec_json ("contract_terms", - contract_terms), - GNUNET_PQ_result_spec_uint64 ("order_serial", - order_serial), - GNUNET_PQ_result_spec_bool ("paid", - paid), - GNUNET_PQ_result_spec_auto_from_type ("claim_token", - &ct), - GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("pos_key", - pos_key), - NULL), - GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_uint32 ("pos_algorithm", - &pos32), - NULL), - GNUNET_PQ_result_spec_end + struct LookupRefundsContext lrc = { + .rc = rc, + .rc_cls = rc_cls, + .pg = pg }; + enum GNUNET_DB_QueryStatus qs; + /* no preflight check here, run in transaction by caller! */ + TALER_LOG_DEBUG ("Looking for refund of h_contract_terms %s at `%s'\n", + GNUNET_h2s (&h_contract_terms->hash), + instance_id); check_connection (pg); - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_contract_terms2", - params, - (NULL != contract_terms) - ? rs - : &rs[1]); - *pos_algorithm = (enum TALER_MerchantConfirmationAlgorithm) pos32; - if (NULL != claim_token) - *claim_token = ct; - return qs; + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "lookup_refunds", + params, + &lookup_refunds_cb, + &lrc); + if (0 >= qs) + return qs; + return lrc.qs; } /** - * Retrieve contract terms given its @a order_id + * Mark contract as paid and store the current @a session_id + * for which the contract was paid. Deletes the underlying order + * and marks the locked stocks of the order as sold. * * @param cls closure - * @param instance_id instance's identifier - * @param order_id order_id used to lookup. - * @param[out] contract_terms where to store the result, NULL to only check for existence - * @param[out] order_serial set to the order's serial number - * @param[out] paid set to true if the order is fully paid - * @param[out] claim_token set to token to use for access control + * @param instance_id instance to mark contract as paid for + * @param h_contract_terms hash of the contract that is now paid + * @param session_id the session that paid the contract * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_lookup_contract_terms ( +postgres_mark_contract_paid ( void *cls, const char *instance_id, - const char *order_id, - json_t **contract_terms, - uint64_t *order_serial, - bool *paid, - struct TALER_ClaimTokenP *claim_token) + const struct TALER_PrivateContractHashP *h_contract_terms, + const char *session_id) { struct PostgresClosure *pg = cls; - enum GNUNET_DB_QueryStatus qs; - struct TALER_ClaimTokenP ct; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_string (session_id), GNUNET_PQ_query_param_end }; - struct GNUNET_PQ_ResultSpec rs[] = { - /* contract_terms must be first! */ - TALER_PQ_result_spec_json ("contract_terms", - contract_terms), - GNUNET_PQ_result_spec_uint64 ("order_serial", - order_serial), - GNUNET_PQ_result_spec_bool ("paid", - paid), - GNUNET_PQ_result_spec_auto_from_type ("claim_token", - &ct), - GNUNET_PQ_result_spec_end + struct GNUNET_PQ_QueryParam uparams[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_end }; + enum GNUNET_DB_QueryStatus qs; - check_connection (pg); - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_contract_terms", - params, - (NULL != contract_terms) - ? rs - : &rs[1]); - if (NULL != claim_token) - *claim_token = ct; - return qs; + /* Session ID must always be given by the caller. */ + GNUNET_assert (NULL != session_id); + + /* no preflight check here, run in transaction by caller! */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Marking h_contract_terms '%s' of %s as paid for session `%s'\n", + GNUNET_h2s (&h_contract_terms->hash), + instance_id, + session_id); + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "mark_contract_paid", + params); + if (qs <= 0) + return qs; + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "mark_inventory_sold", + uparams); + if (qs < 0) + return qs; /* 0: no inventory management, that's OK! */ + /* ON DELETE CASCADE deletes from merchant_order_locks */ + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "delete_completed_order", + uparams); } /** - * Store contract terms given its @a order_id. Note that some attributes are - * expected to be calculated inside of the function, like the hash of the - * contract terms (to be hashed), the creation_time and pay_deadline (to be - * obtained from the merchant_orders table). The "session_id" should be - * initially set to the empty string. The "fulfillment_url" and "refund_deadline" - * must be extracted from @a contract_terms. + * Function called during aborts to refund a coin. Marks the + * respective coin as refunded. * * @param cls closure - * @param instance_id instance's identifier - * @param order_id order_id used to store - * @param contract_terms contract terms to store - * @param[out] order_serial set to the serial of the order - * @return transaction status, #GNUNET_DB_STATUS_HARD_ERROR if @a contract_terms - * is malformed + * @param instance_id instance to refund payment for + * @param h_contract_terms hash of the contract to refund coin for + * @param refund_timestamp timestamp of the refund + * @param coin_pub public key of the coin to refund (fully) + * @param reason text justifying the refund + * @return transaction status + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a coin_pub is unknown to us; + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid, + * regardless of whether it actually increased the refund */ static enum GNUNET_DB_QueryStatus -postgres_insert_contract_terms ( - void *cls, - const char *instance_id, - const char *order_id, - json_t *contract_terms, - uint64_t *order_serial) +postgres_refund_coin (void *cls, + const char *instance_id, + const struct TALER_PrivateContractHashP *h_contract_terms, + struct GNUNET_TIME_Timestamp refund_timestamp, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const char *reason) { struct PostgresClosure *pg = cls; - struct GNUNET_TIME_Timestamp pay_deadline; - struct GNUNET_TIME_Timestamp refund_deadline; - const char *fulfillment_url; - struct TALER_PrivateContractHashP h_contract_terms; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_timestamp (&refund_timestamp), + GNUNET_PQ_query_param_auto_from_type (coin_pub), + GNUNET_PQ_query_param_string (reason), + GNUNET_PQ_query_param_end + }; - if (GNUNET_OK != - TALER_JSON_contract_hash (contract_terms, - &h_contract_terms)) - { - GNUNET_break (0); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_timestamp ("pay_deadline", - &pay_deadline), - GNUNET_JSON_spec_timestamp ("refund_deadline", - &refund_deadline), - GNUNET_JSON_spec_end () - }; - enum GNUNET_GenericReturnValue res; - - res = TALER_MHD_parse_json_data (NULL, - contract_terms, - spec); - if (GNUNET_OK != res) - { - GNUNET_break (0); - return GNUNET_DB_STATUS_HARD_ERROR; - } - } - - fulfillment_url = - json_string_value (json_object_get (contract_terms, - "fulfillment_url")); - check_connection (pg); - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (order_id), - TALER_PQ_query_param_json (contract_terms), - GNUNET_PQ_query_param_auto_from_type (&h_contract_terms), - GNUNET_PQ_query_param_timestamp (&pay_deadline), - GNUNET_PQ_query_param_timestamp (&refund_deadline), - (NULL == fulfillment_url) - ? GNUNET_PQ_query_param_null () - : GNUNET_PQ_query_param_string (fulfillment_url), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("order_serial", - order_serial), - GNUNET_PQ_result_spec_end - }; - - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_contract_terms", - params, - rs); - } + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "refund_coin", + params); } /** - * Update the contract terms stored for @a order_id. Note that some attributes are - * expected to be calculated inside of the function, like the hash of the - * contract terms (to be hashed), the creation_time and pay_deadline (to be - * obtained from the merchant_orders table). The "session_id" should be - * initially set to the empty string. The "fulfillment_url" and "refund_deadline" - * must be extracted from @a contract_terms. + * Retrieve contract terms given its @a order_id * * @param cls closure * @param instance_id instance's identifier - * @param order_id order_id used to store - * @param contract_terms contract to store - * @return transaction status, #GNUNET_DB_STATUS_HARD_ERROR if @a contract_terms - * is malformed + * @param order_id order to lookup contract for + * @param[out] h_contract_terms set to the hash of the contract. + * @param[out] paid set to the payment status of the contract + * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_update_contract_terms (void *cls, - const char *instance_id, - const char *order_id, - json_t *contract_terms) +postgres_lookup_order_status ( + void *cls, + const char *instance_id, + const char *order_id, + struct TALER_PrivateContractHashP *h_contract_terms, + bool *paid) { struct PostgresClosure *pg = cls; - struct GNUNET_TIME_Timestamp pay_deadline; - struct GNUNET_TIME_Timestamp refund_deadline; - const char *fulfillment_url = NULL; - struct TALER_PrivateContractHashP h_contract_terms; - - if (GNUNET_OK != - TALER_JSON_contract_hash (contract_terms, - &h_contract_terms)) - { - GNUNET_break (0); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_timestamp ("pay_deadline", - &pay_deadline), - GNUNET_JSON_spec_timestamp ("refund_deadline", - &refund_deadline), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("fulfillment_url", - &fulfillment_url), - NULL), - GNUNET_JSON_spec_end () - }; - enum GNUNET_GenericReturnValue res; - - res = TALER_MHD_parse_json_data (NULL, - contract_terms, - spec); - if (GNUNET_OK != res) - { - GNUNET_break (0); - return GNUNET_DB_STATUS_HARD_ERROR; - } - } + uint8_t paid8; + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", + h_contract_terms), + GNUNET_PQ_result_spec_auto_from_type ("paid", + &paid8), + GNUNET_PQ_result_spec_end + }; check_connection (pg); - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (order_id), - TALER_PQ_query_param_json (contract_terms), - GNUNET_PQ_query_param_auto_from_type (&h_contract_terms), - GNUNET_PQ_query_param_timestamp (&pay_deadline), - GNUNET_PQ_query_param_timestamp (&refund_deadline), - (NULL == fulfillment_url) - ? GNUNET_PQ_query_param_null () - : GNUNET_PQ_query_param_string (fulfillment_url), - GNUNET_PQ_query_param_end - }; - - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_contract_terms", - params); - } + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_order_status", + params, + rs); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + *paid = (0 != paid8); + else + *paid = false; /* just to be safe(r) */ + return qs; } /** - * Delete information about a contract. Note that the transaction must - * enforce that the contract is not awaiting payment anymore AND was not - * paid, or is past the legal expiration. + * Retrieve contract terms given its @a order_serial * * @param cls closure - * @param instance_id instance to delete order of - * @param order_id order to delete - * @param legal_expiration how long do we need to keep (paid) contracts on - * file for legal reasons (i.e. taxation) - * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS - * if locks prevent deletion OR order unknown + * @param instance_id instance's identifier + * @param order_serial serial ID of the order to look up + * @param[out] order_id set to ID of the order + * @param[out] h_contract_terms set to the hash of the contract. + * @param[out] paid set to the payment status of the contract + * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_delete_contract_terms (void *cls, - const char *instance_id, - const char *order_id, - struct GNUNET_TIME_Relative legal_expiration) +postgres_lookup_order_status_by_serial (void *cls, + const char *instance_id, + uint64_t order_serial, + char **order_id, + struct TALER_PrivateContractHashP * + h_contract_terms, + bool *paid) { struct PostgresClosure *pg = cls; - struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + uint8_t paid8; + enum GNUNET_DB_QueryStatus qs; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (order_id), - GNUNET_PQ_query_param_relative_time (&legal_expiration), - GNUNET_PQ_query_param_absolute_time (&now), + GNUNET_PQ_query_param_uint64 (&order_serial), GNUNET_PQ_query_param_end }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", + h_contract_terms), + GNUNET_PQ_result_spec_auto_from_type ("paid", + &paid8), + GNUNET_PQ_result_spec_string ("order_id", + order_id), + GNUNET_PQ_result_spec_end + }; check_connection (pg); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_contract_terms", - params); + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_order_status_by_serial", + params, + rs); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + *paid = (0 != paid8); + else + *paid = false; /* just to be safe(r) */ + return qs; } /** - * Closure for #lookup_deposits_cb(). + * Retrieve payment and wire status for a given @a order_serial and session ID. + * + * @param cls closure + * @param order_serial identifies the order + * @param session_id session for which to check the payment status, NULL for any + * @param[out] paid set to the payment status of the contract + * @param[out] wired set to the wire transfer status of the exchange payment + * @return transaction status */ -struct LookupDepositsContext +static enum GNUNET_DB_QueryStatus +postgres_lookup_payment_status (void *cls, + uint64_t order_serial, + const char *session_id, + bool *paid, + bool *wired) +{ + struct PostgresClosure *pg = cls; + uint8_t paid8; + uint8_t wired8; + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("paid", + &paid8), + GNUNET_PQ_result_spec_auto_from_type ("wired", + &wired8), + GNUNET_PQ_result_spec_end + }; + check_connection (pg); + if (NULL == session_id) + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&order_serial), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_payment_status", + params, + rs); + } + else + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&order_serial), + GNUNET_PQ_query_param_string (session_id), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_payment_status_session_id", + params, + rs); + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + *paid = (0 != paid8); + *wired = (0 != wired8); + } + else + { + *paid = false; /* just to be safe(r) */ + *wired = false; /* just to be safe(r) */ + } + return qs; +} + + +/** + * Closure for lookup_deposits_by_order_cb(). + */ +struct LookupDepositsByOrderContext { + /** - * Function to call with results. + * Plugin context. */ - TALER_MERCHANTDB_DepositsCallback cb; + struct PostgresClosure *pg; /** - * Closure for @e cls. + * Function to call with all results. */ - void *cb_cls; + TALER_MERCHANTDB_DepositedCoinsCallback cb; /** - * Plugin context. + * Closure for @e cb. */ - struct PostgresClosure *pg; + void *cb_cls; /** - * Transaction status (set). + * Set to the query result. */ enum GNUNET_DB_QueryStatus qs; }; @@ -1158,39 +1008,39 @@ struct LookupDepositsContext * Function to be called with the results of a SELECT statement * that has returned @a num_results results. * - * @param[in,out] cls of type `struct LookupDepositsContext *` + * @param cls of type `struct LookupDepositsByOrderContext *` * @param result the postgres result * @param num_results the number of results in @a result */ static void -lookup_deposits_cb (void *cls, - PGresult *result, - unsigned int num_results) +lookup_deposits_by_order_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct LookupDepositsContext *ldc = cls; - struct PostgresClosure *pg = ldc->pg; + struct LookupDepositsByOrderContext *ldoc = cls; + struct PostgresClosure *pg = ldoc->pg; for (unsigned int i = 0; i<num_results; i++) { + uint64_t deposit_serial; + char *exchange_url; + struct TALER_MerchantWireHashP h_wire; struct TALER_CoinSpendPublicKeyP coin_pub; struct TALER_Amount amount_with_fee; struct TALER_Amount deposit_fee; - struct TALER_Amount refund_fee; - struct TALER_Amount wire_fee; - char *exchange_url; struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("deposit_serial", + &deposit_serial), GNUNET_PQ_result_spec_string ("exchange_url", &exchange_url), - GNUNET_PQ_result_spec_auto_from_type ("coin_pub", - &coin_pub), + GNUNET_PQ_result_spec_auto_from_type ("h_wire", + &h_wire), TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", &amount_with_fee), TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee", &deposit_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee", - &refund_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", - &wire_fee), + GNUNET_PQ_result_spec_auto_from_type ("coin_pub", + &coin_pub), GNUNET_PQ_result_spec_end }; @@ -1200,199 +1050,83 @@ lookup_deposits_cb (void *cls, i)) { GNUNET_break (0); - ldc->qs = GNUNET_DB_STATUS_HARD_ERROR; + ldoc->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } - ldc->cb (ldc->cb_cls, - exchange_url, - &coin_pub, - &amount_with_fee, - &deposit_fee, - &refund_fee, - &wire_fee); - GNUNET_PQ_cleanup_result (rs); + ldoc->cb (ldoc->cb_cls, + deposit_serial, + exchange_url, + &h_wire, + &amount_with_fee, + &deposit_fee, + &coin_pub); + GNUNET_PQ_cleanup_result (rs); /* technically useless here */ } - ldc->qs = num_results; + ldoc->qs = num_results; } /** - * Lookup information about coins that were successfully deposited for a - * given contract. + * Retrieve details about coins that were deposited for an order. * * @param cls closure - * @param instance_id instance to lookup deposits for - * @param h_contract_terms proposal data's hashcode - * @param cb function to call with payment data + * @param order_serial identifies the order + * @param cb function to call for each deposited coin * @param cb_cls closure for @a cb * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_lookup_deposits ( - void *cls, - const char *instance_id, - const struct TALER_PrivateContractHashP *h_contract_terms, - TALER_MERCHANTDB_DepositsCallback cb, - void *cb_cls) +postgres_lookup_deposits_by_order (void *cls, + uint64_t order_serial, + TALER_MERCHANTDB_DepositedCoinsCallback cb, + void *cb_cls) { struct PostgresClosure *pg = cls; + struct LookupDepositsByOrderContext ldoc = { + .pg = pg, + .cb = cb, + .cb_cls = cb_cls + }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_uint64 (&order_serial), GNUNET_PQ_query_param_end }; - struct LookupDepositsContext ldc = { - .cb = cb, - .cb_cls = cb_cls, - .pg = pg - }; enum GNUNET_DB_QueryStatus qs; - /* no preflight check here, run in its own transaction by the caller! */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Finding deposits for h_contract_terms '%s'\n", - GNUNET_h2s (&h_contract_terms->hash)); - check_connection (pg); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_deposits", + "lookup_deposits_by_order", params, - &lookup_deposits_cb, - &ldc); - if (qs <= 0) + &lookup_deposits_by_order_cb, + &ldoc); + if (qs < 0) return qs; - return ldc.qs; -} - - -/** - * Insert an exchange signing key into our database. - * - * @param cls closure - * @param master_pub exchange master public key used for @a master_sig - * @param exchange_pub exchange signing key to insert - * @param start_date when does the signing key become valid - * @param expire_date when does the signing key stop being used - * @param end_date when does the signing key become void as proof - * @param master_sig signature of @a master_pub over the @a exchange_pub and the dates - */ -static enum GNUNET_DB_QueryStatus -postgres_insert_exchange_signkey ( - void *cls, - const struct TALER_MasterPublicKeyP *master_pub, - const struct TALER_ExchangePublicKeyP *exchange_pub, - struct GNUNET_TIME_Timestamp start_date, - struct GNUNET_TIME_Timestamp expire_date, - struct GNUNET_TIME_Timestamp end_date, - const struct TALER_MasterSignatureP *master_sig) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (master_pub), - GNUNET_PQ_query_param_auto_from_type (exchange_pub), - GNUNET_PQ_query_param_timestamp (&start_date), - GNUNET_PQ_query_param_timestamp (&expire_date), - GNUNET_PQ_query_param_timestamp (&end_date), - GNUNET_PQ_query_param_auto_from_type (master_sig), - GNUNET_PQ_query_param_end - }; - - check_connection (pg); - postgres_preflight (pg); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_exchange_signkey", - params); - + return ldoc.qs; } /** - * Insert payment confirmation from the exchange into the database. - * - * @param cls closure - * @param instance_id instance to lookup deposits for - * @param deposit_timestamp time when the exchange generated the deposit confirmation - * @param h_contract_terms proposal data's hashcode - * @param coin_pub public key of the coin - * @param exchange_url URL of the exchange that issued @a coin_pub - * @param amount_with_fee amount the exchange will deposit for this coin - * @param deposit_fee fee the exchange will charge for this coin - * @param wire_fee wire fee the exchange charges - * @param refund_fee fee the exchange charges to refund this coin - * @param h_wire hash of the wire details of the target account of the merchant - * @param exchange_sig signature from exchange that coin was accepted - * @param exchange_pub signgin key that was used for @a exchange_sig - * @return transaction status + * Closure for lookup_deposits_by_order_cb(). */ -static enum GNUNET_DB_QueryStatus -postgres_insert_deposit ( - void *cls, - const char *instance_id, - struct GNUNET_TIME_Timestamp deposit_timestamp, - const struct TALER_PrivateContractHashP *h_contract_terms, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const char *exchange_url, - const struct TALER_Amount *amount_with_fee, - const struct TALER_Amount *deposit_fee, - const struct TALER_Amount *refund_fee, - const struct TALER_Amount *wire_fee, - const struct TALER_MerchantWireHashP *h_wire, - const struct TALER_ExchangeSignatureP *exchange_sig, - const struct TALER_ExchangePublicKeyP *exchange_pub) +struct LookupTransferDetailsByOrderContext { - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (h_contract_terms), - GNUNET_PQ_query_param_timestamp (&deposit_timestamp), /* $3 */ - GNUNET_PQ_query_param_auto_from_type (coin_pub), - GNUNET_PQ_query_param_string (exchange_url), - TALER_PQ_query_param_amount (amount_with_fee), /* $6/$7 */ - TALER_PQ_query_param_amount (deposit_fee), /* $8, $9 */ - TALER_PQ_query_param_amount (refund_fee), /* $10, $11 */ - TALER_PQ_query_param_amount (wire_fee), /* $12, $13 */ - GNUNET_PQ_query_param_auto_from_type (h_wire), /* $14 */ - GNUNET_PQ_query_param_auto_from_type (exchange_sig), /* $15 */ - GNUNET_PQ_query_param_auto_from_type (exchange_pub), /* $16 */ - GNUNET_PQ_query_param_end - }; - - /* no preflight check here, run in transaction by caller! */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Storing deposit for instance `%s' h_contract_terms `%s', coin_pub: `%s', amount_with_fee: %s\n", - instance_id, - GNUNET_h2s (&h_contract_terms->hash), - TALER_B2S (coin_pub), - TALER_amount2s (amount_with_fee)); - check_connection (pg); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_deposit", - params); -} - - -/** - * Closure for #lookup_refunds_cb(). - */ -struct LookupRefundsContext -{ /** - * Function to call for each refund. + * Plugin context. */ - TALER_MERCHANTDB_RefundCallback rc; + struct PostgresClosure *pg; /** - * Closure for @e rc. + * Function to call with all results. */ - void *rc_cls; + TALER_MERCHANTDB_OrderTransferDetailsCallback cb; /** - * Plugin context. + * Closure for @e cb. */ - struct PostgresClosure *pg; + void *cb_cls; /** - * Transaction result. + * Set to the query result. */ enum GNUNET_DB_QueryStatus qs; }; @@ -1402,27 +1136,42 @@ struct LookupRefundsContext * Function to be called with the results of a SELECT statement * that has returned @a num_results results. * - * @param cls of type `struct LookupRefundsContext *` + * @param cls of type `struct LookupTransferDetailsByOrderContext *` * @param result the postgres result * @param num_results the number of results in @a result */ static void -lookup_refunds_cb (void *cls, - PGresult *result, - unsigned int num_results) +lookup_transfer_details_by_order_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct LookupRefundsContext *lrc = cls; - struct PostgresClosure *pg = lrc->pg; + struct LookupTransferDetailsByOrderContext *ltdo = cls; + struct PostgresClosure *pg = ltdo->pg; for (unsigned int i = 0; i<num_results; i++) { - struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_Amount refund_amount; + struct TALER_WireTransferIdentifierRawP wtid; + char *exchange_url; + uint64_t deposit_serial; + struct GNUNET_TIME_Timestamp execution_time; + struct TALER_Amount deposit_value; + struct TALER_Amount deposit_fee; + uint8_t transfer_confirmed; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("coin_pub", - &coin_pub), - TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", - &refund_amount), + GNUNET_PQ_result_spec_uint64 ("deposit_serial", + &deposit_serial), + GNUNET_PQ_result_spec_timestamp ("deposit_timestamp", + &execution_time), + GNUNET_PQ_result_spec_string ("exchange_url", + &exchange_url), + GNUNET_PQ_result_spec_auto_from_type ("wtid", + &wtid), + TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_value", + &deposit_value), + TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_fee", + &deposit_fee), + GNUNET_PQ_result_spec_auto_from_type ("transfer_confirmed", + &transfer_confirmed), GNUNET_PQ_result_spec_end }; @@ -1432,481 +1181,259 @@ lookup_refunds_cb (void *cls, i)) { GNUNET_break (0); - lrc->qs = GNUNET_DB_STATUS_HARD_ERROR; + ltdo->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } - lrc->rc (lrc->rc_cls, - &coin_pub, - &refund_amount); + ltdo->cb (ltdo->cb_cls, + &wtid, + exchange_url, + execution_time, + &deposit_value, + &deposit_fee, + (0 != transfer_confirmed)); GNUNET_PQ_cleanup_result (rs); /* technically useless here */ } - lrc->qs = num_results; + ltdo->qs = num_results; } /** - * Obtain refunds associated with a contract. + * Retrieve wire transfer details for all deposits associated with + * a given @a order_serial. * - * @param cls closure, typically a connection to the db - * @param instance_id instance to lookup refunds for - * @param h_contract_terms hash code of the contract - * @param rc function to call for each coin on which there is a refund - * @param rc_cls closure for @a rc + * @param cls closure + * @param order_serial identifies the order + * @param cb function called with the wire transfer details + * @param cb_cls closure for @a cb * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_lookup_refunds ( +postgres_lookup_transfer_details_by_order ( void *cls, - const char *instance_id, - const struct TALER_PrivateContractHashP *h_contract_terms, - TALER_MERCHANTDB_RefundCallback rc, - void *rc_cls) + uint64_t order_serial, + TALER_MERCHANTDB_OrderTransferDetailsCallback cb, + void *cb_cls) { struct PostgresClosure *pg = cls; + struct LookupTransferDetailsByOrderContext ltdo = { + .pg = pg, + .cb = cb, + .cb_cls = cb_cls + }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_uint64 (&order_serial), GNUNET_PQ_query_param_end }; - struct LookupRefundsContext lrc = { - .rc = rc, - .rc_cls = rc_cls, - .pg = pg - }; enum GNUNET_DB_QueryStatus qs; - /* no preflight check here, run in transaction by caller! */ - TALER_LOG_DEBUG ("Looking for refund of h_contract_terms %s at `%s'\n", - GNUNET_h2s (&h_contract_terms->hash), - instance_id); - check_connection (pg); - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_refunds", - params, - &lookup_refunds_cb, - &lrc); - if (0 >= qs) + qs = GNUNET_PQ_eval_prepared_multi_select ( + pg->conn, + "lookup_transfer_details_by_order", + params, + &lookup_transfer_details_by_order_cb, + &ltdo); + if (qs < 0) return qs; - return lrc.qs; + return ltdo.qs; } /** - * Mark contract as paid and store the current @a session_id - * for which the contract was paid. Deletes the underlying order - * and marks the locked stocks of the order as sold. + * Insert wire transfer details for a deposit. * * @param cls closure - * @param instance_id instance to mark contract as paid for - * @param h_contract_terms hash of the contract that is now paid - * @param session_id the session that paid the contract + * @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_mark_contract_paid ( +postgres_insert_deposit_to_transfer ( void *cls, - const char *instance_id, - const struct TALER_PrivateContractHashP *h_contract_terms, - const char *session_id) + uint64_t deposit_serial, + const struct TALER_EXCHANGE_DepositData *dd) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (h_contract_terms), - GNUNET_PQ_query_param_string (session_id), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_QueryParam uparams[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_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 }; - enum GNUNET_DB_QueryStatus qs; - /* Session ID must always be given by the caller. */ - GNUNET_assert (NULL != session_id); - - /* no preflight check here, run in transaction by caller! */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Marking h_contract_terms '%s' of %s as paid for session `%s'\n", - GNUNET_h2s (&h_contract_terms->hash), - instance_id, - session_id); - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "mark_contract_paid", - params); - if (qs <= 0) - return qs; - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "mark_inventory_sold", - uparams); - if (qs < 0) - return qs; /* 0: no inventory management, that's OK! */ - /* ON DELETE CASCADE deletes from merchant_order_locks */ return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_completed_order", - uparams); + "insert_deposit_to_transfer", + params); } /** - * Function called during aborts to refund a coin. Marks the - * respective coin as refunded. + * Set 'wired' status for an order to 'true'. * * @param cls closure - * @param instance_id instance to refund payment for - * @param h_contract_terms hash of the contract to refund coin for - * @param refund_timestamp timestamp of the refund - * @param coin_pub public key of the coin to refund (fully) - * @param reason text justifying the refund + * @param order_serial serial number of the order * @return transaction status - * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a coin_pub is unknown to us; - * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid, - * regardless of whether it actually increased the refund */ static enum GNUNET_DB_QueryStatus -postgres_refund_coin (void *cls, - const char *instance_id, - const struct TALER_PrivateContractHashP *h_contract_terms, - struct GNUNET_TIME_Timestamp refund_timestamp, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const char *reason) +postgres_mark_order_wired (void *cls, + uint64_t order_serial) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (h_contract_terms), - GNUNET_PQ_query_param_timestamp (&refund_timestamp), - GNUNET_PQ_query_param_auto_from_type (coin_pub), - GNUNET_PQ_query_param_string (reason), + GNUNET_PQ_query_param_uint64 (&order_serial), GNUNET_PQ_query_param_end }; return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "refund_coin", + "mark_order_wired", params); } /** - * Retrieve contract terms given its @a order_id - * - * @param cls closure - * @param instance_id instance's identifier - * @param order_id order to lookup contract for - * @param[out] h_contract_terms set to the hash of the contract. - * @param[out] paid set to the payment status of the contract - * @return transaction status + * Closure for #process_refund_cb(). */ -static enum GNUNET_DB_QueryStatus -postgres_lookup_order_status ( - void *cls, - const char *instance_id, - const char *order_id, - struct TALER_PrivateContractHashP *h_contract_terms, - bool *paid) +struct FindRefundContext { - struct PostgresClosure *pg = cls; - uint8_t paid8; - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (order_id), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", - h_contract_terms), - GNUNET_PQ_result_spec_auto_from_type ("paid", - &paid8), - GNUNET_PQ_result_spec_end - }; - check_connection (pg); - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_order_status", - params, - rs); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - *paid = (0 != paid8); - else - *paid = false; /* just to be safe(r) */ - return qs; -} + /** + * Plugin context. + */ + struct PostgresClosure *pg; + /** + * Updated to reflect total amount refunded so far. + */ + struct TALER_Amount refunded_amount; -/** - * Retrieve contract terms given its @a order_serial - * - * @param cls closure - * @param instance_id instance's identifier - * @param order_serial serial ID of the order to look up - * @param[out] order_id set to ID of the order - * @param[out] h_contract_terms set to the hash of the contract. - * @param[out] paid set to the payment status of the contract - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_lookup_order_status_by_serial (void *cls, - const char *instance_id, - uint64_t order_serial, - char **order_id, - struct TALER_PrivateContractHashP * - h_contract_terms, - bool *paid) -{ - struct PostgresClosure *pg = cls; - uint8_t paid8; - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_uint64 (&order_serial), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", - h_contract_terms), - GNUNET_PQ_result_spec_auto_from_type ("paid", - &paid8), - GNUNET_PQ_result_spec_string ("order_id", - order_id), - GNUNET_PQ_result_spec_end - }; + /** + * Set to the largest refund transaction ID encountered. + */ + uint64_t max_rtransaction_id; - check_connection (pg); - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_order_status_by_serial", - params, - rs); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - *paid = (0 != paid8); - else - *paid = false; /* just to be safe(r) */ - return qs; -} + /** + * Set to true on hard errors. + */ + bool err; +}; /** - * Retrieve payment and wire status for a given @a order_serial and session ID. + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. * - * @param cls closure - * @param order_serial identifies the order - * @param session_id session for which to check the payment status, NULL for any - * @param[out] paid set to the payment status of the contract - * @param[out] wired set to the wire transfer status of the exchange payment - * @return transaction status + * @param cls closure, our `struct FindRefundContext` + * @param result the postgres result + * @param num_results the number of results in @a result */ -static enum GNUNET_DB_QueryStatus -postgres_lookup_payment_status (void *cls, - uint64_t order_serial, - const char *session_id, - bool *paid, - bool *wired) +static void +process_refund_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct PostgresClosure *pg = cls; - uint8_t paid8; - uint8_t wired8; - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("paid", - &paid8), - GNUNET_PQ_result_spec_auto_from_type ("wired", - &wired8), - GNUNET_PQ_result_spec_end - }; - check_connection (pg); - if (NULL == session_id) - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&order_serial), - GNUNET_PQ_query_param_end - }; + struct FindRefundContext *ictx = cls; + struct PostgresClosure *pg = ictx->pg; - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_payment_status", - params, - rs); - } - else + for (unsigned int i = 0; i<num_results; i++) { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&order_serial), - GNUNET_PQ_query_param_string (session_id), - GNUNET_PQ_query_param_end + /* 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 }; - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_payment_status_session_id", - params, - rs); - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - *paid = (0 != paid8); - *wired = (0 != wired8); - } - else - { - *paid = false; /* just to be safe(r) */ - *wired = false; /* just to be safe(r) */ + 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)); } - return qs; } /** - * Closure for lookup_deposits_by_order_cb(). + * Closure for #process_deposits_for_refund_cb(). */ -struct LookupDepositsByOrderContext +struct InsertRefundContext { - /** - * Plugin context. + * Used to provide a connection to the db */ struct PostgresClosure *pg; /** - * Function to call with all results. + * Amount to which increase the refund for this contract */ - TALER_MERCHANTDB_DepositedCoinsCallback cb; + const struct TALER_Amount *refund; /** - * Closure for @e cb. + * Human-readable reason behind this refund */ - void *cb_cls; + const char *reason; /** - * Set to the query result. + * Transaction status code. */ - enum GNUNET_DB_QueryStatus qs; + enum TALER_MERCHANTDB_RefundStatus rs; }; /** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. - * - * @param cls of type `struct LookupDepositsByOrderContext *` - * @param result the postgres result - * @param num_results the number of results in @a result - */ -static void -lookup_deposits_by_order_cb (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct LookupDepositsByOrderContext *ldoc = cls; - struct PostgresClosure *pg = ldoc->pg; - - for (unsigned int i = 0; i<num_results; i++) - { - uint64_t deposit_serial; - char *exchange_url; - struct TALER_MerchantWireHashP h_wire; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_Amount amount_with_fee; - struct TALER_Amount deposit_fee; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("deposit_serial", - &deposit_serial), - GNUNET_PQ_result_spec_string ("exchange_url", - &exchange_url), - GNUNET_PQ_result_spec_auto_from_type ("h_wire", - &h_wire), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", - &amount_with_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee", - &deposit_fee), - GNUNET_PQ_result_spec_auto_from_type ("coin_pub", - &coin_pub), - GNUNET_PQ_result_spec_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - ldoc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } - ldoc->cb (ldoc->cb_cls, - deposit_serial, - exchange_url, - &h_wire, - &amount_with_fee, - &deposit_fee, - &coin_pub); - GNUNET_PQ_cleanup_result (rs); /* technically useless here */ - } - ldoc->qs = num_results; -} - - -/** - * Retrieve details about coins that were deposited for an order. - * - * @param cls closure - * @param order_serial identifies the order - * @param cb function to call for each deposited coin - * @param cb_cls closure for @a cb - * @return transaction status + * Data extracted per coin. */ -static enum GNUNET_DB_QueryStatus -postgres_lookup_deposits_by_order (void *cls, - uint64_t order_serial, - TALER_MERCHANTDB_DepositedCoinsCallback cb, - void *cb_cls) +struct RefundCoinData { - struct PostgresClosure *pg = cls; - struct LookupDepositsByOrderContext ldoc = { - .pg = pg, - .cb = cb, - .cb_cls = cb_cls - }; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&order_serial), - GNUNET_PQ_query_param_end - }; - enum GNUNET_DB_QueryStatus qs; - - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_deposits_by_order", - params, - &lookup_deposits_by_order_cb, - &ldoc); - if (qs < 0) - return qs; - return ldoc.qs; -} - -/** - * Closure for lookup_deposits_by_order_cb(). - */ -struct LookupTransferDetailsByOrderContext -{ + /** + * Public key of a coin. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; /** - * Plugin context. + * Amount deposited for this coin. */ - struct PostgresClosure *pg; + struct TALER_Amount deposited_with_fee; /** - * Function to call with all results. + * Amount refunded already for this coin. */ - TALER_MERCHANTDB_OrderTransferDetailsCallback cb; + struct TALER_Amount refund_amount; /** - * Closure for @e cb. + * Order serial (actually not really per-coin). */ - void *cb_cls; + uint64_t order_serial; /** - * Set to the query result. + * Maximum rtransaction_id for this coin so far. */ - enum GNUNET_DB_QueryStatus qs; + uint64_t max_rtransaction_id; + }; @@ -1914,44 +1441,42 @@ struct LookupTransferDetailsByOrderContext * Function to be called with the results of a SELECT statement * that has returned @a num_results results. * - * @param cls of type `struct LookupTransferDetailsByOrderContext *` + * @param cls closure, our `struct InsertRefundContext` * @param result the postgres result * @param num_results the number of results in @a result */ static void -lookup_transfer_details_by_order_cb (void *cls, - PGresult *result, - unsigned int num_results) +process_deposits_for_refund_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct LookupTransferDetailsByOrderContext *ltdo = cls; - struct PostgresClosure *pg = ltdo->pg; + 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, + &current_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 TALER_WireTransferIdentifierRawP wtid; - char *exchange_url; - uint64_t deposit_serial; - struct GNUNET_TIME_Timestamp execution_time; - struct TALER_Amount deposit_value; - struct TALER_Amount deposit_fee; - uint8_t transfer_confirmed; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("deposit_serial", - &deposit_serial), - GNUNET_PQ_result_spec_timestamp ("deposit_timestamp", - &execution_time), - GNUNET_PQ_result_spec_string ("exchange_url", - &exchange_url), - GNUNET_PQ_result_spec_auto_from_type ("wtid", - &wtid), - TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_value", - &deposit_value), - TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_fee", - &deposit_fee), - GNUNET_PQ_result_spec_auto_from_type ("transfer_confirmed", - &transfer_confirmed), - GNUNET_PQ_result_spec_end - }; + 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, @@ -1959,142 +1484,284 @@ lookup_transfer_details_by_order_cb (void *cls, i)) { GNUNET_break (0); - ltdo->qs = GNUNET_DB_STATUS_HARD_ERROR; + ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR; return; } - ltdo->cb (ltdo->cb_cls, - &wtid, - exchange_url, - execution_time, - &deposit_value, - &deposit_fee, - (0 != transfer_confirmed)); - GNUNET_PQ_cleanup_result (rs); /* technically useless here */ + + { + 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 (&current_refund, + &current_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)); } - ltdo->qs = num_results; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Total existing refund is %s\n", + TALER_amount2s (&current_refund)); + + /* stop immediately if we are 'done' === amount already + * refunded. */ + if (0 >= TALER_amount_cmp (ctx->refund, + &current_refund)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Existing refund of %s at or above requested refund. Finished early.\n", + TALER_amount2s (&current_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, + &current_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 (&current_refund, + &current_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, + &current_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; } /** - * Retrieve wire transfer details for all deposits associated with - * a given @a order_serial. + * Function called when some backoffice staff decides to award or + * increase the refund on an existing contract. This function + * MUST be called from within a transaction scope setup by the + * caller as it executes multiple SQL statements. * * @param cls closure - * @param order_serial identifies the order - * @param cb function called with the wire transfer details - * @param cb_cls closure for @a cb + * @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 GNUNET_DB_QueryStatus -postgres_lookup_transfer_details_by_order ( - void *cls, - uint64_t order_serial, - TALER_MERCHANTDB_OrderTransferDetailsCallback cb, - void *cb_cls) +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; - struct LookupTransferDetailsByOrderContext ltdo = { - .pg = pg, - .cb = cb, - .cb_cls = cb_cls - }; + enum GNUNET_DB_QueryStatus qs; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&order_serial), + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_end }; - enum GNUNET_DB_QueryStatus qs; + struct InsertRefundContext ctx = { + .pg = pg, + .refund = refund, + .reason = reason + }; - qs = GNUNET_PQ_eval_prepared_multi_select ( - pg->conn, - "lookup_transfer_details_by_order", - params, - &lookup_transfer_details_by_order_cb, - &ltdo); - if (qs < 0) - return qs; - return ltdo.qs; -} - - -/** - * Insert wire transfer details for a deposit. - * - * @param cls closure - * @param deposit_serial serial number of the deposit - * @param dd deposit transfer data from the exchange to store - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_insert_deposit_to_transfer ( - void *cls, - uint64_t deposit_serial, - const struct TALER_EXCHANGE_DepositData *dd) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&deposit_serial), - TALER_PQ_query_param_amount (&dd->coin_contribution), - GNUNET_PQ_query_param_timestamp (&dd->execution_time), - GNUNET_PQ_query_param_auto_from_type (&dd->exchange_sig), - GNUNET_PQ_query_param_auto_from_type (&dd->exchange_pub), - GNUNET_PQ_query_param_auto_from_type (&dd->wtid), - GNUNET_PQ_query_param_end - }; - - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_deposit_to_transfer", - params); -} - - -/** - * Set 'wired' status for an order to 'true'. - * - * @param cls closure - * @param order_serial serial number of the order - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_mark_order_wired (void *cls, - uint64_t order_serial) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&order_serial), - GNUNET_PQ_query_param_end - }; - - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "mark_order_wired", - params); + 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 #process_refund_cb(). + * Closure for #lookup_refunds_detailed_cb(). */ -struct FindRefundContext +struct LookupRefundsDetailedContext { - /** - * Plugin context. + * Function to call for each refund. */ - struct PostgresClosure *pg; + TALER_MERCHANTDB_RefundDetailCallback rc; /** - * Updated to reflect total amount refunded so far. + * Closure for @e rc. */ - struct TALER_Amount refunded_amount; + void *rc_cls; /** - * Set to the largest refund transaction ID encountered. + * Plugin context. */ - uint64_t max_rtransaction_id; + struct PostgresClosure *pg; /** - * Set to true on hard errors. + * Transaction result. */ - bool err; + enum GNUNET_DB_QueryStatus qs; }; @@ -2102,28 +1769,45 @@ struct FindRefundContext * 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 cls of type `struct GetRefundsContext *` * @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) +lookup_refunds_detailed_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct FindRefundContext *ictx = cls; - struct PostgresClosure *pg = ictx->pg; + struct LookupRefundsDetailedContext *lrdc = cls; + struct PostgresClosure *pg = lrdc->pg; for (unsigned int i = 0; i<num_results; i++) { - /* Sum up existing refunds */ - struct TALER_Amount acc; + uint64_t refund_serial; + struct GNUNET_TIME_Timestamp timestamp; + struct TALER_CoinSpendPublicKeyP coin_pub; uint64_t rtransaction_id; + struct TALER_Amount refund_amount; + char *reason; + char *exchange_url; + uint8_t pending8; struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", - &acc), + GNUNET_PQ_result_spec_uint64 ("refund_serial", + &refund_serial), + GNUNET_PQ_result_spec_timestamp ("refund_timestamp", + &timestamp), + GNUNET_PQ_result_spec_auto_from_type ("coin_pub", + &coin_pub), + GNUNET_PQ_result_spec_string ("exchange_url", + &exchange_url), GNUNET_PQ_result_spec_uint64 ("rtransaction_id", &rtransaction_id), + GNUNET_PQ_result_spec_string ("reason", + &reason), + TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", + &refund_amount), + GNUNET_PQ_result_spec_auto_from_type ("pending", + &pending8), GNUNET_PQ_result_spec_end }; @@ -2133,1040 +1817,946 @@ process_refund_cb (void *cls, 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; + lrdc->qs = GNUNET_DB_STATUS_HARD_ERROR; 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)); + lrdc->rc (lrdc->rc_cls, + refund_serial, + timestamp, + &coin_pub, + exchange_url, + rtransaction_id, + reason, + &refund_amount, + 0 != pending8); + GNUNET_PQ_cleanup_result (rs); } + lrdc->qs = num_results; } /** - * Closure for #process_deposits_for_refund_cb(). + * Obtain detailed refund data associated with a contract. + * + * @param cls closure, typically a connection to the db + * @param instance_id instance to lookup refunds for + * @param h_contract_terms hash code of the contract + * @param rc function to call for each coin on which there is a refund + * @param rc_cls closure for @a rc + * @return transaction status */ -struct InsertRefundContext +static enum GNUNET_DB_QueryStatus +postgres_lookup_refunds_detailed ( + void *cls, + const char *instance_id, + const struct TALER_PrivateContractHashP *h_contract_terms, + TALER_MERCHANTDB_RefundDetailCallback rc, + void *rc_cls) { - /** - * 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; + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_end + }; + struct LookupRefundsDetailedContext lrdc = { + .rc = rc, + .rc_cls = rc_cls, + .pg = pg + }; + enum GNUNET_DB_QueryStatus qs; - /** - * Transaction status code. - */ - enum TALER_MERCHANTDB_RefundStatus rs; -}; + /* no preflight check here, run in transaction by caller! */ + TALER_LOG_DEBUG ("Looking for refund %s + %s\n", + GNUNET_h2s (&h_contract_terms->hash), + instance_id); + check_connection (pg); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "lookup_refunds_detailed", + params, + &lookup_refunds_detailed_cb, + &lrdc); + if (0 >= qs) + return qs; + return lrdc.qs; +} /** - * Data extracted per coin. + * Insert refund proof data from the exchange into the database. + * + * @param cls closure + * @param refund_serial serial number of the refund + * @param exchange_sig signature from exchange that coin was refunded + * @param exchange_pub signing key that was used for @a exchange_sig + * @return transaction status */ -struct RefundCoinData +static enum GNUNET_DB_QueryStatus +postgres_insert_refund_proof ( + void *cls, + uint64_t refund_serial, + const struct TALER_ExchangeSignatureP *exchange_sig, + const struct TALER_ExchangePublicKeyP *exchange_pub) { + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&refund_serial), + GNUNET_PQ_query_param_auto_from_type (exchange_sig), + GNUNET_PQ_query_param_auto_from_type (exchange_pub), + GNUNET_PQ_query_param_end + }; - /** - * 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; - -}; + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_refund_proof", + params); +} /** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. + * Lookup refund proof data. * - * @param cls closure, our `struct InsertRefundContext` - * @param result the postgres result - * @param num_results the number of results in @a result + * @param cls closure + * @param refund_serial serial number of the refund + * @param[out] exchange_sig set to signature from exchange + * @param[out] exchange_pub signing key that was used for @a exchange_sig + * @return transaction status */ -static void -process_deposits_for_refund_cb (void *cls, - PGresult *result, - unsigned int num_results) +static enum GNUNET_DB_QueryStatus +postgres_lookup_refund_proof (void *cls, + uint64_t refund_serial, + struct TALER_ExchangeSignatureP *exchange_sig, + struct TALER_ExchangePublicKeyP *exchange_pub) { - struct 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; + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&refund_serial), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("exchange_sig", + exchange_sig), + GNUNET_PQ_result_spec_auto_from_type ("exchange_pub", + exchange_pub), + GNUNET_PQ_result_spec_end + }; - now = GNUNET_TIME_timestamp_get (); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (ctx->refund->currency, - &current_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 - }; + check_connection (pg); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_refund_proof", + params, + rs); +} - 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 - }; +/** + * Retrieve the order ID that was used to pay for a resource within a session. + * + * @param cls closure + * @param instance_id identifying the instance + * @param fulfillment_url URL that canonically identifies the resource + * being paid for + * @param session_id session id + * @param[out] order_id where to store the order ID that was used when + * paying for the resource URL + * @return transaction status + */ +enum GNUNET_DB_QueryStatus +postgres_lookup_order_by_fulfillment (void *cls, + const char *instance_id, + const char *fulfillment_url, + const char *session_id, + char **order_id) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (fulfillment_url), + GNUNET_PQ_query_param_string (session_id), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_string ("order_id", + order_id), + GNUNET_PQ_result_spec_end + }; - 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", + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_order_by_fulfillment", 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 (&current_refund, - &current_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 (&current_refund)); + rs); +} - /* stop immediately if we are 'done' === amount already - * refunded. */ - if (0 >= TALER_amount_cmp (ctx->refund, - &current_refund)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Existing refund of %s at or above requested refund. Finished early.\n", - TALER_amount2s (&current_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; +/** + * 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 + }; - /* 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; - } + check_connection (pg); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_transfer", + params); +} - 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, - &current_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 (&current_refund, - &current_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, - &current_refund)) - { - ctx->rs = TALER_MERCHANTDB_RS_SUCCESS; - return; - } - } +/** + * Delete information about a transfer. Note that transfers + * confirmed by the exchange cannot be deleted anymore. + * + * @param cls closure + * @param instance_id instance to delete transfer of + * @param transfer_serial_id transfer to delete + * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS + * if deletion is prohibited OR transfer is unknown + */ +static enum GNUNET_DB_QueryStatus +postgres_delete_transfer (void *cls, + const char *instance_id, + uint64_t transfer_serial_id) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_uint64 (&transfer_serial_id), + GNUNET_PQ_query_param_end + }; - /** - * 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; + check_connection (pg); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "delete_transfer", + params); } /** - * Function called when some backoffice staff decides to award or - * increase the refund on an existing contract. This function - * MUST be called from within a transaction scope setup by the - * caller as it executes multiple SQL statements. + * Check if information about a transfer exists with the + * backend. Returns no data, only the query status. * * @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!) + * @param instance_id instance to delete transfer of + * @param transfer_serial_id transfer to delete + * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT + * if the transfer record exists */ -static enum TALER_MERCHANTDB_RefundStatus -postgres_increase_refund (void *cls, - const char *instance_id, - const char *order_id, - const struct TALER_Amount *refund, - const char *reason) +static enum GNUNET_DB_QueryStatus +postgres_check_transfer_exists (void *cls, + const char *instance_id, + uint64_t transfer_serial_id) { 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_uint64 (&transfer_serial_id), GNUNET_PQ_query_param_end }; - struct InsertRefundContext ctx = { - .pg = pg, - .refund = refund, - .reason = reason + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_end }; - 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; - } + check_connection (pg); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "check_transfer_exists", + params, + rs); } /** - * Closure for #lookup_refunds_detailed_cb(). + * Lookup account serial by payto URI. + * + * @param cls closure + * @param instance_id instance to lookup the account from + * @param payto_uri what is the merchant's bank account to lookup + * @param[out] account_serial serial number of the account + * @return transaction status */ -struct LookupRefundsDetailedContext +static enum GNUNET_DB_QueryStatus +postgres_lookup_account (void *cls, + const char *instance_id, + const char *payto_uri, + uint64_t *account_serial) { - /** - * Function to call for each refund. - */ - TALER_MERCHANTDB_RefundDetailCallback rc; - - /** - * Closure for @e rc. - */ - void *rc_cls; - - /** - * Plugin context. - */ - struct PostgresClosure *pg; + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (payto_uri), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("account_serial", + account_serial), + GNUNET_PQ_result_spec_end + }; - /** - * Transaction result. - */ - enum GNUNET_DB_QueryStatus qs; -}; + check_connection (pg); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_account", + params, + rs); +} /** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. + * Insert information about a wire transfer the merchant has received. * - * @param cls of type `struct GetRefundsContext *` - * @param result the postgres result - * @param num_results the number of results in @a result + * @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 void -lookup_refunds_detailed_cb (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct LookupRefundsDetailedContext *lrdc = cls; - struct PostgresClosure *pg = lrdc->pg; - - for (unsigned int i = 0; i<num_results; i++) - { - uint64_t refund_serial; - struct GNUNET_TIME_Timestamp timestamp; - struct TALER_CoinSpendPublicKeyP coin_pub; - uint64_t rtransaction_id; - struct TALER_Amount refund_amount; - char *reason; - char *exchange_url; - uint8_t pending8; +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 ("refund_serial", - &refund_serial), - GNUNET_PQ_result_spec_timestamp ("refund_timestamp", - &timestamp), - GNUNET_PQ_result_spec_auto_from_type ("coin_pub", - &coin_pub), - GNUNET_PQ_result_spec_string ("exchange_url", - &exchange_url), - GNUNET_PQ_result_spec_uint64 ("rtransaction_id", - &rtransaction_id), - GNUNET_PQ_result_spec_string ("reason", - &reason), - TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", - &refund_amount), - GNUNET_PQ_result_spec_auto_from_type ("pending", - &pending8), + GNUNET_PQ_result_spec_uint64 ("credit_serial", + &credit_serial), GNUNET_PQ_result_spec_end }; - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_credit_serial", + params, + rs); + if (0 > qs) { - GNUNET_break (0); - lrdc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; + 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; } - lrdc->rc (lrdc->rc_cls, - refund_serial, - timestamp, - &coin_pub, - exchange_url, - rtransaction_id, - reason, - &refund_amount, - 0 != pending8); - GNUNET_PQ_cleanup_result (rs); } - lrdc->qs = num_results; + + /* 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 detailed refund data associated with a contract. + * Obtain information about wire fees charged by an exchange, + * including signature (so we have proof). * - * @param cls closure, typically a connection to the db - * @param instance_id instance to lookup refunds for - * @param h_contract_terms hash code of the contract - * @param rc function to call for each coin on which there is a refund - * @param rc_cls closure for @a rc - * @return transaction status + * @param cls closure + * @param master_pub public key of the exchange + * @param wire_method the wire method + * @param contract_date date of the contract to use for the lookup + * @param[out] fees wire fees charged + * @param[out] start_date start of fee being used + * @param[out] end_date end of fee being used + * @param[out] master_sig signature of exchange over fee structure + * @return transaction status code */ static enum GNUNET_DB_QueryStatus -postgres_lookup_refunds_detailed ( - void *cls, - const char *instance_id, - const struct TALER_PrivateContractHashP *h_contract_terms, - TALER_MERCHANTDB_RefundDetailCallback rc, - void *rc_cls) +postgres_lookup_wire_fee (void *cls, + const struct TALER_MasterPublicKeyP *master_pub, + const char *wire_method, + struct GNUNET_TIME_Timestamp contract_date, + struct TALER_WireFeeSet *fees, + struct GNUNET_TIME_Timestamp *start_date, + struct GNUNET_TIME_Timestamp *end_date, + struct TALER_MasterSignatureP *master_sig) { struct PostgresClosure *pg = cls; + struct GNUNET_HashCode h_wire_method; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_auto_from_type (master_pub), + GNUNET_PQ_query_param_auto_from_type (&h_wire_method), + GNUNET_PQ_query_param_timestamp (&contract_date), GNUNET_PQ_query_param_end }; - struct LookupRefundsDetailedContext lrdc = { - .rc = rc, - .rc_cls = rc_cls, - .pg = pg + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", + &fees->wire), + TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee", + &fees->closing), + GNUNET_PQ_result_spec_timestamp ("start_date", + start_date), + GNUNET_PQ_result_spec_timestamp ("end_date", + end_date), + GNUNET_PQ_result_spec_auto_from_type ("master_sig", + master_sig), + GNUNET_PQ_result_spec_end }; - enum GNUNET_DB_QueryStatus qs; - /* no preflight check here, run in transaction by caller! */ - TALER_LOG_DEBUG ("Looking for refund %s + %s\n", - GNUNET_h2s (&h_contract_terms->hash), - instance_id); check_connection (pg); - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_refunds_detailed", - params, - &lookup_refunds_detailed_cb, - &lrdc); - if (0 >= qs) - return qs; - return lrdc.qs; + GNUNET_CRYPTO_hash (wire_method, + strlen (wire_method) + 1, + &h_wire_method); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_wire_fee", + params, + rs); } /** - * Insert refund proof data from the exchange into the database. - * - * @param cls closure - * @param refund_serial serial number of the refund - * @param exchange_sig signature from exchange that coin was refunded - * @param exchange_pub signing key that was used for @a exchange_sig - * @return transaction status + * Closure for #lookup_deposits_by_contract_and_coin_cb(). */ -static enum GNUNET_DB_QueryStatus -postgres_insert_refund_proof ( - void *cls, - uint64_t refund_serial, - const struct TALER_ExchangeSignatureP *exchange_sig, - const struct TALER_ExchangePublicKeyP *exchange_pub) +struct LookupDepositsByCnCContext { - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&refund_serial), - GNUNET_PQ_query_param_auto_from_type (exchange_sig), - GNUNET_PQ_query_param_auto_from_type (exchange_pub), - GNUNET_PQ_query_param_end - }; + /** + * Function to call for each deposit. + */ + TALER_MERCHANTDB_CoinDepositCallback cb; - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_refund_proof", - params); -} + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Plugin context. + */ + struct PostgresClosure *pg; + + /** + * Transaction result. + */ + enum GNUNET_DB_QueryStatus qs; +}; /** - * Lookup refund proof data. + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. * - * @param cls closure - * @param refund_serial serial number of the refund - * @param[out] exchange_sig set to signature from exchange - * @param[out] exchange_pub signing key that was used for @a exchange_sig - * @return transaction status + * @param cls of type `struct LookupDepositsByCnCContext *` + * @param result the postgres result + * @param num_results the number of results in @a result */ -static enum GNUNET_DB_QueryStatus -postgres_lookup_refund_proof (void *cls, - uint64_t refund_serial, - struct TALER_ExchangeSignatureP *exchange_sig, - struct TALER_ExchangePublicKeyP *exchange_pub) +static void +lookup_deposits_by_contract_and_coin_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&refund_serial), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("exchange_sig", - exchange_sig), - GNUNET_PQ_result_spec_auto_from_type ("exchange_pub", - exchange_pub), - GNUNET_PQ_result_spec_end - }; + struct LookupDepositsByCnCContext *ldcc = cls; + struct PostgresClosure *pg = ldcc->pg; - check_connection (pg); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_refund_proof", - params, - rs); + for (unsigned int i = 0; i<num_results; i++) + { + char *exchange_url; + struct TALER_Amount amount_with_fee; + struct TALER_Amount deposit_fee; + struct TALER_Amount refund_fee; + struct TALER_Amount wire_fee; + struct TALER_MerchantWireHashP h_wire; + struct GNUNET_TIME_Timestamp deposit_timestamp; + struct GNUNET_TIME_Timestamp refund_deadline; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_ExchangePublicKeyP exchange_pub; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_string ("exchange_url", + &exchange_url), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", + &amount_with_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee", + &deposit_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee", + &refund_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", + &wire_fee), + GNUNET_PQ_result_spec_auto_from_type ("h_wire", + &h_wire), + GNUNET_PQ_result_spec_timestamp ("deposit_timestamp", + &deposit_timestamp), + GNUNET_PQ_result_spec_timestamp ("refund_deadline", + &refund_deadline), + GNUNET_PQ_result_spec_auto_from_type ("exchange_sig", + &exchange_sig), + GNUNET_PQ_result_spec_auto_from_type ("exchange_pub", + &exchange_pub), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return; + } + ldcc->cb (ldcc->cb_cls, + exchange_url, + &amount_with_fee, + &deposit_fee, + &refund_fee, + &wire_fee, + &h_wire, + deposit_timestamp, + refund_deadline, + &exchange_sig, + &exchange_pub); + GNUNET_PQ_cleanup_result (rs); + } + ldcc->qs = num_results; } /** - * Retrieve the order ID that was used to pay for a resource within a session. + * Lookup information about coin payments by @a h_contract_terms and + * @a coin_pub. * * @param cls closure - * @param instance_id identifying the instance - * @param fulfillment_url URL that canonically identifies the resource - * being paid for - * @param session_id session id - * @param[out] order_id where to store the order ID that was used when - * paying for the resource URL + * @param instance_id instance to lookup payments for + * @param h_contract_terms proposal data's hashcode + * @param coin_pub public key to use for the search + * @param cb function to call with payment data + * @param cb_cls closure for @a cb * @return transaction status */ -enum GNUNET_DB_QueryStatus -postgres_lookup_order_by_fulfillment (void *cls, - const char *instance_id, - const char *fulfillment_url, - const char *session_id, - char **order_id) +static enum GNUNET_DB_QueryStatus +postgres_lookup_deposits_by_contract_and_coin ( + void *cls, + const char *instance_id, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + TALER_MERCHANTDB_CoinDepositCallback cb, + void *cb_cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (fulfillment_url), - GNUNET_PQ_query_param_string (session_id), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_end }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_string ("order_id", - order_id), - GNUNET_PQ_result_spec_end + struct LookupDepositsByCnCContext ldcc = { + .cb = cb, + .cb_cls = cb_cls, + .pg = pg }; + enum GNUNET_DB_QueryStatus qs; - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_order_by_fulfillment", - params, - rs); + check_connection (pg); + qs = GNUNET_PQ_eval_prepared_multi_select ( + pg->conn, + "lookup_deposits_by_contract_and_coin", + params, + &lookup_deposits_by_contract_and_coin_cb, + &ldcc); + if (0 >= qs) + return qs; + return ldcc.qs; } /** - * Insert information about a wire transfer the merchant has received. + * Lookup transfer status. * * @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 + * @param instance_id at which instance should we resolve the transfer + * @param exchange_url the exchange that made the transfer + * @param wtid wire transfer subject + * @param[out] total_amount amount that was debited from our + * aggregate balance at the exchange (in total, sum of + * the wire transfer amount and the @a wire_fee) + * @param[out] wire_fee the wire fee the exchange charged (only set if @a have_exchange_sig is true) + * @param[out] exchange_amount the amount the exchange claims was transferred (only set if @a have_exchange_sig is true) + * @param[out] execution_time when the transfer was executed by the exchange (only set if @a have_exchange_sig is true) + * @param[out] have_exchange_sig do we have a response from the exchange about this transfer + * @param[out] verified did we confirm the transfer was OK * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_insert_transfer ( +postgres_lookup_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 TALER_Amount *total_amount, + struct TALER_Amount *wire_fee, + struct TALER_Amount *exchange_amount, + struct GNUNET_TIME_Timestamp *execution_time, + bool *have_exchange_sig, + bool *verified) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (exchange_url), GNUNET_PQ_query_param_auto_from_type (wtid), - 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 }; + uint8_t verified8; + /** Amount we got actually credited, _excludes_ the wire fee */ + bool no_sig; + struct TALER_Amount credit_amount; + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_AMOUNT ("credit_amount", + &credit_amount), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", + wire_fee), + &no_sig), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_amount", + exchange_amount), + NULL), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_timestamp ("execution_time", + execution_time), + NULL), + GNUNET_PQ_result_spec_auto_from_type ("verified", + &verified8), + GNUNET_PQ_result_spec_end + }; + enum GNUNET_DB_QueryStatus qs; check_connection (pg); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_transfer", - params); + *execution_time = GNUNET_TIME_UNIT_ZERO_TS; + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_transfer", + params, + rs); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Lookup transfer returned %d\n", + qs); + if (qs > 0) + { + *have_exchange_sig = ! no_sig; + *verified = (0 != verified8); + if ( (! no_sig) && + (0 > + TALER_amount_add (total_amount, + &credit_amount, + wire_fee)) ) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } + else + { + *verified = false; + *have_exchange_sig = false; + } + return qs; } /** - * Delete information about a transfer. Note that transfers - * confirmed by the exchange cannot be deleted anymore. - * - * @param cls closure - * @param instance_id instance to delete transfer of - * @param transfer_serial_id transfer to delete - * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS - * if deletion is prohibited OR transfer is unknown + * Closure for #lookup_transfer_summary_cb(). */ -static enum GNUNET_DB_QueryStatus -postgres_delete_transfer (void *cls, - const char *instance_id, - uint64_t transfer_serial_id) +struct LookupTransferSummaryContext { - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_uint64 (&transfer_serial_id), - GNUNET_PQ_query_param_end - }; + /** + * Function to call for each order that was aggregated. + */ + TALER_MERCHANTDB_TransferSummaryCallback cb; - check_connection (pg); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_transfer", - params); -} + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Plugin context. + */ + struct PostgresClosure *pg; + + /** + * Transaction result. + */ + enum GNUNET_DB_QueryStatus qs; +}; /** - * Check if information about a transfer exists with the - * backend. Returns no data, only the query status. + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. * - * @param cls closure - * @param instance_id instance to delete transfer of - * @param transfer_serial_id transfer to delete - * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT - * if the transfer record exists + * @param cls of type `struct LookupTransferSummaryContext *` + * @param result the postgres result + * @param num_results the number of results in @a result */ -static enum GNUNET_DB_QueryStatus -postgres_check_transfer_exists (void *cls, - const char *instance_id, - uint64_t transfer_serial_id) +static void +lookup_transfer_summary_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_uint64 (&transfer_serial_id), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_end - }; + struct LookupTransferSummaryContext *ltdc = cls; + struct PostgresClosure *pg = ltdc->pg; - check_connection (pg); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "check_transfer_exists", - params, - rs); + for (unsigned int i = 0; i<num_results; i++) + { + char *order_id; + struct TALER_Amount deposit_value; + struct TALER_Amount deposit_fee; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_string ("order_id", + &order_id), + TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_value", + &deposit_value), + TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_fee", + &deposit_fee), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ltdc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return; + } + ltdc->cb (ltdc->cb_cls, + order_id, + &deposit_value, + &deposit_fee); + GNUNET_PQ_cleanup_result (rs); + } + ltdc->qs = num_results; } /** - * Lookup account serial by payto URI. + * Lookup transfer summary. * * @param cls closure - * @param instance_id instance to lookup the account from - * @param payto_uri what is the merchant's bank account to lookup - * @param[out] account_serial serial number of the account + * @param exchange_url the exchange that made the transfer + * @param wtid wire transfer subject + * @param cb function to call with detailed transfer data + * @param cb_cls closure for @a cb * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_lookup_account (void *cls, - const char *instance_id, - const char *payto_uri, - uint64_t *account_serial) +postgres_lookup_transfer_summary ( + void *cls, + const char *exchange_url, + const struct TALER_WireTransferIdentifierRawP *wtid, + TALER_MERCHANTDB_TransferSummaryCallback cb, + void *cb_cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (payto_uri), + GNUNET_PQ_query_param_string (exchange_url), + GNUNET_PQ_query_param_auto_from_type (wtid), GNUNET_PQ_query_param_end }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("account_serial", - account_serial), - GNUNET_PQ_result_spec_end + struct LookupTransferSummaryContext ltdc = { + .cb = cb, + .cb_cls = cb_cls, + .pg = pg }; + enum GNUNET_DB_QueryStatus qs; check_connection (pg); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_account", - params, - rs); + qs = GNUNET_PQ_eval_prepared_multi_select ( + pg->conn, + "lookup_transfer_summary", + params, + &lookup_transfer_summary_cb, + &ltdc); + if (0 >= qs) + return qs; + return ltdc.qs; } /** - * 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 + * Closure for #lookup_transfer_details_cb(). */ -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 LookupTransferDetailsContext { - 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). - * - * @param cls closure - * @param master_pub public key of the exchange - * @param wire_method the wire method - * @param contract_date date of the contract to use for the lookup - * @param[out] fees wire fees charged - * @param[out] start_date start of fee being used - * @param[out] end_date end of fee being used - * @param[out] master_sig signature of exchange over fee structure - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -postgres_lookup_wire_fee (void *cls, - const struct TALER_MasterPublicKeyP *master_pub, - const char *wire_method, - struct GNUNET_TIME_Timestamp contract_date, - struct TALER_WireFeeSet *fees, - struct GNUNET_TIME_Timestamp *start_date, - struct GNUNET_TIME_Timestamp *end_date, - struct TALER_MasterSignatureP *master_sig) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_HashCode h_wire_method; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (master_pub), - GNUNET_PQ_query_param_auto_from_type (&h_wire_method), - GNUNET_PQ_query_param_timestamp (&contract_date), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", - &fees->wire), - TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee", - &fees->closing), - GNUNET_PQ_result_spec_timestamp ("start_date", - start_date), - GNUNET_PQ_result_spec_timestamp ("end_date", - end_date), - GNUNET_PQ_result_spec_auto_from_type ("master_sig", - master_sig), - GNUNET_PQ_result_spec_end - }; - - check_connection (pg); - GNUNET_CRYPTO_hash (wire_method, - strlen (wire_method) + 1, - &h_wire_method); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_wire_fee", - params, - rs); -} - - -/** - * Closure for #lookup_deposits_by_contract_and_coin_cb(). - */ -struct LookupDepositsByCnCContext -{ - /** - * Function to call for each deposit. - */ - TALER_MERCHANTDB_CoinDepositCallback cb; + /** + * Function to call for each order that was aggregated. + */ + TALER_MERCHANTDB_TransferDetailsCallback cb; /** * Closure for @e cb. @@ -3189,51 +2779,33 @@ struct LookupDepositsByCnCContext * Function to be called with the results of a SELECT statement * that has returned @a num_results results. * - * @param cls of type `struct LookupDepositsByCnCContext *` + * @param cls of type `struct LookupTransferDetailsContext *` * @param result the postgres result * @param num_results the number of results in @a result */ static void -lookup_deposits_by_contract_and_coin_cb (void *cls, - PGresult *result, - unsigned int num_results) +lookup_transfer_details_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct LookupDepositsByCnCContext *ldcc = cls; - struct PostgresClosure *pg = ldcc->pg; + struct LookupTransferDetailsContext *ltdc = cls; + struct PostgresClosure *pg = ltdc->pg; for (unsigned int i = 0; i<num_results; i++) { - char *exchange_url; - struct TALER_Amount amount_with_fee; - struct TALER_Amount deposit_fee; - struct TALER_Amount refund_fee; - struct TALER_Amount wire_fee; - struct TALER_MerchantWireHashP h_wire; - struct GNUNET_TIME_Timestamp deposit_timestamp; - struct GNUNET_TIME_Timestamp refund_deadline; - struct TALER_ExchangeSignatureP exchange_sig; - struct TALER_ExchangePublicKeyP exchange_pub; + uint64_t current_offset; + struct TALER_TrackTransferDetails ttd; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_string ("exchange_url", - &exchange_url), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", - &amount_with_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee", - &deposit_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee", - &refund_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", - &wire_fee), - GNUNET_PQ_result_spec_auto_from_type ("h_wire", - &h_wire), - GNUNET_PQ_result_spec_timestamp ("deposit_timestamp", - &deposit_timestamp), - GNUNET_PQ_result_spec_timestamp ("refund_deadline", - &refund_deadline), - GNUNET_PQ_result_spec_auto_from_type ("exchange_sig", - &exchange_sig), - GNUNET_PQ_result_spec_auto_from_type ("exchange_pub", - &exchange_pub), + GNUNET_PQ_result_spec_uint64 ("offset_in_exchange_list", + &current_offset), + GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", + &ttd.h_contract_terms), + GNUNET_PQ_result_spec_auto_from_type ("coin_pub", + &ttd.coin_pub), + TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_value", + &ttd.coin_value), + TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_fee", + &ttd.coin_fee), GNUNET_PQ_result_spec_end }; @@ -3243,55 +2815,43 @@ lookup_deposits_by_contract_and_coin_cb (void *cls, i)) { GNUNET_break (0); - ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR; + ltdc->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } - ldcc->cb (ldcc->cb_cls, - exchange_url, - &amount_with_fee, - &deposit_fee, - &refund_fee, - &wire_fee, - &h_wire, - deposit_timestamp, - refund_deadline, - &exchange_sig, - &exchange_pub); + ltdc->cb (ltdc->cb_cls, + (unsigned int) current_offset, + &ttd); GNUNET_PQ_cleanup_result (rs); } - ldcc->qs = num_results; + ltdc->qs = num_results; } /** - * Lookup information about coin payments by @a h_contract_terms and - * @a coin_pub. + * Lookup transfer details. * * @param cls closure - * @param instance_id instance to lookup payments for - * @param h_contract_terms proposal data's hashcode - * @param coin_pub public key to use for the search - * @param cb function to call with payment data + * @param exchange_url the exchange that made the transfer + * @param wtid wire transfer subject + * @param cb function to call with detailed transfer data * @param cb_cls closure for @a cb * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_lookup_deposits_by_contract_and_coin ( +postgres_lookup_transfer_details ( void *cls, - const char *instance_id, - const struct TALER_PrivateContractHashP *h_contract_terms, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - TALER_MERCHANTDB_CoinDepositCallback cb, + const char *exchange_url, + const struct TALER_WireTransferIdentifierRawP *wtid, + TALER_MERCHANTDB_TransferDetailsCallback cb, void *cb_cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (h_contract_terms), - GNUNET_PQ_query_param_auto_from_type (coin_pub), + GNUNET_PQ_query_param_string (exchange_url), + GNUNET_PQ_query_param_auto_from_type (wtid), GNUNET_PQ_query_param_end }; - struct LookupDepositsByCnCContext ldcc = { + struct LookupTransferDetailsContext ltdc = { .cb = cb, .cb_cls = cb_cls, .pg = pg @@ -3301,289 +2861,256 @@ postgres_lookup_deposits_by_contract_and_coin ( check_connection (pg); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_deposits_by_contract_and_coin", + "lookup_transfer_details", params, - &lookup_deposits_by_contract_and_coin_cb, - &ldcc); + &lookup_transfer_details_cb, + &ltdc); if (0 >= qs) return qs; - return ldcc.qs; + return ltdc.qs; } /** - * Lookup transfer status. + * Store information about wire fees charged by an exchange, + * including signature (so we have proof). * * @param cls closure - * @param instance_id at which instance should we resolve the transfer - * @param exchange_url the exchange that made the transfer - * @param wtid wire transfer subject - * @param[out] total_amount amount that was debited from our - * aggregate balance at the exchange (in total, sum of - * the wire transfer amount and the @a wire_fee) - * @param[out] wire_fee the wire fee the exchange charged (only set if @a have_exchange_sig is true) - * @param[out] exchange_amount the amount the exchange claims was transferred (only set if @a have_exchange_sig is true) - * @param[out] execution_time when the transfer was executed by the exchange (only set if @a have_exchange_sig is true) - * @param[out] have_exchange_sig do we have a response from the exchange about this transfer - * @param[out] verified did we confirm the transfer was OK - * @return transaction status + * @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_lookup_transfer ( +postgres_store_wire_fee_by_exchange ( void *cls, - const char *instance_id, - const char *exchange_url, - const struct TALER_WireTransferIdentifierRawP *wtid, - struct TALER_Amount *total_amount, - struct TALER_Amount *wire_fee, - struct TALER_Amount *exchange_amount, - struct GNUNET_TIME_Timestamp *execution_time, - bool *have_exchange_sig, - bool *verified) + 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_string (exchange_url), - GNUNET_PQ_query_param_auto_from_type (wtid), - GNUNET_PQ_query_param_string (instance_id), + 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 }; - uint8_t verified8; - /** Amount we got actually credited, _excludes_ the wire fee */ - bool no_sig; - struct TALER_Amount credit_amount; - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_AMOUNT ("credit_amount", - &credit_amount), - GNUNET_PQ_result_spec_allow_null ( - TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", - wire_fee), - &no_sig), - GNUNET_PQ_result_spec_allow_null ( - TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_amount", - exchange_amount), - NULL), - GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_timestamp ("execution_time", - execution_time), - NULL), - GNUNET_PQ_result_spec_auto_from_type ("verified", - &verified8), - GNUNET_PQ_result_spec_end - }; - enum GNUNET_DB_QueryStatus qs; + /* no preflight check here, run in its own transaction by the caller */ check_connection (pg); - *execution_time = GNUNET_TIME_UNIT_ZERO_TS; - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_transfer", - params, - rs); GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Lookup transfer returned %d\n", - qs); - if (qs > 0) - { - *have_exchange_sig = ! no_sig; - *verified = (0 != verified8); - if ( (! no_sig) && - (0 > - TALER_amount_add (total_amount, - &credit_amount, - wire_fee)) ) - { - GNUNET_break (0); - return GNUNET_DB_STATUS_HARD_ERROR; - } - } - else - { - *verified = false; - *have_exchange_sig = false; - } - return qs; + "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); } /** - * Closure for #lookup_transfer_summary_cb(). + * Add @a credit to a reserve to be used for tipping. Note that + * this function does not actually perform any wire transfers to + * credit the reserve, it merely tells the merchant backend that + * a reserve now exists. This has to happen before tips can be + * authorized. + * + * @param cls closure, typically a connection to the db + * @param instance_id which instance is the reserve tied to + * @param reserve_priv which reserve is topped up or created + * @param reserve_pub which reserve is topped up or created + * @param 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 */ -struct LookupTransferSummaryContext +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) { - /** - * Function to call for each order that was aggregated. - */ - TALER_MERCHANTDB_TransferSummaryCallback cb; - - /** - * Closure for @e cb. - */ - void *cb_cls; - - /** - * Plugin context. - */ - struct PostgresClosure *pg; - - /** - * Transaction result. - */ + 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; + } -/** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. - * - * @param cls of type `struct LookupTransferSummaryContext *` - * @param result the postgres result - * @param num_results the number of results in @a result - */ -static void -lookup_transfer_summary_cb (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct LookupTransferSummaryContext *ltdc = cls; - struct PostgresClosure *pg = ltdc->pg; + /* 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 + }; - for (unsigned int i = 0; i<num_results; i++) + 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 */ { - char *order_id; - struct TALER_Amount deposit_value; - struct TALER_Amount deposit_fee; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_string ("order_id", - &order_id), - TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_value", - &deposit_value), - TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_fee", - &deposit_fee), - GNUNET_PQ_result_spec_end + 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 (reserve_pub), + GNUNET_PQ_query_param_end }; - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_reserve_key", + params); + if (0 > qs) { - GNUNET_break (0); - ltdc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; + postgres_rollback (pg); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + return qs; } - ltdc->cb (ltdc->cb_cls, - order_id, - &deposit_value, - &deposit_fee); - GNUNET_PQ_cleanup_result (rs); } - ltdc->qs = num_results; + qs = postgres_commit (pg); + if (0 <= qs) + return TALER_EC_NONE; /* success */ + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + return qs; } /** - * Lookup transfer summary. + * Confirms @a credit as the amount the exchange claims to have received and + * thus really 'activates' the reserve. This has to happen before tips can + * be authorized. * - * @param cls closure - * @param exchange_url the exchange that made the transfer - * @param wtid wire transfer subject - * @param cb function to call with detailed transfer data - * @param cb_cls closure for @a cb - * @return transaction status + * @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_lookup_transfer_summary ( - void *cls, - const char *exchange_url, - const struct TALER_WireTransferIdentifierRawP *wtid, - TALER_MERCHANTDB_TransferSummaryCallback cb, - void *cb_cls) +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 (exchange_url), - GNUNET_PQ_query_param_auto_from_type (wtid), + 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 }; - struct LookupTransferSummaryContext ltdc = { - .cb = cb, - .cb_cls = cb_cls, - .pg = pg - }; - enum GNUNET_DB_QueryStatus qs; - check_connection (pg); - qs = GNUNET_PQ_eval_prepared_multi_select ( - pg->conn, - "lookup_transfer_summary", - params, - &lookup_transfer_summary_cb, - &ltdc); - if (0 >= qs) - return qs; - return ltdc.qs; + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "activate_reserve", + params); } /** - * Closure for #lookup_transfer_details_cb(). + * Closure for #lookup_pending_reserves_cb. */ -struct LookupTransferDetailsContext +struct LookupPendingReservesContext { /** - * Function to call for each order that was aggregated. + * Postgres context. */ - TALER_MERCHANTDB_TransferDetailsCallback cb; + struct PostgresClosure *pg; /** - * Closure for @e cb. + * Function to call with the results */ - void *cb_cls; + TALER_MERCHANTDB_PendingReservesCallback cb; /** - * Plugin context. + * Closure for @e cb */ - struct PostgresClosure *pg; + void *cb_cls; /** - * Transaction result. + * Set in case of errors. */ enum GNUNET_DB_QueryStatus qs; + }; /** * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. + * that has returned @a num_results results about accounts. * - * @param cls of type `struct LookupTransferDetailsContext *` + * @param[in,out] cls of type `struct LookupReservesContext *` * @param result the postgres result * @param num_results the number of results in @a result */ static void -lookup_transfer_details_cb (void *cls, +lookup_pending_reserves_cb (void *cls, PGresult *result, unsigned int num_results) { - struct LookupTransferDetailsContext *ltdc = cls; - struct PostgresClosure *pg = ltdc->pg; + struct LookupPendingReservesContext *lrc = cls; + struct PostgresClosure *pg = lrc->pg; - for (unsigned int i = 0; i<num_results; i++) + for (unsigned int i = 0; i < num_results; i++) { - uint64_t current_offset; - struct TALER_TrackTransferDetails ttd; + struct TALER_ReservePublicKeyP reserve_pub; + struct TALER_Amount merchant_initial_balance; + char *exchange_url; + char *instance_id; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("offset_in_exchange_list", - &current_offset), - GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", - &ttd.h_contract_terms), - GNUNET_PQ_result_spec_auto_from_type ("coin_pub", - &ttd.coin_pub), - TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_value", - &ttd.coin_value), - TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_fee", - &ttd.coin_fee), + GNUNET_PQ_result_spec_auto_from_type ("reserve_pub", + &reserve_pub), + GNUNET_PQ_result_spec_string ("merchant_id", + &instance_id), + GNUNET_PQ_result_spec_string ("exchange_url", + &exchange_url), + TALER_PQ_RESULT_SPEC_AMOUNT ("merchant_initial_balance", + &merchant_initial_balance), GNUNET_PQ_result_spec_end }; @@ -3593,247 +3120,59 @@ lookup_transfer_details_cb (void *cls, i)) { GNUNET_break (0); - ltdc->qs = GNUNET_DB_STATUS_HARD_ERROR; + lrc->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } - ltdc->cb (ltdc->cb_cls, - (unsigned int) current_offset, - &ttd); + lrc->cb (lrc->cb_cls, + instance_id, + exchange_url, + &reserve_pub, + &merchant_initial_balance); GNUNET_PQ_cleanup_result (rs); } - ltdc->qs = num_results; } /** - * Lookup transfer details. + * Lookup reserves pending activation across all instances. * * @param cls closure - * @param exchange_url the exchange that made the transfer - * @param wtid wire transfer subject - * @param cb function to call with detailed transfer data + * @param cb function to call with reserve summary data * @param cb_cls closure for @a cb * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_lookup_transfer_details ( - void *cls, - const char *exchange_url, - const struct TALER_WireTransferIdentifierRawP *wtid, - TALER_MERCHANTDB_TransferDetailsCallback cb, - void *cb_cls) +postgres_lookup_pending_reserves (void *cls, + TALER_MERCHANTDB_PendingReservesCallback cb, + void *cb_cls) { struct PostgresClosure *pg = cls; + struct LookupPendingReservesContext lrc = { + .pg = pg, + .cb = cb, + .cb_cls = cb_cls + }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (exchange_url), - GNUNET_PQ_query_param_auto_from_type (wtid), GNUNET_PQ_query_param_end }; - struct LookupTransferDetailsContext ltdc = { - .cb = cb, - .cb_cls = cb_cls, - .pg = pg - }; enum GNUNET_DB_QueryStatus qs; check_connection (pg); - qs = GNUNET_PQ_eval_prepared_multi_select ( - pg->conn, - "lookup_transfer_details", - params, - &lookup_transfer_details_cb, - &ltdc); - if (0 >= qs) - return qs; - return ltdc.qs; + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "lookup_pending_reserves", + params, + &lookup_pending_reserves_cb, + &lrc); + if (lrc.qs < 0) + return lrc.qs; + return qs; } /** - * 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 + * Closure for #lookup_reserve_tips_cb(). */ -static enum GNUNET_DB_QueryStatus -postgres_store_wire_fee_by_exchange ( - void *cls, - const struct TALER_MasterPublicKeyP *master_pub, - const struct GNUNET_HashCode *h_wire_method, - const struct TALER_WireFeeSet *fees, - struct GNUNET_TIME_Timestamp start_date, - struct GNUNET_TIME_Timestamp end_date, - const struct TALER_MasterSignatureP *master_sig) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (master_pub), - GNUNET_PQ_query_param_auto_from_type (h_wire_method), - TALER_PQ_query_param_amount (&fees->wire), - TALER_PQ_query_param_amount (&fees->closing), - GNUNET_PQ_query_param_timestamp (&start_date), - GNUNET_PQ_query_param_timestamp (&end_date), - GNUNET_PQ_query_param_auto_from_type (master_sig), - GNUNET_PQ_query_param_end - }; - - /* no preflight check here, run in its own transaction by the caller */ - check_connection (pg); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Storing wire fee for %s starting at %s of %s\n", - TALER_B2S (master_pub), - GNUNET_TIME_timestamp2s (start_date), - TALER_amount2s (&fees->wire)); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_wire_fee", - params); -} - - -/** - * Add @a credit to a reserve to be used for tipping. Note that - * this function does not actually perform any wire transfers to - * credit the reserve, it merely tells the merchant backend that - * a reserve now exists. This has to happen before tips can be - * authorized. - * - * @param cls closure, typically a connection to the db - * @param instance_id which instance is the reserve tied to - * @param reserve_priv which reserve is topped up or created - * @param reserve_pub which reserve is topped up or created - * @param 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 (reserve_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 tips can - * be authorized. - * - * @param cls closure, typically a connection to the db - * @param instance_id which instance is the reserve tied to - * @param reserve_pub which reserve is topped up or created - * @param initial_exchange_balance how much money was be added to the reserve - * according to the exchange - * @return transaction status, usually - * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success - */ -static enum GNUNET_DB_QueryStatus -postgres_activate_reserve (void *cls, - const char *instance_id, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_Amount *initial_exchange_balance) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (reserve_pub), - TALER_PQ_query_param_amount (initial_exchange_balance), - GNUNET_PQ_query_param_end - }; - - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "activate_reserve", - params); -} - - -/** - * Closure for #lookup_pending_reserves_cb. - */ -struct LookupPendingReservesContext +struct LookupTipsContext { /** * Postgres context. @@ -3841,20 +3180,19 @@ struct LookupPendingReservesContext struct PostgresClosure *pg; /** - * Function to call with the results + * Array with information about tips generated from this reserve. */ - TALER_MERCHANTDB_PendingReservesCallback cb; + struct TALER_MERCHANTDB_TipDetails *tips; /** - * Closure for @e cb + * Length of the @e tips array. */ - void *cb_cls; + unsigned int tips_length; /** * Set in case of errors. */ enum GNUNET_DB_QueryStatus qs; - }; @@ -3862,33 +3200,31 @@ struct LookupPendingReservesContext * Function to be called with the results of a SELECT statement * that has returned @a num_results results about accounts. * - * @param[in,out] cls of type `struct LookupReservesContext *` + * @param[in,out] cls of type `struct LookupTipsContext *` * @param result the postgres result * @param num_results the number of results in @a result */ static void -lookup_pending_reserves_cb (void *cls, - PGresult *result, - unsigned int num_results) +lookup_reserve_tips_cb (void *cls, + PGresult *result, + unsigned int num_results) { - struct LookupPendingReservesContext *lrc = cls; - struct PostgresClosure *pg = lrc->pg; + struct LookupTipsContext *ltc = cls; + struct PostgresClosure *pg = ltc->pg; + GNUNET_array_grow (ltc->tips, + ltc->tips_length, + num_results); for (unsigned int i = 0; i < num_results; i++) { - struct TALER_ReservePublicKeyP reserve_pub; - struct TALER_Amount merchant_initial_balance; - char *exchange_url; - char *instance_id; + struct TALER_MERCHANTDB_TipDetails *td = &ltc->tips[i]; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("reserve_pub", - &reserve_pub), - GNUNET_PQ_result_spec_string ("merchant_id", - &instance_id), - GNUNET_PQ_result_spec_string ("exchange_url", - &exchange_url), - TALER_PQ_RESULT_SPEC_AMOUNT ("merchant_initial_balance", - &merchant_initial_balance), + GNUNET_PQ_result_spec_string ("justification", + &td->reason), + GNUNET_PQ_result_spec_auto_from_type ("tip_id", + &td->tip_id), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount", + &td->total_amount), GNUNET_PQ_result_spec_end }; @@ -3898,154 +3234,40 @@ lookup_pending_reserves_cb (void *cls, i)) { GNUNET_break (0); - lrc->qs = GNUNET_DB_STATUS_HARD_ERROR; + ltc->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } - lrc->cb (lrc->cb_cls, - instance_id, - exchange_url, - &reserve_pub, - &merchant_initial_balance); - GNUNET_PQ_cleanup_result (rs); } } /** - * Lookup reserves pending activation across all instances. + * Lookup reserve details. * * @param cls closure + * @param instance_id instance to lookup payments for + * @param reserve_pub public key of the reserve to inspect + * @param fetch_tips if true, also return information about tips * @param cb function to call with reserve summary data * @param cb_cls closure for @a cb * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_lookup_pending_reserves (void *cls, - TALER_MERCHANTDB_PendingReservesCallback cb, - void *cb_cls) +postgres_lookup_reserve (void *cls, + const char *instance_id, + const struct TALER_ReservePublicKeyP *reserve_pub, + bool fetch_tips, + TALER_MERCHANTDB_ReserveDetailsCallback cb, + void *cb_cls) { struct PostgresClosure *pg = cls; - struct LookupPendingReservesContext lrc = { + struct LookupTipsContext ltc = { .pg = pg, - .cb = cb, - .cb_cls = cb_cls + .qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_end - }; - enum GNUNET_DB_QueryStatus qs; - - check_connection (pg); - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_pending_reserves", - params, - &lookup_pending_reserves_cb, - &lrc); - if (lrc.qs < 0) - return lrc.qs; - return qs; -} - - -/** - * Closure for #lookup_reserve_tips_cb(). - */ -struct LookupTipsContext -{ - /** - * Postgres context. - */ - struct PostgresClosure *pg; - - /** - * Array with information about tips generated from this reserve. - */ - struct TALER_MERCHANTDB_TipDetails *tips; - - /** - * Length of the @e tips array. - */ - unsigned int tips_length; - - /** - * Set in case of errors. - */ - enum GNUNET_DB_QueryStatus qs; -}; - - -/** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results about accounts. - * - * @param[in,out] cls of type `struct LookupTipsContext *` - * @param result the postgres result - * @param num_results the number of results in @a result - */ -static void -lookup_reserve_tips_cb (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct LookupTipsContext *ltc = cls; - struct PostgresClosure *pg = ltc->pg; - - GNUNET_array_grow (ltc->tips, - ltc->tips_length, - num_results); - for (unsigned int i = 0; i < num_results; i++) - { - struct TALER_MERCHANTDB_TipDetails *td = &ltc->tips[i]; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_string ("justification", - &td->reason), - GNUNET_PQ_result_spec_auto_from_type ("tip_id", - &td->tip_id), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount", - &td->total_amount), - GNUNET_PQ_result_spec_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - ltc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } - } -} - - -/** - * Lookup reserve details. - * - * @param cls closure - * @param instance_id instance to lookup payments for - * @param reserve_pub public key of the reserve to inspect - * @param fetch_tips if true, also return information about tips - * @param cb function to call with reserve summary data - * @param cb_cls closure for @a cb - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_lookup_reserve (void *cls, - const char *instance_id, - const struct TALER_ReservePublicKeyP *reserve_pub, - bool fetch_tips, - TALER_MERCHANTDB_ReserveDetailsCallback cb, - void *cb_cls) -{ - struct PostgresClosure *pg = cls; - struct LookupTipsContext ltc = { - .pg = pg, - .qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT - }; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_auto_from_type (reserve_pub), + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (reserve_pub), GNUNET_PQ_query_param_end }; struct GNUNET_TIME_Timestamp creation_time; @@ -6206,980 +5428,6 @@ postgres_connect (void *cls) struct GNUNET_PQ_PreparedStatement ps[] = { GNUNET_PQ_make_prepare ("end_transaction", "COMMIT"), - /* for postgres_expire_locks() */ - GNUNET_PQ_make_prepare ("unlock_products", - "DELETE FROM merchant_inventory_locks" - " WHERE expiration < $1"), - /* for postgres_expire_locks() */ - GNUNET_PQ_make_prepare ("unlock_orders", - "DELETE FROM merchant_orders" - " WHERE pay_deadline < $1"), - /* for postgres_expire_locks() */ - GNUNET_PQ_make_prepare ("unlock_contracts", - "DELETE FROM merchant_contract_terms" - " WHERE NOT paid" - " AND pay_deadline < $1"), - - /* for postgres_delete_order() */ - GNUNET_PQ_make_prepare ("delete_order", - "WITH ms AS" - "(SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - ", mc AS" - "(SELECT paid" - " FROM merchant_contract_terms" - " JOIN ms USING (merchant_serial)" - " WHERE order_id=$2) " - "DELETE" - " FROM merchant_orders mo" - " WHERE order_id=$2" - " AND merchant_serial=(SELECT merchant_serial FROM ms)" - " AND ( (pay_deadline < $3)" - " OR (NOT EXISTS (SELECT paid FROM mc))" - " OR ($4 AND (FALSE=(SELECT paid FROM mc))) );"), - GNUNET_PQ_make_prepare ("delete_contract", - "DELETE" - " FROM merchant_contract_terms" - " WHERE order_id=$2 AND" - " merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND NOT paid;"), - /* for postgres_lookup_order() */ - GNUNET_PQ_make_prepare ("lookup_order", - "SELECT" - " contract_terms" - ",claim_token" - ",h_post_data" - ",pos_key" - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND merchant_orders.order_id=$2"), - /* for postgres_lookup_order_summary() */ - GNUNET_PQ_make_prepare ("lookup_order_summary", - "(SELECT" - " creation_time" - ",order_serial" - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND merchant_contract_terms.order_id=$2)" - "UNION" - "(SELECT" - " creation_time" - ",order_serial" - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND merchant_orders.order_id=$2)"), - /* for postgres_lookup_orders() */ - GNUNET_PQ_make_prepare ("lookup_orders_inc", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " ORDER BY order_serial ASC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " ORDER BY order_serial ASC" - " LIMIT $2)" - " ORDER BY order_serial ASC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_inc_paid", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND" - " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " ORDER BY order_serial ASC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND" - " BOOL($5) = paid" - " ORDER BY order_serial ASC" - " LIMIT $2)" - " ORDER BY order_serial ASC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_inc_refunded", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND" - " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " ORDER BY order_serial ASC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND" - " CAST($6 as BOOL) = (order_serial IN" - " (SELECT order_serial " - " FROM merchant_refunds))" - " ORDER BY order_serial ASC" - " LIMIT $2)" - " ORDER BY order_serial ASC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_inc_wired", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND" - " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " ORDER BY order_serial ASC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND" - " BOOL($7) = wired" - " ORDER BY order_serial ASC" - " LIMIT $2)" - " ORDER BY order_serial ASC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_inc_paid_refunded", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND" - " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ - " AND" - " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " ORDER BY order_serial ASC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND" - " BOOL($5) = paid" - " AND" - " BOOL($6) = (order_serial IN" - " (SELECT order_serial " - " FROM merchant_refunds))" - " ORDER BY order_serial ASC" - " LIMIT $2)" - " ORDER BY order_serial ASC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_inc_paid_wired", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND" - " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ - " AND" - " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " ORDER BY order_serial ASC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND" - " BOOL($5) = paid" - " AND" - " BOOL($7) = wired" - " ORDER BY order_serial ASC" - " LIMIT $2)" - " ORDER BY order_serial ASC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_inc_refunded_wired", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND" - " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ - " AND" - " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " ORDER BY order_serial ASC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND" - " BOOL($6) = (order_serial IN" - " (SELECT order_serial " - " FROM merchant_refunds))" - " AND" - " BOOL($7) = wired" - " ORDER BY order_serial ASC" - " LIMIT $2)" - " ORDER BY order_serial ASC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_inc_paid_refunded_wired", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND" - " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ - " AND" - " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ - " AND" - " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " ORDER BY order_serial ASC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND" - " BOOL($5) = paid" - " AND" - " BOOL($6) = (order_serial IN" - " (SELECT order_serial " - " FROM merchant_refunds))" - " AND" - " BOOL($7) = wired" - " ORDER BY order_serial ASC" - " LIMIT $2)" - " ORDER BY order_serial ASC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_dec", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " ORDER BY order_serial DESC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " ORDER BY order_serial DESC" - " LIMIT $2)" - " ORDER BY order_serial DESC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_dec_paid", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND" - " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " ORDER BY order_serial DESC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND" - " BOOL($5) = paid" - " ORDER BY order_serial DESC" - " LIMIT $2)" - " ORDER BY order_serial DESC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_dec_refunded", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND" - " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " ORDER BY order_serial DESC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND" - " BOOL($6) = (order_serial IN" - " (SELECT order_serial " - " FROM merchant_refunds))" - " ORDER BY order_serial DESC" - " LIMIT $2)" - " ORDER BY order_serial DESC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_dec_wired", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND" - " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " ORDER BY order_serial DESC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND" - " BOOL($7) = wired" - " ORDER BY order_serial DESC" - " LIMIT $2)" - " ORDER BY order_serial DESC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_dec_paid_refunded", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND" - " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ - " AND" - " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " ORDER BY order_serial DESC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($7 as BOOL)" /* otherwise $7 is unused and Postgres unhappy */ - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND" - " BOOL($5) = paid" - " AND" - " BOOL($6) = (order_serial IN" - " (SELECT order_serial " - " FROM merchant_refunds))" - " ORDER BY order_serial DESC" - " LIMIT $2)" - " ORDER BY order_serial DESC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_dec_paid_wired", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND" - " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ - " AND" - " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " ORDER BY order_serial DESC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($6 as BOOL)" /* otherwise $6 is unused and Postgres unhappy */ - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND" - " BOOL($5) = paid" - " AND" - " BOOL($7) = wired" - " ORDER BY order_serial DESC" - " LIMIT $2)" - " ORDER BY order_serial DESC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_dec_refunded_wired", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND" - " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ - " AND" - " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " ORDER BY order_serial DESC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - ",CAST($5 as BOOL)" /* otherwise $5 is unused and Postgres unhappy */ - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND" - " BOOL($6) = (order_serial IN" - " (SELECT order_serial " - " FROM merchant_refunds))" - " AND" - " BOOL($7) = wired" - " ORDER BY order_serial DESC" - " LIMIT $2)" - " ORDER BY order_serial DESC" - " LIMIT $2"), - GNUNET_PQ_make_prepare ("lookup_orders_dec_paid_refunded_wired", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND" - " NOT CAST($5 as BOOL)" /* unclaimed orders are never paid */ - " AND" - " NOT CAST($6 as BOOL)"/* unclaimed orders are never refunded */ - " AND" - " NOT CAST($7 as BOOL)" /* unclaimed orders are never wired */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " ORDER BY order_serial DESC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND" - " BOOL($5) = paid" - " AND" - " BOOL($6) = (order_serial IN" - " (SELECT order_serial " - " FROM merchant_refunds))" - " AND" - " BOOL($7) = wired" - " ORDER BY order_serial DESC" - " LIMIT $2)" - " ORDER BY order_serial DESC" - " LIMIT $2"), - /* for postgres_insert_order() */ - GNUNET_PQ_make_prepare ("insert_order", - "INSERT INTO merchant_orders" - "(merchant_serial" - ",order_id" - ",pay_deadline" - ",claim_token" - ",h_post_data" - ",creation_time" - ",contract_terms" - ",pos_key" - ",pos_algorithm)" - " SELECT merchant_serial," - " $2, $3, $4, $5, $6, $7, $8, $9" - " FROM merchant_instances" - " WHERE merchant_id=$1"), - /* for postgres_unlock_inventory() */ - GNUNET_PQ_make_prepare ("unlock_inventory", - "DELETE" - " FROM merchant_inventory_locks" - " WHERE lock_uuid=$1"), - /* for postgres_insert_order_lock() */ - GNUNET_PQ_make_prepare ("insert_order_lock", - "WITH tmp AS" - " (SELECT " - " product_serial" - " ,merchant_serial" - " ,total_stock" - " ,total_sold" - " ,total_lost" - " FROM merchant_inventory" - " WHERE product_id=$3" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1))" - " INSERT INTO merchant_order_locks" - " (product_serial" - " ,total_locked" - " ,order_serial)" - " SELECT tmp.product_serial, $4, order_serial" - " FROM merchant_orders" - " JOIN tmp USING(merchant_serial)" - " WHERE order_id=$2 AND" - " tmp.total_stock - tmp.total_sold - tmp.total_lost - $4 >= " - " (SELECT COALESCE(SUM(total_locked), 0)" - " FROM merchant_inventory_locks" - " WHERE product_serial=tmp.product_serial) + " - " (SELECT COALESCE(SUM(total_locked), 0)" - " FROM merchant_order_locks" - " WHERE product_serial=tmp.product_serial)"), - /* for postgres_lookup_contract_terms() */ - GNUNET_PQ_make_prepare ("lookup_contract_terms", - "SELECT" - " contract_terms" - ",order_serial" - ",claim_token" - ",paid" - " FROM merchant_contract_terms" - " WHERE order_id=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"), - /* for postgres_lookup_contract_terms() */ - GNUNET_PQ_make_prepare ("lookup_contract_terms2", - "SELECT" - " contract_terms" - ",order_serial" - ",claim_token" - ",paid" - ",pos_key" - ",pos_algorithm" - " FROM merchant_contract_terms" - " WHERE order_id=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"), - /* for postgres_insert_contract_terms() */ - GNUNET_PQ_make_prepare ("insert_contract_terms", - "INSERT INTO merchant_contract_terms" - "(order_serial" - ",merchant_serial" - ",order_id" - ",contract_terms" - ",h_contract_terms" - ",creation_time" - ",pay_deadline" - ",refund_deadline" - ",fulfillment_url" - ",claim_token" - ",pos_key" - ",pos_algorithm)" - "SELECT" - " mo.order_serial" - ",mo.merchant_serial" - ",mo.order_id" - ",$3" /* contract_terms */ - ",$4" /* h_contract_terms */ - ",mo.creation_time" - ",$5" /* pay_deadline */ - ",$6" /* refund_deadline */ - ",$7" /* fulfillment_url */ - ",mo.claim_token" - ",mo.pos_key" - ",mo.pos_algorithm" - " FROM merchant_orders mo" - " WHERE order_id=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " RETURNING order_serial"), - /* for postgres_update_contract_terms() */ - GNUNET_PQ_make_prepare ("update_contract_terms", - "UPDATE merchant_contract_terms SET" - " contract_terms=$3" - ",h_contract_terms=$4" - ",pay_deadline=$5" - ",refund_deadline=$6" - ",fulfillment_url=$7" - " WHERE order_id=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"), - /* for postgres_delete_contract_terms() */ - GNUNET_PQ_make_prepare ("delete_contract_terms", - "DELETE FROM merchant_contract_terms" - " WHERE order_id=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND ( ( (pay_deadline < $4) AND" - " (NOT paid) ) OR" - " (creation_time + $3 < $4) )"), /* for postgres_lookup_deposits() */ GNUNET_PQ_make_prepare ("lookup_deposits", "SELECT" @@ -8426,19 +6674,32 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) = &TMH_PG_update_product; plugin->lock_product = &TMH_PG_lock_product; - plugin->expire_locks = &postgres_expire_locks; - plugin->delete_order = &postgres_delete_order; - plugin->lookup_order = &postgres_lookup_order; - plugin->lookup_order_summary = &postgres_lookup_order_summary; - plugin->lookup_orders = &postgres_lookup_orders; - plugin->insert_order = &postgres_insert_order; - plugin->unlock_inventory = &postgres_unlock_inventory; - plugin->insert_order_lock = &postgres_insert_order_lock; - plugin->lookup_contract_terms = &postgres_lookup_contract_terms; - plugin->lookup_contract_terms2 = &postgres_lookup_contract_terms2; - plugin->insert_contract_terms = &postgres_insert_contract_terms; - plugin->update_contract_terms = &postgres_update_contract_terms; - plugin->delete_contract_terms = &postgres_delete_contract_terms; + plugin->expire_locks + = &TMH_PG_expire_locks; + plugin->delete_order + = &TMH_PG_delete_order; + plugin->lookup_order + = &TMH_PG_lookup_order; + plugin->lookup_order_summary + = &TMH_PG_lookup_order_summary; + plugin->lookup_orders + = &TMH_PG_lookup_orders; + plugin->insert_order + = &TMH_PG_insert_order; + plugin->unlock_inventory + = &TMH_PG_unlock_inventory; + plugin->insert_order_lock + = &TMH_PG_insert_order_lock; + plugin->lookup_contract_terms + = &TMH_PG_lookup_contract_terms; + plugin->lookup_contract_terms2 + = &TMH_PG_lookup_contract_terms2; + plugin->insert_contract_terms + = &TMH_PG_insert_contract_terms; + plugin->update_contract_terms + = &TMH_PG_update_contract_terms; + plugin->delete_contract_terms + = &TMH_PG_delete_contract_terms; plugin->lookup_deposits = &postgres_lookup_deposits; plugin->insert_exchange_signkey = &postgres_insert_exchange_signkey; plugin->insert_deposit = &postgres_insert_deposit;