lookup_deposits_by_contract_and_coin.c (11148B)
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 src/backenddb/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 "merchant-database/lookup_deposits_by_contract_and_coin.h" 26 #include "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 TALER_MERCHANTDB_PostgresContext *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 ( 118 void *cls, 119 PGresult *result, 120 unsigned int num_results) 121 { 122 struct LookupDepositsByCnCContext *ldcc = cls; 123 124 for (unsigned int i = 0; i<num_results; i++) 125 { 126 char *exchange_url; 127 struct TALER_Amount amount_with_fee; 128 struct TALER_Amount deposit_fee; 129 struct TALER_Amount refund_fee; 130 struct TALER_Amount wire_fee; 131 struct TALER_MerchantWireHashP h_wire; 132 struct GNUNET_TIME_Timestamp deposit_timestamp; 133 struct GNUNET_TIME_Timestamp refund_deadline; 134 struct TALER_ExchangeSignatureP exchange_sig; 135 struct TALER_ExchangePublicKeyP exchange_pub; 136 struct GNUNET_PQ_ResultSpec rs[] = { 137 GNUNET_PQ_result_spec_string ("exchange_url", 138 &exchange_url), 139 TALER_PQ_result_spec_amount_with_currency ("amount_with_fee", 140 &amount_with_fee), 141 TALER_PQ_result_spec_amount_with_currency ("deposit_fee", 142 &deposit_fee), 143 TALER_PQ_result_spec_amount_with_currency ("refund_fee", 144 &refund_fee), 145 TALER_PQ_result_spec_amount_with_currency ("wire_fee", 146 &wire_fee), 147 GNUNET_PQ_result_spec_auto_from_type ("h_wire", 148 &h_wire), 149 GNUNET_PQ_result_spec_timestamp ("deposit_timestamp", 150 &deposit_timestamp), 151 GNUNET_PQ_result_spec_timestamp ("refund_deadline", 152 &refund_deadline), 153 GNUNET_PQ_result_spec_auto_from_type ("exchange_sig", 154 &exchange_sig), 155 GNUNET_PQ_result_spec_auto_from_type ("exchange_pub", 156 &exchange_pub), 157 GNUNET_PQ_result_spec_end 158 }; 159 160 if (GNUNET_OK != 161 GNUNET_PQ_extract_result (result, 162 rs, 163 i)) 164 { 165 GNUNET_break (0); 166 ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR; 167 return; 168 } 169 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 170 "Coin original deposit value is %s\n", 171 TALER_amount2s (&amount_with_fee)); 172 if (TALER_amount_is_valid (&ldcc->refund_total)) 173 { 174 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 175 "Coin had total refunds of %s\n", 176 TALER_amount2s (&ldcc->refund_total)); 177 if (1 == 178 TALER_amount_cmp (&ldcc->refund_total, 179 &amount_with_fee)) 180 { 181 /* Refunds exceeded total deposit? not OK! */ 182 GNUNET_break (0); 183 ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR; 184 return; 185 } 186 if (0 == 187 TALER_amount_cmp (&ldcc->refund_total, 188 &amount_with_fee)) 189 { 190 /* refund_total == amount_with_fee; 191 in this case, the total contributed to the 192 wire transfer is zero (as are fees) */ 193 GNUNET_assert (GNUNET_OK == 194 TALER_amount_set_zero (ldcc->refund_total.currency, 195 &amount_with_fee)); 196 GNUNET_assert (GNUNET_OK == 197 TALER_amount_set_zero (ldcc->refund_total.currency, 198 &deposit_fee)); 199 200 } 201 else 202 { 203 /* Compute deposit value by subtracting refunds */ 204 GNUNET_assert (0 < 205 TALER_amount_subtract (&amount_with_fee, 206 &amount_with_fee, 207 &ldcc->refund_total)); 208 if (-1 == 209 TALER_amount_cmp (&amount_with_fee, 210 &deposit_fee)) 211 { 212 /* amount_with_fee < deposit_fee, so after refunds less than 213 the deposit fee remains; reduce deposit fee to 214 the remaining value of the coin */ 215 deposit_fee = amount_with_fee; 216 } 217 } 218 } 219 ldcc->cb (ldcc->cb_cls, 220 exchange_url, 221 &amount_with_fee, 222 &deposit_fee, 223 &refund_fee, 224 &wire_fee, 225 &h_wire, 226 deposit_timestamp, 227 refund_deadline, 228 &exchange_sig, 229 &exchange_pub); 230 GNUNET_PQ_cleanup_result (rs); 231 } 232 ldcc->qs = num_results; 233 } 234 235 236 enum GNUNET_DB_QueryStatus 237 TALER_MERCHANTDB_lookup_deposits_by_contract_and_coin ( 238 struct TALER_MERCHANTDB_PostgresContext *pg, 239 const char *instance_id, 240 const struct TALER_PrivateContractHashP * 241 h_contract_terms, 242 const struct TALER_CoinSpendPublicKeyP * 243 coin_pub, 244 TALER_MERCHANTDB_CoinDepositCallback cb, 245 void *cb_cls) 246 { 247 struct GNUNET_PQ_QueryParam params[] = { 248 GNUNET_PQ_query_param_string (instance_id), 249 GNUNET_PQ_query_param_auto_from_type (h_contract_terms), 250 GNUNET_PQ_query_param_auto_from_type (coin_pub), 251 GNUNET_PQ_query_param_end 252 }; 253 struct LookupDepositsByCnCContext ldcc = { 254 .cb = cb, 255 .cb_cls = cb_cls, 256 .pg = pg 257 }; 258 enum GNUNET_DB_QueryStatus qs; 259 260 check_connection (pg); 261 /* no preflight check here, run in transaction by caller! */ 262 TALER_LOG_DEBUG ("Looking for refund of h_contract_terms %s at `%s'\n", 263 GNUNET_h2s (&h_contract_terms->hash), 264 instance_id); 265 check_connection (pg); 266 PREPARE (pg, 267 "lookup_refunds_by_coin_and_contract", 268 "SELECT" 269 " refund_amount" 270 " FROM merchant_refunds" 271 /* Join to filter by refunds that actually 272 did work, not only those we approved */ 273 " JOIN merchant_refund_proofs" 274 " USING (refund_serial)" 275 " WHERE coin_pub=$3" 276 " AND order_serial=" 277 " (SELECT order_serial" 278 " FROM merchant_contract_terms" 279 " WHERE h_contract_terms=$2" 280 " AND merchant_serial=" 281 " (SELECT merchant_serial" 282 " FROM merchant_instances" 283 " WHERE merchant_id=$1))"); 284 qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, 285 "lookup_refunds_by_coin_and_contract", 286 params, 287 &lookup_refunds_cb, 288 &ldcc); 289 if (0 > qs) 290 return qs; 291 292 PREPARE (pg, 293 "lookup_deposits_by_contract_and_coin", 294 "SELECT" 295 " mcon.exchange_url" 296 ",dep.amount_with_fee" 297 ",dep.deposit_fee" 298 ",dep.refund_fee" 299 ",mcon.wire_fee" 300 ",acc.h_wire" 301 ",mcon.deposit_timestamp" 302 ",mct.refund_deadline" 303 ",mcon.exchange_sig" 304 ",msig.exchange_pub" 305 " FROM merchant_contract_terms mct" 306 " JOIN merchant_deposit_confirmations mcon" 307 " USING (order_serial)" 308 " JOIN merchant_deposits dep" 309 " USING (deposit_confirmation_serial)" 310 " JOIN merchant_exchange_signing_keys msig" 311 " ON (mcon.signkey_serial=msig.signkey_serial)" 312 " JOIN merchant_accounts acc" 313 " USING (account_serial)" 314 " WHERE h_contract_terms=$2" 315 " AND dep.coin_pub=$3" 316 " AND mct.merchant_serial=" 317 " (SELECT merchant_serial" 318 " FROM merchant_instances" 319 " WHERE merchant_id=$1)"); 320 321 qs = GNUNET_PQ_eval_prepared_multi_select ( 322 pg->conn, 323 "lookup_deposits_by_contract_and_coin", 324 params, 325 &lookup_deposits_by_contract_and_coin_cb, 326 &ldcc); 327 if (0 >= qs) 328 return qs; 329 return ldcc.qs; 330 }