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