merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

increase_refund.c (21219B)


      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/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_pq_lib.h>
     23 #include "merchant-database/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 find_exchange (struct InsertRefundContext *irc,
    185                const char *exchange_url)
    186 {
    187   if (NULL == irc->olc)
    188     return NULL; /* no limits */
    189   /* Check if entry exists, if so, do nothing */
    190   for (struct ExchangeLimit *el = irc->el_head;
    191        NULL != el;
    192        el = el->next)
    193     if (0 == strcmp (exchange_url,
    194                      el->exchange_url))
    195       return el;
    196   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 setup_exchange (struct InsertRefundContext *irc,
    210                 const char *exchange_url)
    211 {
    212   struct ExchangeLimit *el;
    213 
    214   if (NULL == irc->olc)
    215     return NULL; /* no limits */
    216   /* Check if entry exists, if so, do nothing */
    217   if (NULL !=
    218       (el = find_exchange (irc,
    219                            exchange_url)))
    220     return el;
    221   el = GNUNET_new (struct ExchangeLimit);
    222   el->exchange_url = GNUNET_strdup (exchange_url);
    223   /* olc only lowers, so set to the maximum amount we care about */
    224   el->remaining_refund_limit = *irc->refund;
    225   irc->olc (irc->olc_cls,
    226             exchange_url,
    227             &el->remaining_refund_limit);
    228   GNUNET_CONTAINER_DLL_insert (irc->el_head,
    229                                irc->el_tail,
    230                                el);
    231   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 lower_balance (struct ExchangeLimit *el,
    244                const struct TALER_Amount *val)
    245 {
    246   if (NULL == el)
    247     return true;
    248   return 0 <= TALER_amount_subtract (&el->remaining_refund_limit,
    249                                      &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 process_refund_cb (void *cls,
    264                    PGresult *result,
    265                    unsigned int num_results)
    266 {
    267   struct FindRefundContext *ictx = cls;
    268 
    269   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     struct GNUNET_PQ_ResultSpec rs[] = {
    275       TALER_PQ_result_spec_amount_with_currency ("refund_amount",
    276                                                  &acc),
    277       GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
    278                                     &rtransaction_id),
    279       GNUNET_PQ_result_spec_end
    280     };
    281 
    282     if (GNUNET_OK !=
    283         GNUNET_PQ_extract_result (result,
    284                                   rs,
    285                                   i))
    286     {
    287       GNUNET_break (0);
    288       ictx->err = true;
    289       return;
    290     }
    291     if (GNUNET_OK !=
    292         TALER_amount_cmp_currency (&ictx->refunded_amount,
    293                                    &acc))
    294     {
    295       GNUNET_break (0);
    296       ictx->err = true;
    297       return;
    298     }
    299     if (0 >
    300         TALER_amount_add (&ictx->refunded_amount,
    301                           &ictx->refunded_amount,
    302                           &acc))
    303     {
    304       GNUNET_break (0);
    305       ictx->err = true;
    306       return;
    307     }
    308     ictx->max_rtransaction_id = GNUNET_MAX (ictx->max_rtransaction_id,
    309                                             rtransaction_id);
    310     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 prep_select_refund (struct TALER_MERCHANTDB_PostgresContext *pg)
    325 {
    326   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   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 prep_insert_refund (struct TALER_MERCHANTDB_PostgresContext *pg)
    345 {
    346   // FIXME: return 'refund_serial' from this INSERT statement for #10577
    347   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   return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    358 }
    359 
    360 
    361 /**
    362  * Function to be called with the results of a SELECT statement
    363  * that has returned @a num_results results.
    364  *
    365  * @param cls closure, our `struct InsertRefundContext`
    366  * @param result the postgres result
    367  * @param num_results the number of results in @a result
    368  */
    369 static void
    370 process_deposits_for_refund_cb (void *cls,
    371                                 PGresult *result,
    372                                 unsigned int num_results)
    373 {
    374   struct InsertRefundContext *ctx = cls;
    375   struct TALER_MERCHANTDB_PostgresContext *pg = ctx->pg;
    376   struct TALER_Amount current_refund;
    377   struct RefundCoinData rcd[GNUNET_NZL (num_results)];
    378   struct GNUNET_TIME_Timestamp now;
    379 
    380   now = GNUNET_TIME_timestamp_get ();
    381   GNUNET_assert (GNUNET_OK ==
    382                  TALER_amount_set_zero (ctx->refund->currency,
    383                                         &current_refund));
    384   memset (rcd,
    385           0,
    386           sizeof (rcd));
    387   /* Pass 1:  Collect amount of existing refunds into current_refund.
    388    * Also store existing refunded amount for each deposit in deposit_refund. */
    389   for (unsigned int i = 0; i<num_results; i++)
    390   {
    391     struct RefundCoinData *rcdi = &rcd[i];
    392     struct GNUNET_PQ_ResultSpec rs[] = {
    393       GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
    394                                             &rcdi->coin_pub),
    395       GNUNET_PQ_result_spec_uint64 ("order_serial",
    396                                     &rcdi->order_serial),
    397       GNUNET_PQ_result_spec_string ("exchange_url",
    398                                     &rcdi->exchange_url),
    399       TALER_PQ_result_spec_amount_with_currency ("amount_with_fee",
    400                                                  &rcdi->deposited_with_fee),
    401       GNUNET_PQ_result_spec_end
    402     };
    403     struct FindRefundContext ictx = {
    404       .pg = pg,
    405     };
    406     struct ExchangeLimit *el;
    407 
    408     if (GNUNET_OK !=
    409         GNUNET_PQ_extract_result (result,
    410                                   rs,
    411                                   i))
    412     {
    413       GNUNET_break (0);
    414       ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    415       goto cleanup;
    416     }
    417     el = setup_exchange (ctx,
    418                          rcdi->exchange_url);
    419     if (0 != strcmp (rcdi->deposited_with_fee.currency,
    420                      ctx->refund->currency))
    421     {
    422       GNUNET_break_op (0);
    423       ctx->rs = TALER_MERCHANTDB_RS_BAD_CURRENCY;
    424       goto cleanup;
    425     }
    426 
    427     {
    428       enum GNUNET_DB_QueryStatus ires;
    429       struct GNUNET_PQ_QueryParam params[] = {
    430         GNUNET_PQ_query_param_auto_from_type (&rcdi->coin_pub),
    431         GNUNET_PQ_query_param_uint64 (&rcdi->order_serial),
    432         GNUNET_PQ_query_param_end
    433       };
    434 
    435       GNUNET_assert (GNUNET_OK ==
    436                      TALER_amount_set_zero (
    437                        ctx->refund->currency,
    438                        &ictx.refunded_amount));
    439       ires = prep_select_refund (pg);
    440       if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != ires)
    441       {
    442         GNUNET_break (0);
    443         ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    444         goto cleanup;
    445       }
    446       ires = GNUNET_PQ_eval_prepared_multi_select (
    447         pg->conn,
    448         "",
    449         params,
    450         &process_refund_cb,
    451         &ictx);
    452       if ( (ictx.err) ||
    453            (GNUNET_DB_STATUS_HARD_ERROR == ires) )
    454       {
    455         GNUNET_break (0);
    456         ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    457         goto cleanup;
    458       }
    459       if (GNUNET_DB_STATUS_SOFT_ERROR == ires)
    460       {
    461         ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
    462         goto cleanup;
    463       }
    464     }
    465     if (0 >
    466         TALER_amount_add (&current_refund,
    467                           &current_refund,
    468                           &ictx.refunded_amount))
    469     {
    470       GNUNET_break (0);
    471       ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    472       goto cleanup;
    473     }
    474     rcdi->refund_amount = ictx.refunded_amount;
    475     rcdi->max_rtransaction_id = ictx.max_rtransaction_id;
    476     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    477                 "Existing refund for coin %s is %s\n",
    478                 TALER_B2S (&rcdi->coin_pub),
    479                 TALER_amount2s (&ictx.refunded_amount));
    480     GNUNET_break (lower_balance (el,
    481                                  &ictx.refunded_amount));
    482   } /* end for all deposited coins */
    483 
    484   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    485               "Total existing refund is %s\n",
    486               TALER_amount2s (&current_refund));
    487 
    488   /* stop immediately if we are 'done' === amount already
    489    * refunded.  */
    490   if (0 >= TALER_amount_cmp (ctx->refund,
    491                              &current_refund))
    492   {
    493     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    494                 "Existing refund of %s at or above requested refund. Finished early.\n",
    495                 TALER_amount2s (&current_refund));
    496     ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
    497     goto cleanup;
    498   }
    499 
    500   /* Phase 2:  Try to increase current refund until it matches desired refund */
    501   for (unsigned int i = 0; i<num_results; i++)
    502   {
    503     struct RefundCoinData *rcdi = &rcd[i];
    504     const struct TALER_Amount *increment;
    505     struct TALER_Amount left;
    506     struct TALER_Amount remaining_refund;
    507     struct ExchangeLimit *el;
    508 
    509     /* How much of the coin is left after the existing refunds? */
    510     if (0 >
    511         TALER_amount_subtract (&left,
    512                                &rcdi->deposited_with_fee,
    513                                &rcdi->refund_amount))
    514     {
    515       GNUNET_break (0);
    516       ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    517       goto cleanup;
    518     }
    519 
    520     if (TALER_amount_is_zero (&left))
    521     {
    522       /* coin was fully refunded, move to next coin */
    523       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    524                   "Coin %s fully refunded, moving to next coin\n",
    525                   TALER_B2S (&rcdi->coin_pub));
    526       continue;
    527     }
    528     el = find_exchange (ctx,
    529                         rcdi->exchange_url);
    530     if ( (NULL != el) &&
    531          (TALER_amount_is_zero (&el->remaining_refund_limit)) )
    532     {
    533       /* legal limit reached, move to next coin */
    534       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    535                   "Exchange %s legal limit reached, moving to next coin\n",
    536                   rcdi->exchange_url);
    537       continue;
    538     }
    539 
    540     rcdi->max_rtransaction_id++;
    541     /* How much of the refund is still to be paid back? */
    542     if (0 >
    543         TALER_amount_subtract (&remaining_refund,
    544                                ctx->refund,
    545                                &current_refund))
    546     {
    547       GNUNET_break (0);
    548       ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    549       goto cleanup;
    550     }
    551     /* cap by legal limit */
    552     if (NULL != el)
    553     {
    554       struct TALER_Amount new_limit;
    555 
    556       TALER_amount_min (&new_limit,
    557                         &remaining_refund,
    558                         &el->remaining_refund_limit);
    559       if (0 != TALER_amount_cmp (&new_limit,
    560                                  &remaining_refund))
    561       {
    562         remaining_refund = new_limit;
    563         ctx->legal_capped = true;
    564       }
    565     }
    566     /* By how much will we increase the refund for this coin? */
    567     if (0 >= TALER_amount_cmp (&remaining_refund,
    568                                &left))
    569     {
    570       /* remaining_refund <= left */
    571       increment = &remaining_refund;
    572     }
    573     else
    574     {
    575       increment = &left;
    576     }
    577 
    578     if (0 >
    579         TALER_amount_add (&current_refund,
    580                           &current_refund,
    581                           increment))
    582     {
    583       GNUNET_break (0);
    584       ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    585       goto cleanup;
    586     }
    587     GNUNET_break (lower_balance (el,
    588                                  increment));
    589     /* actually run the refund */
    590     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    591                 "Coin %s deposit amount is %s\n",
    592                 TALER_B2S (&rcdi->coin_pub),
    593                 TALER_amount2s (&rcdi->deposited_with_fee));
    594     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    595                 "Coin %s refund will be incremented by %s\n",
    596                 TALER_B2S (&rcdi->coin_pub),
    597                 TALER_amount2s (increment));
    598     {
    599       enum GNUNET_DB_QueryStatus qs;
    600       struct GNUNET_PQ_QueryParam params[] = {
    601         GNUNET_PQ_query_param_uint64 (&rcdi->order_serial),
    602         GNUNET_PQ_query_param_uint64 (&rcdi->max_rtransaction_id), /* already inc'ed */
    603         GNUNET_PQ_query_param_timestamp (&now),
    604         GNUNET_PQ_query_param_auto_from_type (&rcdi->coin_pub),
    605         GNUNET_PQ_query_param_string (ctx->reason),
    606         TALER_PQ_query_param_amount_with_currency (pg->conn,
    607                                                    increment),
    608         GNUNET_PQ_query_param_end
    609       };
    610 
    611       check_connection (pg);
    612       qs = prep_insert_refund (pg);
    613       if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
    614       {
    615         GNUNET_break (0);
    616         ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    617         goto cleanup;
    618       }
    619       qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
    620                                                "",
    621                                                params);
    622       switch (qs)
    623       {
    624       case GNUNET_DB_STATUS_HARD_ERROR:
    625         GNUNET_break (0);
    626         ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    627         goto cleanup;
    628       case GNUNET_DB_STATUS_SOFT_ERROR:
    629         ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
    630         goto cleanup;
    631       default:
    632         ctx->rs = (enum TALER_MERCHANTDB_RefundStatus) qs;
    633         break;
    634       }
    635     }
    636 
    637     /* stop immediately if we are done */
    638     if (0 == TALER_amount_cmp (ctx->refund,
    639                                &current_refund))
    640     {
    641       ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
    642       goto cleanup;
    643     }
    644   }
    645 
    646   if (ctx->legal_capped)
    647   {
    648     ctx->rs = TALER_MERCHANTDB_RS_LEGAL_FAILURE;
    649     goto cleanup;
    650   }
    651   /**
    652    * We end up here if not all of the refund has been covered.
    653    * Although this should be checked as the business should never
    654    * issue a refund bigger than the contract's actual price, we cannot
    655    * rely upon the frontend being correct.
    656    */
    657   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    658               "The refund of %s is bigger than the order's value\n",
    659               TALER_amount2s (ctx->refund));
    660   ctx->rs = TALER_MERCHANTDB_RS_TOO_HIGH;
    661 cleanup:
    662   for (unsigned int i = 0; i<num_results; i++)
    663     GNUNET_free (rcd[i].exchange_url);
    664 }
    665 
    666 
    667 enum TALER_MERCHANTDB_RefundStatus
    668 TALER_MERCHANTDB_increase_refund (
    669   struct TALER_MERCHANTDB_PostgresContext *pg,
    670   const char *instance_id,
    671   const char *order_id,
    672   const struct TALER_Amount *refund,
    673   TALER_MERCHANTDB_OperationLimitCallback olc,
    674   void *olc_cls,
    675   const char *reason)
    676 {
    677   enum GNUNET_DB_QueryStatus qs;
    678   struct GNUNET_PQ_QueryParam params[] = {
    679     GNUNET_PQ_query_param_string (order_id),
    680     GNUNET_PQ_query_param_end
    681   };
    682   struct InsertRefundContext ctx = {
    683     .pg = pg,
    684     .refund = refund,
    685     .olc = olc,
    686     .olc_cls = olc_cls,
    687     .reason = reason,
    688   };
    689 
    690   GNUNET_assert (NULL != pg->current_merchant_id);
    691   GNUNET_assert (0 == strcmp (instance_id,
    692                               pg->current_merchant_id));
    693   TMH_PQ_prepare_anon (pg,
    694                        "SELECT"
    695                        " dep.coin_pub"
    696                        ",dco.order_serial"
    697                        ",dep.amount_with_fee"
    698                        ",dco.exchange_url"
    699                        " FROM merchant_deposits dep"
    700                        " JOIN merchant_deposit_confirmations dco"
    701                        "   USING (deposit_confirmation_serial)"
    702                        " WHERE order_serial="
    703                        "  (SELECT order_serial"
    704                        "     FROM merchant_contract_terms"
    705                        "    WHERE order_id=$1"
    706                        "      AND paid)");
    707   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    708               "Asked to refund %s on order %s\n",
    709               TALER_amount2s (refund),
    710               order_id);
    711   qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
    712                                              "",
    713                                              params,
    714                                              &process_deposits_for_refund_cb,
    715                                              &ctx);
    716   {
    717     struct ExchangeLimit *el;
    718 
    719     while (NULL != (el = ctx.el_head))
    720     {
    721       GNUNET_CONTAINER_DLL_remove (ctx.el_head,
    722                                    ctx.el_tail,
    723                                    el);
    724       GNUNET_free (el->exchange_url);
    725       GNUNET_free (el);
    726     }
    727   }
    728   switch (qs)
    729   {
    730   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    731     /* never paid, means we clearly cannot refund anything */
    732     return TALER_MERCHANTDB_RS_NO_SUCH_ORDER;
    733   case GNUNET_DB_STATUS_SOFT_ERROR:
    734     return TALER_MERCHANTDB_RS_SOFT_ERROR;
    735   case GNUNET_DB_STATUS_HARD_ERROR:
    736     return TALER_MERCHANTDB_RS_HARD_ERROR;
    737   default:
    738     /* Got one or more deposits */
    739     return ctx.rs;
    740   }
    741 }