LCOV - code coverage report
Current view: top level - backenddb - pg_increase_refund.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 144 203 70.9 %
Date: 2025-06-23 16:22:09 Functions: 6 6 100.0 %

          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             :                                         &current_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 (&current_refund,
     418             :                           &current_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 (&current_refund));
     438             : 
     439             :   /* stop immediately if we are 'done' === amount already
     440             :    * refunded.  */
     441           8 :   if (0 >= TALER_amount_cmp (ctx->refund,
     442             :                              &current_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 (&current_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             :                                &current_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 (&current_refund,
     531             :                           &current_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             :                                &current_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             : }

Generated by: LCOV version 1.16