pg_lookup_deposits_by_contract_and_coin.c (11179B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file backenddb/pg_lookup_deposits_by_contract_and_coin.c 18 * @brief Implementation of the lookup_deposits_by_contract_and_coin function for Postgres 19 * @author Iván Ávalos 20 */ 21 #include "platform.h" 22 #include <taler/taler_error_codes.h> 23 #include <taler/taler_dbevents.h> 24 #include <taler/taler_pq_lib.h> 25 #include "pg_lookup_deposits_by_contract_and_coin.h" 26 #include "pg_helper.h" 27 28 /** 29 * Closure for #lookup_deposits_by_contract_and_coin_cb(). 30 */ 31 struct LookupDepositsByCnCContext 32 { 33 /** 34 * Function to call for each deposit. 35 */ 36 TALER_MERCHANTDB_CoinDepositCallback cb; 37 38 /** 39 * Closure for @e cb. 40 */ 41 void *cb_cls; 42 43 /** 44 * Plugin context. 45 */ 46 struct PostgresClosure *pg; 47 48 /** 49 * Total amount refunded on this coin and contract. 50 */ 51 struct TALER_Amount refund_total; 52 53 /** 54 * Transaction result. 55 */ 56 enum GNUNET_DB_QueryStatus qs; 57 }; 58 59 60 /** 61 * Function to be called with the results of a SELECT statement 62 * that has returned @a num_results results. 63 * 64 * @param cls of type `struct LookupDepositsByCnCContext *` 65 * @param result the postgres result 66 * @param num_results the number of results in @a result 67 */ 68 static void 69 lookup_refunds_cb (void *cls, 70 PGresult *result, 71 unsigned int num_results) 72 { 73 struct LookupDepositsByCnCContext *ldcc = cls; 74 75 for (unsigned int i = 0; i<num_results; i++) 76 { 77 struct TALER_Amount refund_amount; 78 struct GNUNET_PQ_ResultSpec rs[] = { 79 TALER_PQ_result_spec_amount_with_currency ("refund_amount", 80 &refund_amount), 81 GNUNET_PQ_result_spec_end 82 }; 83 84 if (GNUNET_OK != 85 GNUNET_PQ_extract_result (result, 86 rs, 87 i)) 88 { 89 GNUNET_break (0); 90 ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR; 91 return; 92 } 93 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 94 "Coin had refund of %s\n", 95 TALER_amount2s (&refund_amount)); 96 if (0 == i) 97 ldcc->refund_total = refund_amount; 98 else 99 GNUNET_assert (0 <= 100 TALER_amount_add (&ldcc->refund_total, 101 &ldcc->refund_total, 102 &refund_amount)); 103 GNUNET_PQ_cleanup_result (rs); /* technically useless here */ 104 } 105 } 106 107 108 /** 109 * Function to be called with the results of a SELECT statement 110 * that has returned @a num_results results. 111 * 112 * @param cls of type `struct LookupDepositsByCnCContext *` 113 * @param result the postgres result 114 * @param num_results the number of results in @a result 115 */ 116 static void 117 lookup_deposits_by_contract_and_coin_cb (void *cls, 118 PGresult *result, 119 unsigned int num_results) 120 { 121 struct LookupDepositsByCnCContext *ldcc = cls; 122 123 for (unsigned int i = 0; i<num_results; i++) 124 { 125 char *exchange_url; 126 struct TALER_Amount amount_with_fee; 127 struct TALER_Amount deposit_fee; 128 struct TALER_Amount refund_fee; 129 struct TALER_Amount wire_fee; 130 struct TALER_MerchantWireHashP h_wire; 131 struct GNUNET_TIME_Timestamp deposit_timestamp; 132 struct GNUNET_TIME_Timestamp refund_deadline; 133 struct TALER_ExchangeSignatureP exchange_sig; 134 struct TALER_ExchangePublicKeyP exchange_pub; 135 struct GNUNET_PQ_ResultSpec rs[] = { 136 GNUNET_PQ_result_spec_string ("exchange_url", 137 &exchange_url), 138 TALER_PQ_result_spec_amount_with_currency ("amount_with_fee", 139 &amount_with_fee), 140 TALER_PQ_result_spec_amount_with_currency ("deposit_fee", 141 &deposit_fee), 142 TALER_PQ_result_spec_amount_with_currency ("refund_fee", 143 &refund_fee), 144 TALER_PQ_result_spec_amount_with_currency ("wire_fee", 145 &wire_fee), 146 GNUNET_PQ_result_spec_auto_from_type ("h_wire", 147 &h_wire), 148 GNUNET_PQ_result_spec_timestamp ("deposit_timestamp", 149 &deposit_timestamp), 150 GNUNET_PQ_result_spec_timestamp ("refund_deadline", 151 &refund_deadline), 152 GNUNET_PQ_result_spec_auto_from_type ("exchange_sig", 153 &exchange_sig), 154 GNUNET_PQ_result_spec_auto_from_type ("exchange_pub", 155 &exchange_pub), 156 GNUNET_PQ_result_spec_end 157 }; 158 159 if (GNUNET_OK != 160 GNUNET_PQ_extract_result (result, 161 rs, 162 i)) 163 { 164 GNUNET_break (0); 165 ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR; 166 return; 167 } 168 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 169 "Coin original deposit value is %s\n", 170 TALER_amount2s (&amount_with_fee)); 171 if (TALER_amount_is_valid (&ldcc->refund_total)) 172 { 173 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 174 "Coin had total refunds of %s\n", 175 TALER_amount2s (&ldcc->refund_total)); 176 if (1 == 177 TALER_amount_cmp (&ldcc->refund_total, 178 &amount_with_fee)) 179 { 180 /* Refunds exceeded total deposit? not OK! */ 181 GNUNET_break (0); 182 ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR; 183 return; 184 } 185 if (0 == 186 TALER_amount_cmp (&ldcc->refund_total, 187 &amount_with_fee)) 188 { 189 /* refund_total == amount_with_fee; 190 in this case, the total contributed to the 191 wire transfer is zero (as are fees) */ 192 GNUNET_assert (GNUNET_OK == 193 TALER_amount_set_zero (ldcc->refund_total.currency, 194 &amount_with_fee)); 195 GNUNET_assert (GNUNET_OK == 196 TALER_amount_set_zero (ldcc->refund_total.currency, 197 &deposit_fee)); 198 199 } 200 else 201 { 202 /* Compute deposit value by subtracting refunds */ 203 GNUNET_assert (0 < 204 TALER_amount_subtract (&amount_with_fee, 205 &amount_with_fee, 206 &ldcc->refund_total)); 207 if (-1 == 208 TALER_amount_cmp (&amount_with_fee, 209 &deposit_fee)) 210 { 211 /* amount_with_fee < deposit_fee, so after refunds less than 212 the deposit fee remains; reduce deposit fee to 213 the remaining value of the coin */ 214 deposit_fee = amount_with_fee; 215 } 216 } 217 } 218 ldcc->cb (ldcc->cb_cls, 219 exchange_url, 220 &amount_with_fee, 221 &deposit_fee, 222 &refund_fee, 223 &wire_fee, 224 &h_wire, 225 deposit_timestamp, 226 refund_deadline, 227 &exchange_sig, 228 &exchange_pub); 229 GNUNET_PQ_cleanup_result (rs); 230 } 231 ldcc->qs = num_results; 232 } 233 234 235 enum GNUNET_DB_QueryStatus 236 TMH_PG_lookup_deposits_by_contract_and_coin ( 237 void *cls, 238 const char *instance_id, 239 const struct TALER_PrivateContractHashP *h_contract_terms, 240 const struct TALER_CoinSpendPublicKeyP *coin_pub, 241 TALER_MERCHANTDB_CoinDepositCallback cb, 242 void *cb_cls) 243 { 244 struct PostgresClosure *pg = cls; 245 struct GNUNET_PQ_QueryParam params[] = { 246 GNUNET_PQ_query_param_string (instance_id), 247 GNUNET_PQ_query_param_auto_from_type (h_contract_terms), 248 GNUNET_PQ_query_param_auto_from_type (coin_pub), 249 GNUNET_PQ_query_param_end 250 }; 251 struct LookupDepositsByCnCContext ldcc = { 252 .cb = cb, 253 .cb_cls = cb_cls, 254 .pg = pg 255 }; 256 enum GNUNET_DB_QueryStatus qs; 257 258 check_connection (pg); 259 /* no preflight check here, run in transaction by caller! */ 260 TALER_LOG_DEBUG ("Looking for refund of h_contract_terms %s at `%s'\n", 261 GNUNET_h2s (&h_contract_terms->hash), 262 instance_id); 263 check_connection (pg); 264 PREPARE (pg, 265 "lookup_refunds_by_coin_and_contract", 266 "SELECT" 267 " refund_amount" 268 " FROM merchant_refunds" 269 /* Join to filter by refunds that actually 270 did work, not only those we approved */ 271 " JOIN merchant_refund_proofs" 272 " USING (refund_serial)" 273 " WHERE coin_pub=$3" 274 " AND order_serial=" 275 " (SELECT order_serial" 276 " FROM merchant_contract_terms" 277 " WHERE h_contract_terms=$2" 278 " AND merchant_serial=" 279 " (SELECT merchant_serial" 280 " FROM merchant_instances" 281 " WHERE merchant_id=$1))"); 282 qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, 283 "lookup_refunds_by_coin_and_contract", 284 params, 285 &lookup_refunds_cb, 286 &ldcc); 287 if (0 > qs) 288 return qs; 289 290 PREPARE (pg, 291 "lookup_deposits_by_contract_and_coin", 292 "SELECT" 293 " mcon.exchange_url" 294 ",dep.amount_with_fee" 295 ",dep.deposit_fee" 296 ",dep.refund_fee" 297 ",mcon.wire_fee" 298 ",acc.h_wire" 299 ",mcon.deposit_timestamp" 300 ",mct.refund_deadline" 301 ",mcon.exchange_sig" 302 ",msig.exchange_pub" 303 " FROM merchant_contract_terms mct" 304 " JOIN merchant_deposit_confirmations mcon" 305 " USING (order_serial)" 306 " JOIN merchant_deposits dep" 307 " USING (deposit_confirmation_serial)" 308 " JOIN merchant_exchange_signing_keys msig" 309 " ON (mcon.signkey_serial=msig.signkey_serial)" 310 " JOIN merchant_accounts acc" 311 " USING (account_serial)" 312 " WHERE h_contract_terms=$2" 313 " AND dep.coin_pub=$3" 314 " AND mct.merchant_serial=" 315 " (SELECT merchant_serial" 316 " FROM merchant_instances" 317 " WHERE merchant_id=$1)"); 318 319 qs = GNUNET_PQ_eval_prepared_multi_select ( 320 pg->conn, 321 "lookup_deposits_by_contract_and_coin", 322 params, 323 &lookup_deposits_by_contract_and_coin_cb, 324 &ldcc); 325 if (0 >= qs) 326 return qs; 327 return ldcc.qs; 328 }