LCOV - code coverage report
Current view: top level - backenddb - do_increase_refund.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 74.8 % 218 163
Test Date: 2026-09-04 23:42:01 Functions: 100.0 % 8 8

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

Generated by: LCOV version 2.0-1