merchant

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

do_increase_refund.c (22051B)


      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 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                        " ON CONFLICT (order_serial"
    358                        ",coin_pub"
    359                        ",rtransaction_id)"
    360                        " DO NOTHING");
    361   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 process_deposits_for_refund_cb (void *cls,
    375                                 PGresult *result,
    376                                 unsigned int num_results)
    377 {
    378   struct InsertRefundContext *ctx = cls;
    379   struct TALER_MERCHANTDB_PostgresContext *pg = ctx->pg;
    380   struct TALER_Amount current_refund;
    381   struct RefundCoinData rcd[GNUNET_NZL (num_results)];
    382   struct GNUNET_TIME_Timestamp now;
    383 
    384   now = GNUNET_TIME_timestamp_get ();
    385   GNUNET_assert (GNUNET_OK ==
    386                  TALER_amount_set_zero (ctx->refund->currency,
    387                                         &current_refund));
    388   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   for (unsigned int i = 0; i<num_results; i++)
    394   {
    395     struct RefundCoinData *rcdi = &rcd[i];
    396     struct GNUNET_PQ_ResultSpec rs[] = {
    397       GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
    398                                             &rcdi->coin_pub),
    399       GNUNET_PQ_result_spec_uint64 ("order_serial",
    400                                     &rcdi->order_serial),
    401       GNUNET_PQ_result_spec_string ("exchange_url",
    402                                     &rcdi->exchange_url),
    403       TALER_PQ_result_spec_amount_with_currency ("amount_with_fee",
    404                                                  &rcdi->deposited_with_fee),
    405       GNUNET_PQ_result_spec_end
    406     };
    407     struct FindRefundContext ictx = {
    408       .pg = pg,
    409     };
    410     struct ExchangeLimit *el;
    411 
    412     if (GNUNET_OK !=
    413         GNUNET_PQ_extract_result (result,
    414                                   rs,
    415                                   i))
    416     {
    417       GNUNET_break (0);
    418       ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    419       goto cleanup;
    420     }
    421     el = setup_exchange (ctx,
    422                          rcdi->exchange_url);
    423     if (GNUNET_YES !=
    424         TALER_amount_cmp_currency (&rcdi->deposited_with_fee,
    425                                    ctx->refund))
    426     {
    427       GNUNET_break_op (0);
    428       ctx->rs = TALER_MERCHANTDB_RS_BAD_CURRENCY;
    429       goto cleanup;
    430     }
    431 
    432     {
    433       enum GNUNET_DB_QueryStatus ires;
    434       struct GNUNET_PQ_QueryParam params[] = {
    435         GNUNET_PQ_query_param_auto_from_type (&rcdi->coin_pub),
    436         GNUNET_PQ_query_param_uint64 (&rcdi->order_serial),
    437         GNUNET_PQ_query_param_end
    438       };
    439 
    440       GNUNET_assert (GNUNET_OK ==
    441                      TALER_amount_set_zero (
    442                        ctx->refund->currency,
    443                        &ictx.refunded_amount));
    444       ires = prep_select_refund (pg);
    445       if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != ires)
    446       {
    447         GNUNET_break (0);
    448         ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    449         goto cleanup;
    450       }
    451       ires = GNUNET_PQ_eval_prepared_multi_select (
    452         pg->conn,
    453         "",
    454         params,
    455         &process_refund_cb,
    456         &ictx);
    457       if ( (ictx.err) ||
    458            (GNUNET_DB_STATUS_HARD_ERROR == ires) )
    459       {
    460         GNUNET_break (0);
    461         ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    462         goto cleanup;
    463       }
    464       if (GNUNET_DB_STATUS_SOFT_ERROR == ires)
    465       {
    466         ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
    467         goto cleanup;
    468       }
    469     }
    470     if (0 >
    471         TALER_amount_add (&current_refund,
    472                           &current_refund,
    473                           &ictx.refunded_amount))
    474     {
    475       GNUNET_break (0);
    476       ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    477       goto cleanup;
    478     }
    479     rcdi->refund_amount = ictx.refunded_amount;
    480     rcdi->max_rtransaction_id = ictx.max_rtransaction_id;
    481     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     GNUNET_break (lower_balance (el,
    486                                  &ictx.refunded_amount));
    487   } /* end for all deposited coins */
    488 
    489   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   if (0 >= TALER_amount_cmp (ctx->refund,
    496                              &current_refund))
    497   {
    498     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     ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
    502     goto cleanup;
    503   }
    504 
    505   /* Phase 2:  Try to increase current refund until it matches desired refund */
    506   for (unsigned int i = 0; i<num_results; i++)
    507   {
    508     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     if (0 >
    516         TALER_amount_subtract (&left,
    517                                &rcdi->deposited_with_fee,
    518                                &rcdi->refund_amount))
    519     {
    520       GNUNET_break (0);
    521       ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    522       goto cleanup;
    523     }
    524 
    525     if (TALER_amount_is_zero (&left))
    526     {
    527       /* coin was fully refunded, move to next coin */
    528       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    529                   "Coin %s fully refunded, moving to next coin\n",
    530                   TALER_B2S (&rcdi->coin_pub));
    531       continue;
    532     }
    533     el = find_exchange (ctx,
    534                         rcdi->exchange_url);
    535     if ( (NULL != el) &&
    536          (TALER_amount_is_zero (&el->remaining_refund_limit)) )
    537     {
    538       /* legal limit reached, move to next coin */
    539       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    540                   "Exchange %s legal limit reached, moving to next coin\n",
    541                   rcdi->exchange_url);
    542       continue;
    543     }
    544 
    545     rcdi->max_rtransaction_id++;
    546     /* How much of the refund is still to be paid back? */
    547     if (0 >
    548         TALER_amount_subtract (&remaining_refund,
    549                                ctx->refund,
    550                                &current_refund))
    551     {
    552       GNUNET_break (0);
    553       ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    554       goto cleanup;
    555     }
    556     /* cap by legal limit */
    557     if (NULL != el)
    558     {
    559       struct TALER_Amount new_limit;
    560 
    561       TALER_amount_min (&new_limit,
    562                         &remaining_refund,
    563                         &el->remaining_refund_limit);
    564       if (0 != TALER_amount_cmp (&new_limit,
    565                                  &remaining_refund))
    566       {
    567         remaining_refund = new_limit;
    568         ctx->legal_capped = true;
    569       }
    570     }
    571     /* By how much will we increase the refund for this coin? */
    572     if (0 >= TALER_amount_cmp (&remaining_refund,
    573                                &left))
    574     {
    575       /* remaining_refund <= left */
    576       increment = &remaining_refund;
    577     }
    578     else
    579     {
    580       increment = &left;
    581     }
    582 
    583     if (0 >
    584         TALER_amount_add (&current_refund,
    585                           &current_refund,
    586                           increment))
    587     {
    588       GNUNET_break (0);
    589       ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    590       goto cleanup;
    591     }
    592     GNUNET_break (lower_balance (el,
    593                                  increment));
    594     /* actually run the refund */
    595     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     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       struct GNUNET_PQ_QueryParam params[] = {
    606         GNUNET_PQ_query_param_uint64 (&rcdi->order_serial),
    607         GNUNET_PQ_query_param_uint64 (&rcdi->max_rtransaction_id), /* already inc'ed */
    608         GNUNET_PQ_query_param_timestamp (&now),
    609         GNUNET_PQ_query_param_auto_from_type (&rcdi->coin_pub),
    610         GNUNET_PQ_query_param_string (ctx->reason),
    611         TALER_PQ_query_param_amount_with_currency (pg->conn,
    612                                                    increment),
    613         GNUNET_PQ_query_param_end
    614       };
    615 
    616       qs = prep_insert_refund (pg);
    617       if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
    618       {
    619         GNUNET_break (0);
    620         ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    621         goto cleanup;
    622       }
    623       qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
    624                                                "",
    625                                                params);
    626       switch (qs)
    627       {
    628       case GNUNET_DB_STATUS_HARD_ERROR:
    629         GNUNET_break (0);
    630         ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
    631         goto cleanup;
    632       case GNUNET_DB_STATUS_SOFT_ERROR:
    633         ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
    634         goto cleanup;
    635       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         ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
    645         goto cleanup;
    646       default:
    647         ctx->rs = (enum TALER_MERCHANTDB_RefundStatus) qs;
    648         break;
    649       }
    650     }
    651 
    652     /* stop immediately if we are done */
    653     if (0 == TALER_amount_cmp (ctx->refund,
    654                                &current_refund))
    655     {
    656       ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
    657       goto cleanup;
    658     }
    659   }
    660 
    661   if (ctx->legal_capped)
    662   {
    663     ctx->rs = TALER_MERCHANTDB_RS_LEGAL_FAILURE;
    664     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   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   ctx->rs = TALER_MERCHANTDB_RS_TOO_HIGH;
    676 cleanup:
    677   for (unsigned int i = 0; i<num_results; i++)
    678     GNUNET_free (rcd[i].exchange_url);
    679 }
    680 
    681 
    682 enum TALER_MERCHANTDB_RefundStatus
    683 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   struct GNUNET_PQ_QueryParam params[] = {
    694     GNUNET_PQ_query_param_string (order_id),
    695     GNUNET_PQ_query_param_end
    696   };
    697   struct InsertRefundContext ctx = {
    698     .pg = pg,
    699     .refund = refund,
    700     .olc = olc,
    701     .olc_cls = olc_cls,
    702     .reason = reason,
    703   };
    704 
    705   GNUNET_assert (NULL != pg->current_merchant_id);
    706   GNUNET_assert (0 == strcmp (instance_id,
    707                               pg->current_merchant_id));
    708   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   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    723               "Asked to refund %s on order %s\n",
    724               TALER_amount2s (refund),
    725               order_id);
    726   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     while (NULL != (el = ctx.el_head))
    735     {
    736       GNUNET_CONTAINER_DLL_remove (ctx.el_head,
    737                                    ctx.el_tail,
    738                                    el);
    739       GNUNET_free (el->exchange_url);
    740       GNUNET_free (el);
    741     }
    742   }
    743   switch (qs)
    744   {
    745   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    746     /* never paid, means we clearly cannot refund anything */
    747     return TALER_MERCHANTDB_RS_NO_SUCH_ORDER;
    748   case GNUNET_DB_STATUS_SOFT_ERROR:
    749     return TALER_MERCHANTDB_RS_SOFT_ERROR;
    750   case GNUNET_DB_STATUS_HARD_ERROR:
    751     return TALER_MERCHANTDB_RS_HARD_ERROR;
    752   default:
    753     /* Got one or more deposits */
    754     return ctx.rs;
    755   }
    756 }