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

            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 2.0-1