Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022-2024 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/do_increase_refund.c
18 : * @brief Implementation of the do_increase_refund function for Postgres
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include <taler/taler_pq_lib.h>
23 : #include "merchant-database/do_increase_refund.h"
24 : #include "helper.h"
25 :
26 :
27 : /**
28 : * Information about refund limits per exchange.
29 : */
30 : struct ExchangeLimit
31 : {
32 : /**
33 : * Kept in a DLL.
34 : */
35 : struct ExchangeLimit *next;
36 :
37 : /**
38 : * Kept in a DLL.
39 : */
40 : struct ExchangeLimit *prev;
41 :
42 : /**
43 : * Exchange the limit is about.
44 : */
45 : char *exchange_url;
46 :
47 : /**
48 : * Refund amount remaining at this exchange.
49 : */
50 : struct TALER_Amount remaining_refund_limit;
51 :
52 : };
53 :
54 :
55 : /**
56 : * Closure for #process_refund_cb().
57 : */
58 : struct FindRefundContext
59 : {
60 :
61 : /**
62 : * Plugin context.
63 : */
64 : struct TALER_MERCHANTDB_PostgresContext *pg;
65 :
66 : /**
67 : * Updated to reflect total amount refunded so far.
68 : */
69 : struct TALER_Amount refunded_amount;
70 :
71 : /**
72 : * Set to the largest refund transaction ID encountered.
73 : */
74 : uint64_t max_rtransaction_id;
75 :
76 : /**
77 : * Set to true on hard errors.
78 : */
79 : bool err;
80 : };
81 :
82 :
83 : /**
84 : * Closure for #process_deposits_for_refund_cb().
85 : */
86 : struct InsertRefundContext
87 : {
88 : /**
89 : * Used to provide a connection to the db
90 : */
91 : struct TALER_MERCHANTDB_PostgresContext *pg;
92 :
93 : /**
94 : * Head of DLL of per-exchange refund limits.
95 : */
96 : struct ExchangeLimit *el_head;
97 :
98 : /**
99 : * Tail of DLL of per-exchange refund limits.
100 : */
101 : struct ExchangeLimit *el_tail;
102 :
103 : /**
104 : * Amount to which increase the refund for this contract
105 : */
106 : const struct TALER_Amount *refund;
107 :
108 : /**
109 : * Human-readable reason behind this refund
110 : */
111 : const char *reason;
112 :
113 : /**
114 : * Function to call to determine per-exchange limits.
115 : * NULL for no limits.
116 : */
117 : TALER_MERCHANTDB_OperationLimitCallback olc;
118 :
119 : /**
120 : * Closure for @e olc.
121 : */
122 : void *olc_cls;
123 :
124 : /**
125 : * Transaction status code.
126 : */
127 : enum TALER_MERCHANTDB_RefundStatus rs;
128 :
129 : /**
130 : * Did we have to cap refunds of any coin
131 : * due to legal limits?
132 : */
133 : bool legal_capped;
134 :
135 : };
136 :
137 :
138 : /**
139 : * Data extracted per coin.
140 : */
141 : struct RefundCoinData
142 : {
143 :
144 : /**
145 : * Public key of a coin.
146 : */
147 : struct TALER_CoinSpendPublicKeyP coin_pub;
148 :
149 : /**
150 : * Amount deposited for this coin.
151 : */
152 : struct TALER_Amount deposited_with_fee;
153 :
154 : /**
155 : * Amount refunded already for this coin.
156 : */
157 : struct TALER_Amount refund_amount;
158 :
159 : /**
160 : * Order serial (actually not really per-coin).
161 : */
162 : uint64_t order_serial;
163 :
164 : /**
165 : * Maximum rtransaction_id for this coin so far.
166 : */
167 : uint64_t max_rtransaction_id;
168 :
169 : /**
170 : * Exchange this coin was issued by.
171 : */
172 : char *exchange_url;
173 :
174 : };
175 :
176 :
177 : /**
178 : * Find an exchange record for the refund limit enforcement.
179 : *
180 : * @param irc refund context
181 : * @param exchange_url base URL of the exchange
182 : */
183 : static struct ExchangeLimit *
184 25 : find_exchange (struct InsertRefundContext *irc,
185 : const char *exchange_url)
186 : {
187 25 : if (NULL == irc->olc)
188 5 : return NULL; /* no limits */
189 : /* Check if entry exists, if so, do nothing */
190 20 : for (struct ExchangeLimit *el = irc->el_head;
191 20 : NULL != el;
192 0 : el = el->next)
193 10 : if (0 == strcmp (exchange_url,
194 10 : el->exchange_url))
195 10 : return el;
196 10 : return NULL;
197 : }
198 :
199 :
200 : /**
201 : * Setup an exchange for the refund limit enforcement and initialize the
202 : * original refund limit for the exchange.
203 : *
204 : * @param irc refund context
205 : * @param exchange_url base URL of the exchange
206 : * @return limiting data structure
207 : */
208 : static struct ExchangeLimit *
209 18 : setup_exchange (struct InsertRefundContext *irc,
210 : const char *exchange_url)
211 : {
212 : struct ExchangeLimit *el;
213 :
214 18 : if (NULL == irc->olc)
215 8 : return NULL; /* no limits */
216 : /* Check if entry exists, if so, do nothing */
217 10 : if (NULL !=
218 10 : (el = find_exchange (irc,
219 : exchange_url)))
220 0 : return el;
221 10 : el = GNUNET_new (struct ExchangeLimit);
222 10 : el->exchange_url = GNUNET_strdup (exchange_url);
223 : /* olc only lowers, so set to the maximum amount we care about */
224 10 : el->remaining_refund_limit = *irc->refund;
225 10 : irc->olc (irc->olc_cls,
226 : exchange_url,
227 : &el->remaining_refund_limit);
228 10 : GNUNET_CONTAINER_DLL_insert (irc->el_head,
229 : irc->el_tail,
230 : el);
231 10 : return el;
232 : }
233 :
234 :
235 : /**
236 : * Lower the remaining refund limit in @a el by @a val.
237 : *
238 : * @param[in,out] el exchange limit to lower
239 : * @param val amount to lower limit by
240 : * @return true on success, false on failure
241 : */
242 : static bool
243 33 : lower_balance (struct ExchangeLimit *el,
244 : const struct TALER_Amount *val)
245 : {
246 33 : if (NULL == el)
247 13 : return true;
248 20 : return 0 <= TALER_amount_subtract (&el->remaining_refund_limit,
249 20 : &el->remaining_refund_limit,
250 : val);
251 : }
252 :
253 :
254 : /**
255 : * Function to be called with the results of a SELECT statement
256 : * that has returned @a num_results results.
257 : *
258 : * @param cls closure, our `struct FindRefundContext`
259 : * @param result the postgres result
260 : * @param num_results the number of results in @a result
261 : */
262 : static void
263 18 : process_refund_cb (void *cls,
264 : PGresult *result,
265 : unsigned int num_results)
266 : {
267 18 : struct FindRefundContext *ictx = cls;
268 :
269 25 : for (unsigned int i = 0; i<num_results; i++)
270 : {
271 : /* Sum up existing refunds */
272 : struct TALER_Amount acc;
273 : uint64_t rtransaction_id;
274 7 : struct GNUNET_PQ_ResultSpec rs[] = {
275 7 : TALER_PQ_result_spec_amount_with_currency ("refund_amount",
276 : &acc),
277 7 : GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
278 : &rtransaction_id),
279 : GNUNET_PQ_result_spec_end
280 : };
281 :
282 7 : if (GNUNET_OK !=
283 7 : GNUNET_PQ_extract_result (result,
284 : rs,
285 : i))
286 : {
287 0 : GNUNET_break (0);
288 0 : ictx->err = true;
289 0 : return;
290 : }
291 7 : if (GNUNET_OK !=
292 7 : TALER_amount_cmp_currency (&ictx->refunded_amount,
293 : &acc))
294 : {
295 0 : GNUNET_break (0);
296 0 : ictx->err = true;
297 0 : return;
298 : }
299 7 : if (0 >
300 7 : TALER_amount_add (&ictx->refunded_amount,
301 7 : &ictx->refunded_amount,
302 : &acc))
303 : {
304 0 : GNUNET_break (0);
305 0 : ictx->err = true;
306 0 : return;
307 : }
308 7 : ictx->max_rtransaction_id = GNUNET_MAX (ictx->max_rtransaction_id,
309 : rtransaction_id);
310 7 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
311 : "Found refund of %s\n",
312 : TALER_amount2s (&acc));
313 : }
314 : }
315 :
316 :
317 : /**
318 : * Helper function to prepare statement to select refunds
319 : *
320 : * @param pg context to prepare statement in
321 : * @return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS on success
322 : */
323 : static enum GNUNET_DB_QueryStatus
324 18 : prep_select_refund (struct TALER_MERCHANTDB_PostgresContext *pg)
325 : {
326 18 : TMH_PQ_prepare_anon (pg,
327 : "SELECT"
328 : " refund_amount"
329 : ",rtransaction_id"
330 : " FROM merchant_refunds"
331 : " WHERE coin_pub=$1"
332 : " AND order_serial=$2");
333 18 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
334 : }
335 :
336 :
337 : /**
338 : * Helper function to prepare statement to insert refund
339 : *
340 : * @param pg context to prepare statement in
341 : * @return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS on success
342 : */
343 : static enum GNUNET_DB_QueryStatus
344 15 : prep_insert_refund (struct TALER_MERCHANTDB_PostgresContext *pg)
345 : {
346 : // FIXME: return 'refund_serial' from this INSERT statement for #10577
347 15 : TMH_PQ_prepare_anon (pg,
348 : "INSERT INTO merchant_refunds"
349 : "(order_serial"
350 : ",rtransaction_id"
351 : ",refund_timestamp"
352 : ",coin_pub"
353 : ",reason"
354 : ",refund_amount"
355 : ") VALUES"
356 : "($1, $2, $3, $4, $5, $6)"
357 : " ON CONFLICT (order_serial"
358 : ",coin_pub"
359 : ",rtransaction_id)"
360 : " DO NOTHING");
361 15 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
362 : }
363 :
364 :
365 : /**
366 : * Function to be called with the results of a SELECT statement
367 : * that has returned @a num_results results.
368 : *
369 : * @param cls closure, our `struct InsertRefundContext`
370 : * @param result the postgres result
371 : * @param num_results the number of results in @a result
372 : */
373 : static void
374 17 : process_deposits_for_refund_cb (void *cls,
375 : PGresult *result,
376 : unsigned int num_results)
377 17 : {
378 17 : struct InsertRefundContext *ctx = cls;
379 17 : struct TALER_MERCHANTDB_PostgresContext *pg = ctx->pg;
380 : struct TALER_Amount current_refund;
381 17 : struct RefundCoinData rcd[GNUNET_NZL (num_results)];
382 : struct GNUNET_TIME_Timestamp now;
383 :
384 17 : now = GNUNET_TIME_timestamp_get ();
385 17 : GNUNET_assert (GNUNET_OK ==
386 : TALER_amount_set_zero (ctx->refund->currency,
387 : ¤t_refund));
388 17 : memset (rcd,
389 : 0,
390 : sizeof (rcd));
391 : /* Pass 1: Collect amount of existing refunds into current_refund.
392 : * Also store existing refunded amount for each deposit in deposit_refund. */
393 35 : for (unsigned int i = 0; i<num_results; i++)
394 : {
395 18 : struct RefundCoinData *rcdi = &rcd[i];
396 18 : struct GNUNET_PQ_ResultSpec rs[] = {
397 18 : GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
398 : &rcdi->coin_pub),
399 18 : GNUNET_PQ_result_spec_uint64 ("order_serial",
400 : &rcdi->order_serial),
401 18 : GNUNET_PQ_result_spec_string ("exchange_url",
402 : &rcdi->exchange_url),
403 18 : TALER_PQ_result_spec_amount_with_currency ("amount_with_fee",
404 : &rcdi->deposited_with_fee),
405 : GNUNET_PQ_result_spec_end
406 : };
407 18 : struct FindRefundContext ictx = {
408 : .pg = pg,
409 : };
410 : struct ExchangeLimit *el;
411 :
412 18 : if (GNUNET_OK !=
413 18 : GNUNET_PQ_extract_result (result,
414 : rs,
415 : i))
416 : {
417 0 : GNUNET_break (0);
418 0 : ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
419 0 : goto cleanup;
420 : }
421 18 : el = setup_exchange (ctx,
422 18 : rcdi->exchange_url);
423 18 : if (GNUNET_YES !=
424 18 : TALER_amount_cmp_currency (&rcdi->deposited_with_fee,
425 : ctx->refund))
426 : {
427 0 : GNUNET_break_op (0);
428 0 : ctx->rs = TALER_MERCHANTDB_RS_BAD_CURRENCY;
429 0 : goto cleanup;
430 : }
431 :
432 : {
433 : enum GNUNET_DB_QueryStatus ires;
434 18 : struct GNUNET_PQ_QueryParam params[] = {
435 18 : GNUNET_PQ_query_param_auto_from_type (&rcdi->coin_pub),
436 18 : GNUNET_PQ_query_param_uint64 (&rcdi->order_serial),
437 : GNUNET_PQ_query_param_end
438 : };
439 :
440 18 : GNUNET_assert (GNUNET_OK ==
441 : TALER_amount_set_zero (
442 : ctx->refund->currency,
443 : &ictx.refunded_amount));
444 18 : ires = prep_select_refund (pg);
445 18 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != ires)
446 : {
447 0 : GNUNET_break (0);
448 0 : ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
449 0 : goto cleanup;
450 : }
451 18 : ires = GNUNET_PQ_eval_prepared_multi_select (
452 : pg->conn,
453 : "",
454 : params,
455 : &process_refund_cb,
456 : &ictx);
457 18 : if ( (ictx.err) ||
458 : (GNUNET_DB_STATUS_HARD_ERROR == ires) )
459 : {
460 0 : GNUNET_break (0);
461 0 : ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
462 0 : goto cleanup;
463 : }
464 18 : if (GNUNET_DB_STATUS_SOFT_ERROR == ires)
465 : {
466 0 : ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
467 0 : goto cleanup;
468 : }
469 : }
470 18 : if (0 >
471 18 : TALER_amount_add (¤t_refund,
472 : ¤t_refund,
473 : &ictx.refunded_amount))
474 : {
475 0 : GNUNET_break (0);
476 0 : ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
477 0 : goto cleanup;
478 : }
479 18 : rcdi->refund_amount = ictx.refunded_amount;
480 18 : rcdi->max_rtransaction_id = ictx.max_rtransaction_id;
481 18 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
482 : "Existing refund for coin %s is %s\n",
483 : TALER_B2S (&rcdi->coin_pub),
484 : TALER_amount2s (&ictx.refunded_amount));
485 18 : GNUNET_break (lower_balance (el,
486 : &ictx.refunded_amount));
487 : } /* end for all deposited coins */
488 :
489 17 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
490 : "Total existing refund is %s\n",
491 : TALER_amount2s (¤t_refund));
492 :
493 : /* stop immediately if we are 'done' === amount already
494 : * refunded. */
495 17 : if (0 >= TALER_amount_cmp (ctx->refund,
496 : ¤t_refund))
497 : {
498 1 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
499 : "Existing refund of %s at or above requested refund. Finished early.\n",
500 : TALER_amount2s (¤t_refund));
501 1 : ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
502 1 : goto cleanup;
503 : }
504 :
505 : /* Phase 2: Try to increase current refund until it matches desired refund */
506 19 : for (unsigned int i = 0; i<num_results; i++)
507 : {
508 16 : struct RefundCoinData *rcdi = &rcd[i];
509 : const struct TALER_Amount *increment;
510 : struct TALER_Amount left;
511 : struct TALER_Amount remaining_refund;
512 : struct ExchangeLimit *el;
513 :
514 : /* How much of the coin is left after the existing refunds? */
515 16 : if (0 >
516 16 : TALER_amount_subtract (&left,
517 16 : &rcdi->deposited_with_fee,
518 16 : &rcdi->refund_amount))
519 : {
520 0 : GNUNET_break (0);
521 0 : ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
522 13 : goto cleanup;
523 : }
524 :
525 16 : if (TALER_amount_is_zero (&left))
526 : {
527 : /* coin was fully refunded, move to next coin */
528 1 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
529 : "Coin %s fully refunded, moving to next coin\n",
530 : TALER_B2S (&rcdi->coin_pub));
531 1 : continue;
532 : }
533 15 : el = find_exchange (ctx,
534 15 : rcdi->exchange_url);
535 25 : if ( (NULL != el) &&
536 10 : (TALER_amount_is_zero (&el->remaining_refund_limit)) )
537 : {
538 : /* legal limit reached, move to next coin */
539 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
540 : "Exchange %s legal limit reached, moving to next coin\n",
541 : rcdi->exchange_url);
542 0 : continue;
543 : }
544 :
545 15 : rcdi->max_rtransaction_id++;
546 : /* How much of the refund is still to be paid back? */
547 15 : if (0 >
548 15 : TALER_amount_subtract (&remaining_refund,
549 : ctx->refund,
550 : ¤t_refund))
551 : {
552 0 : GNUNET_break (0);
553 0 : ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
554 0 : goto cleanup;
555 : }
556 : /* cap by legal limit */
557 15 : if (NULL != el)
558 : {
559 : struct TALER_Amount new_limit;
560 :
561 10 : TALER_amount_min (&new_limit,
562 : &remaining_refund,
563 10 : &el->remaining_refund_limit);
564 10 : if (0 != TALER_amount_cmp (&new_limit,
565 : &remaining_refund))
566 : {
567 0 : remaining_refund = new_limit;
568 0 : ctx->legal_capped = true;
569 : }
570 : }
571 : /* By how much will we increase the refund for this coin? */
572 15 : if (0 >= TALER_amount_cmp (&remaining_refund,
573 : &left))
574 : {
575 : /* remaining_refund <= left */
576 13 : increment = &remaining_refund;
577 : }
578 : else
579 : {
580 2 : increment = &left;
581 : }
582 :
583 15 : if (0 >
584 15 : TALER_amount_add (¤t_refund,
585 : ¤t_refund,
586 : increment))
587 : {
588 0 : GNUNET_break (0);
589 0 : ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
590 0 : goto cleanup;
591 : }
592 15 : GNUNET_break (lower_balance (el,
593 : increment));
594 : /* actually run the refund */
595 15 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
596 : "Coin %s deposit amount is %s\n",
597 : TALER_B2S (&rcdi->coin_pub),
598 : TALER_amount2s (&rcdi->deposited_with_fee));
599 15 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
600 : "Coin %s refund will be incremented by %s\n",
601 : TALER_B2S (&rcdi->coin_pub),
602 : TALER_amount2s (increment));
603 : {
604 : enum GNUNET_DB_QueryStatus qs;
605 15 : struct GNUNET_PQ_QueryParam params[] = {
606 15 : GNUNET_PQ_query_param_uint64 (&rcdi->order_serial),
607 15 : GNUNET_PQ_query_param_uint64 (&rcdi->max_rtransaction_id), /* already inc'ed */
608 15 : GNUNET_PQ_query_param_timestamp (&now),
609 15 : GNUNET_PQ_query_param_auto_from_type (&rcdi->coin_pub),
610 15 : GNUNET_PQ_query_param_string (ctx->reason),
611 15 : TALER_PQ_query_param_amount_with_currency (pg->conn,
612 : increment),
613 : GNUNET_PQ_query_param_end
614 : };
615 :
616 15 : qs = prep_insert_refund (pg);
617 15 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
618 : {
619 0 : GNUNET_break (0);
620 0 : ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
621 1 : goto cleanup;
622 : }
623 15 : qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
624 : "",
625 : params);
626 15 : switch (qs)
627 : {
628 0 : case GNUNET_DB_STATUS_HARD_ERROR:
629 0 : GNUNET_break (0);
630 0 : ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
631 0 : goto cleanup;
632 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
633 0 : ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
634 0 : goto cleanup;
635 1 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
636 : /* Another transaction concurrently created a refund with the
637 : very rtransaction_id we picked (we saw its predecessors, but
638 : not it). Without the ON CONFLICT above this would raise
639 : 23505, which maps to 0 == #TALER_MERCHANTDB_RS_NO_SUCH_ORDER
640 : (a bogus 404 for an order we just read) and additionally
641 : poisons the enclosing transaction. Report a serialization
642 : failure instead, so that the caller retries and then observes
643 : the concurrent refund. */
644 1 : ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
645 1 : goto cleanup;
646 14 : default:
647 14 : ctx->rs = (enum TALER_MERCHANTDB_RefundStatus) qs;
648 14 : break;
649 : }
650 : }
651 :
652 : /* stop immediately if we are done */
653 14 : if (0 == TALER_amount_cmp (ctx->refund,
654 : ¤t_refund))
655 : {
656 12 : ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
657 12 : goto cleanup;
658 : }
659 : }
660 :
661 3 : if (ctx->legal_capped)
662 : {
663 0 : ctx->rs = TALER_MERCHANTDB_RS_LEGAL_FAILURE;
664 0 : goto cleanup;
665 : }
666 : /**
667 : * We end up here if not all of the refund has been covered.
668 : * Although this should be checked as the business should never
669 : * issue a refund bigger than the contract's actual price, we cannot
670 : * rely upon the frontend being correct.
671 : */
672 3 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
673 : "The refund of %s is bigger than the order's value\n",
674 : TALER_amount2s (ctx->refund));
675 3 : ctx->rs = TALER_MERCHANTDB_RS_TOO_HIGH;
676 17 : cleanup:
677 35 : for (unsigned int i = 0; i<num_results; i++)
678 18 : GNUNET_free (rcd[i].exchange_url);
679 17 : }
680 :
681 :
682 : enum TALER_MERCHANTDB_RefundStatus
683 17 : TALER_MERCHANTDB_do_increase_refund (
684 : struct TALER_MERCHANTDB_PostgresContext *pg,
685 : const char *instance_id,
686 : const char *order_id,
687 : const struct TALER_Amount *refund,
688 : TALER_MERCHANTDB_OperationLimitCallback olc,
689 : void *olc_cls,
690 : const char *reason)
691 : {
692 : enum GNUNET_DB_QueryStatus qs;
693 17 : struct GNUNET_PQ_QueryParam params[] = {
694 17 : GNUNET_PQ_query_param_string (order_id),
695 : GNUNET_PQ_query_param_end
696 : };
697 17 : struct InsertRefundContext ctx = {
698 : .pg = pg,
699 : .refund = refund,
700 : .olc = olc,
701 : .olc_cls = olc_cls,
702 : .reason = reason,
703 : };
704 :
705 17 : GNUNET_assert (NULL != pg->current_merchant_id);
706 17 : GNUNET_assert (0 == strcmp (instance_id,
707 : pg->current_merchant_id));
708 17 : TMH_PQ_prepare_anon (pg,
709 : "SELECT"
710 : " dep.coin_pub"
711 : ",dco.order_serial"
712 : ",dep.amount_with_fee"
713 : ",dco.exchange_url"
714 : " FROM merchant_deposits dep"
715 : " JOIN merchant_deposit_confirmations dco"
716 : " USING (deposit_confirmation_serial)"
717 : " WHERE order_serial="
718 : " (SELECT order_serial"
719 : " FROM merchant_contract_terms"
720 : " WHERE order_id=$1"
721 : " AND paid)");
722 17 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
723 : "Asked to refund %s on order %s\n",
724 : TALER_amount2s (refund),
725 : order_id);
726 17 : qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
727 : "",
728 : params,
729 : &process_deposits_for_refund_cb,
730 : &ctx);
731 : {
732 : struct ExchangeLimit *el;
733 :
734 27 : while (NULL != (el = ctx.el_head))
735 : {
736 10 : GNUNET_CONTAINER_DLL_remove (ctx.el_head,
737 : ctx.el_tail,
738 : el);
739 10 : GNUNET_free (el->exchange_url);
740 10 : GNUNET_free (el);
741 : }
742 : }
743 17 : switch (qs)
744 : {
745 2 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
746 : /* never paid, means we clearly cannot refund anything */
747 2 : return TALER_MERCHANTDB_RS_NO_SUCH_ORDER;
748 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
749 0 : return TALER_MERCHANTDB_RS_SOFT_ERROR;
750 0 : case GNUNET_DB_STATUS_HARD_ERROR:
751 0 : return TALER_MERCHANTDB_RS_HARD_ERROR;
752 15 : default:
753 : /* Got one or more deposits */
754 15 : return ctx.rs;
755 : }
756 : }
|