lookup_deposits_by_contract_and_coin.c (11101B)
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_pq_lib.h> 23 #include "merchant-database/lookup_deposits_by_contract_and_coin.h" 24 #include "helper.h" 25 26 /** 27 * Closure for #lookup_deposits_by_contract_and_coin_cb(). 28 */ 29 struct LookupDepositsByCnCContext 30 { 31 /** 32 * Function to call for each deposit. 33 */ 34 TALER_MERCHANTDB_CoinDepositCallback cb; 35 36 /** 37 * Closure for @e cb. 38 */ 39 void *cb_cls; 40 41 /** 42 * Plugin context. 43 */ 44 struct TALER_MERCHANTDB_PostgresContext *pg; 45 46 /** 47 * Total amount refunded on this coin and contract. 48 */ 49 struct TALER_Amount refund_total; 50 51 /** 52 * Transaction result. 53 */ 54 enum GNUNET_DB_QueryStatus qs; 55 }; 56 57 58 /** 59 * Function to be called with the results of a SELECT statement 60 * that has returned @a num_results results. 61 * 62 * @param cls of type `struct LookupDepositsByCnCContext *` 63 * @param result the postgres result 64 * @param num_results the number of results in @a result 65 */ 66 static void 67 lookup_refunds_cb (void *cls, 68 PGresult *result, 69 unsigned int num_results) 70 { 71 struct LookupDepositsByCnCContext *ldcc = cls; 72 73 for (unsigned int i = 0; i<num_results; i++) 74 { 75 struct TALER_Amount refund_amount; 76 struct GNUNET_PQ_ResultSpec rs[] = { 77 TALER_PQ_result_spec_amount_with_currency ("refund_amount", 78 &refund_amount), 79 GNUNET_PQ_result_spec_end 80 }; 81 82 if (GNUNET_OK != 83 GNUNET_PQ_extract_result (result, 84 rs, 85 i)) 86 { 87 GNUNET_break (0); 88 ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR; 89 return; 90 } 91 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 92 "Coin had refund of %s\n", 93 TALER_amount2s (&refund_amount)); 94 if (0 == i) 95 ldcc->refund_total = refund_amount; 96 else 97 GNUNET_assert (0 <= 98 TALER_amount_add (&ldcc->refund_total, 99 &ldcc->refund_total, 100 &refund_amount)); 101 GNUNET_PQ_cleanup_result (rs); /* technically useless here */ 102 } 103 } 104 105 106 /** 107 * Function to be called with the results of a SELECT statement 108 * that has returned @a num_results results. 109 * 110 * @param cls of type `struct LookupDepositsByCnCContext *` 111 * @param result the postgres result 112 * @param num_results the number of results in @a result 113 */ 114 static void 115 lookup_deposits_by_contract_and_coin_cb ( 116 void *cls, 117 PGresult *result, 118 unsigned int num_results) 119 { 120 struct LookupDepositsByCnCContext *ldcc = cls; 121 122 for (unsigned int i = 0; i<num_results; i++) 123 { 124 char *exchange_url; 125 struct TALER_Amount amount_with_fee; 126 struct TALER_Amount deposit_fee; 127 struct TALER_Amount refund_fee; 128 struct TALER_Amount wire_fee; 129 struct TALER_MerchantWireHashP h_wire; 130 struct GNUNET_TIME_Timestamp deposit_timestamp; 131 struct GNUNET_TIME_Timestamp refund_deadline; 132 struct TALER_ExchangeSignatureP exchange_sig; 133 struct TALER_ExchangePublicKeyP exchange_pub; 134 struct GNUNET_PQ_ResultSpec rs[] = { 135 GNUNET_PQ_result_spec_string ("exchange_url", 136 &exchange_url), 137 TALER_PQ_result_spec_amount_with_currency ("amount_with_fee", 138 &amount_with_fee), 139 TALER_PQ_result_spec_amount_with_currency ("deposit_fee", 140 &deposit_fee), 141 TALER_PQ_result_spec_amount_with_currency ("refund_fee", 142 &refund_fee), 143 TALER_PQ_result_spec_amount_with_currency ("wire_fee", 144 &wire_fee), 145 GNUNET_PQ_result_spec_auto_from_type ("h_wire", 146 &h_wire), 147 GNUNET_PQ_result_spec_timestamp ("deposit_timestamp", 148 &deposit_timestamp), 149 GNUNET_PQ_result_spec_timestamp ("refund_deadline", 150 &refund_deadline), 151 GNUNET_PQ_result_spec_auto_from_type ("exchange_sig", 152 &exchange_sig), 153 GNUNET_PQ_result_spec_auto_from_type ("exchange_pub", 154 &exchange_pub), 155 GNUNET_PQ_result_spec_end 156 }; 157 158 if (GNUNET_OK != 159 GNUNET_PQ_extract_result (result, 160 rs, 161 i)) 162 { 163 GNUNET_break (0); 164 ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR; 165 return; 166 } 167 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 168 "Coin original deposit value is %s\n", 169 TALER_amount2s (&amount_with_fee)); 170 if (TALER_amount_is_valid (&ldcc->refund_total)) 171 { 172 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 173 "Coin had total refunds of %s\n", 174 TALER_amount2s (&ldcc->refund_total)); 175 if (1 == 176 TALER_amount_cmp (&ldcc->refund_total, 177 &amount_with_fee)) 178 { 179 /* Refunds exceeded total deposit? not OK! */ 180 GNUNET_break (0); 181 ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR; 182 return; 183 } 184 if (0 == 185 TALER_amount_cmp (&ldcc->refund_total, 186 &amount_with_fee)) 187 { 188 /* refund_total == amount_with_fee; 189 in this case, the total contributed to the 190 wire transfer is zero (as are fees) */ 191 GNUNET_assert (GNUNET_OK == 192 TALER_amount_set_zero (ldcc->refund_total.currency, 193 &amount_with_fee)); 194 GNUNET_assert (GNUNET_OK == 195 TALER_amount_set_zero (ldcc->refund_total.currency, 196 &deposit_fee)); 197 198 } 199 else 200 { 201 /* Compute deposit value by subtracting refunds */ 202 GNUNET_assert (0 < 203 TALER_amount_subtract (&amount_with_fee, 204 &amount_with_fee, 205 &ldcc->refund_total)); 206 if (-1 == 207 TALER_amount_cmp (&amount_with_fee, 208 &deposit_fee)) 209 { 210 /* amount_with_fee < deposit_fee, so after refunds less than 211 the deposit fee remains; reduce deposit fee to 212 the remaining value of the coin */ 213 deposit_fee = amount_with_fee; 214 } 215 } 216 } 217 ldcc->cb (ldcc->cb_cls, 218 exchange_url, 219 &amount_with_fee, 220 &deposit_fee, 221 &refund_fee, 222 &wire_fee, 223 &h_wire, 224 deposit_timestamp, 225 refund_deadline, 226 &exchange_sig, 227 &exchange_pub); 228 GNUNET_PQ_cleanup_result (rs); 229 } 230 ldcc->qs = num_results; 231 } 232 233 234 enum GNUNET_DB_QueryStatus 235 TALER_MERCHANTDB_lookup_deposits_by_contract_and_coin ( 236 struct TALER_MERCHANTDB_PostgresContext *pg, 237 const char *instance_id, 238 const struct TALER_PrivateContractHashP * 239 h_contract_terms, 240 const struct TALER_CoinSpendPublicKeyP * 241 coin_pub, 242 TALER_MERCHANTDB_CoinDepositCallback cb, 243 void *cb_cls) 244 { 245 struct GNUNET_PQ_QueryParam params[] = { 246 GNUNET_PQ_query_param_auto_from_type (h_contract_terms), 247 GNUNET_PQ_query_param_auto_from_type (coin_pub), 248 GNUNET_PQ_query_param_end 249 }; 250 struct LookupDepositsByCnCContext ldcc = { 251 .cb = cb, 252 .cb_cls = cb_cls, 253 .pg = pg 254 }; 255 enum GNUNET_DB_QueryStatus qs; 256 257 GNUNET_assert (NULL != pg->current_merchant_id); 258 GNUNET_assert (0 == strcmp (instance_id, 259 pg->current_merchant_id)); 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 TMH_PQ_prepare_anon (pg, 267 "SELECT" 268 " refund_amount" 269 " FROM merchant_refunds" 270 /* Join to filter by refunds that actually 271 did work, not only those we approved */ 272 " JOIN merchant_refund_proofs" 273 " USING (refund_serial)" 274 " WHERE coin_pub=$2" 275 " AND order_serial=" 276 " (SELECT order_serial" 277 " FROM merchant_contract_terms" 278 " WHERE h_contract_terms=$1)"); 279 qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, 280 "", 281 params, 282 &lookup_refunds_cb, 283 &ldcc); 284 if (0 > qs) 285 return qs; 286 287 TMH_PQ_prepare_anon (pg, 288 "SELECT" 289 " mcon.exchange_url" 290 ",dep.amount_with_fee" 291 ",dep.deposit_fee" 292 ",dep.refund_fee" 293 ",mcon.wire_fee" 294 ",acc.h_wire" 295 ",mcon.deposit_timestamp" 296 ",mct.refund_deadline" 297 ",mcon.exchange_sig" 298 ",msig.exchange_pub" 299 " FROM merchant_contract_terms mct" 300 " JOIN merchant_deposit_confirmations mcon" 301 " USING (order_serial)" 302 " JOIN merchant_deposits dep" 303 " USING (deposit_confirmation_serial)" 304 " JOIN merchant.merchant_exchange_signing_keys msig" 305 " ON (mcon.signkey_serial=msig.signkey_serial)" 306 " JOIN merchant_accounts acc" 307 " USING (account_serial)" 308 " WHERE h_contract_terms=$1" 309 " AND dep.coin_pub=$2"); 310 311 qs = GNUNET_PQ_eval_prepared_multi_select ( 312 pg->conn, 313 "", 314 params, 315 &lookup_deposits_by_contract_and_coin_cb, 316 &ldcc); 317 if (0 >= qs) 318 return qs; 319 return ldcc.qs; 320 }