merchant

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

taler-merchant-httpd_post-orders-ORDER_ID-refund.c (25850B)


      1 /*
      2   This file is part of TALER
      3   (C) 2020-2022 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation; either version 3,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file src/backend/taler-merchant-httpd_post-orders-ORDER_ID-refund.c
     22  * @brief handling of POST /orders/$ID/refund requests
     23  * @author Jonathan Buchanan
     24  */
     25 #include "platform.h"
     26 struct CoinRefund;
     27 #define TALER_EXCHANGE_POST_COINS_REFUND_RESULT_CLOSURE struct CoinRefund
     28 #include <taler/taler_dbevents.h>
     29 #include <taler/taler_signatures.h>
     30 #include <taler/taler_json_lib.h>
     31 #include <taler/taler_exchange_service.h>
     32 #include "taler-merchant-httpd.h"
     33 #include "taler-merchant-httpd_exchanges.h"
     34 #include "taler-merchant-httpd_get-exchanges.h"
     35 #include "taler-merchant-httpd_post-orders-ORDER_ID-refund.h"
     36 #include "merchant-database/insert_refund_proof.h"
     37 #include "merchant-database/lookup_contract_terms.h"
     38 #include "merchant-database/set_instance.h"
     39 #include "merchant-database/lookup_refund_proof.h"
     40 #include "merchant-database/lookup_refunds_detailed.h"
     41 #include "merchant-database/event_notify.h"
     42 #include "merchant-database/preflight.h"
     43 
     44 
     45 /**
     46  * Information we keep for each coin to be refunded.
     47  */
     48 struct CoinRefund
     49 {
     50 
     51   /**
     52    * Kept in a DLL.
     53    */
     54   struct CoinRefund *next;
     55 
     56   /**
     57    * Kept in a DLL.
     58    */
     59   struct CoinRefund *prev;
     60 
     61   /**
     62    * Request to connect to the target exchange.
     63    */
     64   struct TMH_EXCHANGES_KeysOperation *fo;
     65 
     66   /**
     67    * Handle for the refund operation with the exchange.
     68    */
     69   struct TALER_EXCHANGE_PostCoinsRefundHandle *rh;
     70 
     71   /**
     72    * Request this operation is part of.
     73    */
     74   struct PostRefundData *prd;
     75 
     76   /**
     77    * URL of the exchange for this @e coin_pub.
     78    */
     79   char *exchange_url;
     80 
     81   /**
     82    * Fully reply from the exchange, only possibly set if
     83    * we got a JSON reply and a non-#MHD_HTTP_OK error code
     84    */
     85   json_t *exchange_reply;
     86 
     87   /**
     88    * When did the merchant grant the refund. To be used to group events
     89    * in the wallet.
     90    */
     91   struct GNUNET_TIME_Timestamp execution_time;
     92 
     93   /**
     94    * Coin to refund.
     95    */
     96   struct TALER_CoinSpendPublicKeyP coin_pub;
     97 
     98   /**
     99    * Refund transaction ID to use.
    100    */
    101   uint64_t rtransaction_id;
    102 
    103   /**
    104    * Unique serial number identifying the refund.
    105    */
    106   uint64_t refund_serial;
    107 
    108   /**
    109    * Amount to refund.
    110    */
    111   struct TALER_Amount refund_amount;
    112 
    113   /**
    114    * Public key of the exchange affirming the refund.
    115    */
    116   struct TALER_ExchangePublicKeyP exchange_pub;
    117 
    118   /**
    119    * Signature of the exchange affirming the refund.
    120    */
    121   struct TALER_ExchangeSignatureP exchange_sig;
    122 
    123   /**
    124    * HTTP status from the exchange, #MHD_HTTP_OK if
    125    * @a exchange_pub and @a exchange_sig are valid.
    126    */
    127   unsigned int exchange_status;
    128 
    129   /**
    130    * HTTP error code from the exchange.
    131    */
    132   enum TALER_ErrorCode exchange_code;
    133 
    134 };
    135 
    136 
    137 /**
    138  * Context for the operation.
    139  */
    140 struct PostRefundData
    141 {
    142 
    143   /**
    144    * Hashed version of contract terms. All zeros if not provided.
    145    */
    146   struct TALER_PrivateContractHashP h_contract_terms;
    147 
    148   /**
    149    * DLL of (suspended) requests.
    150    */
    151   struct PostRefundData *next;
    152 
    153   /**
    154    * DLL of (suspended) requests.
    155    */
    156   struct PostRefundData *prev;
    157 
    158   /**
    159    * Refunds for this order. Head of DLL.
    160    */
    161   struct CoinRefund *cr_head;
    162 
    163   /**
    164    * Refunds for this order. Tail of DLL.
    165    */
    166   struct CoinRefund *cr_tail;
    167 
    168   /**
    169    * Context of the request.
    170    */
    171   struct TMH_HandlerContext *hc;
    172 
    173   /**
    174    * Entry in the #resume_timeout_heap for this check payment, if we are
    175    * suspended.
    176    */
    177   struct TMH_SuspendedConnection sc;
    178 
    179   /**
    180    * order ID for the payment
    181    */
    182   const char *order_id;
    183 
    184   /**
    185    * Where to get the contract
    186    */
    187   const char *contract_url;
    188 
    189   /**
    190    * fulfillment URL of the contract (valid as long as
    191    * @e contract_terms is valid).
    192    */
    193   const char *fulfillment_url;
    194 
    195   /**
    196    * session of the client
    197    */
    198   const char *session_id;
    199 
    200   /**
    201    * Contract terms of the payment we are checking. NULL when they
    202    * are not (yet) known.
    203    */
    204   json_t *contract_terms;
    205 
    206   /**
    207    * Total refunds granted for this payment. Only initialized
    208    * if @e refunded is set to true.
    209    */
    210   struct TALER_Amount refund_amount;
    211 
    212   /**
    213    * Did we suspend @a connection and are thus in
    214    * the #prd_head DLL (#GNUNET_YES). Set to
    215    * #GNUNET_NO if we are not suspended, and to
    216    * #GNUNET_SYSERR if we should close the connection
    217    * without a response due to shutdown.
    218    */
    219   enum GNUNET_GenericReturnValue suspended;
    220 
    221   /**
    222    * Return code: #TALER_EC_NONE if successful.
    223    */
    224   enum TALER_ErrorCode ec;
    225 
    226   /**
    227    * HTTP status to use for the reply, 0 if not yet known.
    228    */
    229   unsigned int http_status;
    230 
    231   /**
    232    * Set to true if we are dealing with an unclaimed order
    233    * (and thus @e h_contract_terms is not set, and certain
    234    * DB queries will not work).
    235    */
    236   bool unclaimed;
    237 
    238   /**
    239    * Set to true if this payment has been refunded and
    240    * @e refund_amount is initialized.
    241    */
    242   bool refunded;
    243 
    244   /**
    245    * Set to true if a refund is still available for the
    246    * wallet for this payment.
    247    */
    248   bool refund_available;
    249 
    250   /**
    251    * Set to true if the client requested HTML, otherwise
    252    * we generate JSON.
    253    */
    254   bool generate_html;
    255 
    256 };
    257 
    258 
    259 /**
    260  * Head of DLL of (suspended) requests.
    261  */
    262 static struct PostRefundData *prd_head;
    263 
    264 /**
    265  * Tail of DLL of (suspended) requests.
    266  */
    267 static struct PostRefundData *prd_tail;
    268 
    269 
    270 /**
    271  * Function called when we are done processing a refund request.
    272  * Frees memory associated with @a ctx.
    273  *
    274  * @param ctx a `struct PostRefundData`
    275  */
    276 static void
    277 refund_cleanup (void *ctx)
    278 {
    279   struct PostRefundData *prd = ctx;
    280   struct CoinRefund *cr;
    281 
    282   while (NULL != (cr = prd->cr_head))
    283   {
    284     GNUNET_CONTAINER_DLL_remove (prd->cr_head,
    285                                  prd->cr_tail,
    286                                  cr);
    287     json_decref (cr->exchange_reply);
    288     GNUNET_free (cr->exchange_url);
    289     if (NULL != cr->fo)
    290     {
    291       TMH_EXCHANGES_keys4exchange_cancel (cr->fo);
    292       cr->fo = NULL;
    293     }
    294     if (NULL != cr->rh)
    295     {
    296       TALER_EXCHANGE_post_coins_refund_cancel (cr->rh);
    297       cr->rh = NULL;
    298     }
    299     GNUNET_free (cr);
    300   }
    301   json_decref (prd->contract_terms);
    302   GNUNET_free (prd);
    303 }
    304 
    305 
    306 /**
    307  * Force resuming all suspended order lookups, needed during shutdown.
    308  */
    309 void
    310 TMH_force_wallet_refund_order_resume (void)
    311 {
    312   struct PostRefundData *prd;
    313 
    314   while (NULL != (prd = prd_head))
    315   {
    316     GNUNET_CONTAINER_DLL_remove (prd_head,
    317                                  prd_tail,
    318                                  prd);
    319     GNUNET_assert (GNUNET_YES == prd->suspended);
    320     prd->suspended = GNUNET_SYSERR;
    321     MHD_resume_connection (prd->sc.con);
    322   }
    323 }
    324 
    325 
    326 /**
    327  * Check if @a prd has exchange requests still pending.
    328  *
    329  * @param prd state to check
    330  * @return true if activities are still pending
    331  */
    332 static bool
    333 exchange_operations_pending (struct PostRefundData *prd)
    334 {
    335   for (struct CoinRefund *cr = prd->cr_head;
    336        NULL != cr;
    337        cr = cr->next)
    338   {
    339     if ( (NULL != cr->fo) ||
    340          (NULL != cr->rh) )
    341       return true;
    342   }
    343   return false;
    344 }
    345 
    346 
    347 /**
    348  * Check if @a prd is ready to be resumed, and if so, do it.
    349  *
    350  * @param prd refund request to be possibly ready
    351  */
    352 static void
    353 check_resume_prd (struct PostRefundData *prd)
    354 {
    355   if ( (TALER_EC_NONE == prd->ec) &&
    356        exchange_operations_pending (prd) )
    357     return;
    358   GNUNET_CONTAINER_DLL_remove (prd_head,
    359                                prd_tail,
    360                                prd);
    361   GNUNET_assert (prd->suspended);
    362   prd->suspended = GNUNET_NO;
    363   MHD_resume_connection (prd->sc.con);
    364   TALER_MHD_daemon_trigger ();
    365 }
    366 
    367 
    368 /**
    369  * Notify applications waiting for a client to obtain
    370  * a refund.
    371  *
    372  * @param prd refund request with the change
    373  */
    374 static void
    375 notify_refund_obtained (struct PostRefundData *prd)
    376 {
    377   struct TMH_OrderPayEventP refund_eh = {
    378     .header.size = htons (sizeof (refund_eh)),
    379     .header.type = htons (TALER_DBEVENT_MERCHANT_REFUND_OBTAINED),
    380     .merchant_pub = prd->hc->instance->merchant_pub
    381   };
    382 
    383   GNUNET_CRYPTO_hash (prd->order_id,
    384                       strlen (prd->order_id),
    385                       &refund_eh.h_order_id);
    386   TALER_MERCHANTDB_event_notify (TMH_db,
    387                                  &refund_eh.header,
    388                                  NULL,
    389                                  0);
    390 }
    391 
    392 
    393 /**
    394  * Callbacks of this type are used to serve the result of submitting a
    395  * refund request to an exchange.
    396  *
    397  * @param cls a `struct CoinRefund`
    398  * @param rr response data
    399  */
    400 static void
    401 refund_cb (struct CoinRefund *cr,
    402            const struct TALER_EXCHANGE_PostCoinsRefundResponse *rr)
    403 {
    404   const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
    405 
    406   cr->rh = NULL;
    407   cr->exchange_status = hr->http_status;
    408   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    409               "Exchange refund status for coin %s is %u\n",
    410               TALER_B2S (&cr->coin_pub),
    411               hr->http_status);
    412   switch (hr->http_status)
    413   {
    414   case MHD_HTTP_OK:
    415     {
    416       enum GNUNET_DB_QueryStatus qs;
    417 
    418       qs = TALER_MERCHANTDB_set_instance (
    419         TMH_db,
    420         cr->prd->hc->instance->settings.id);
    421       if (0 >= qs)
    422       {
    423         /* generally, this is relatively harmless for the merchant, but let's at
    424            least log this. */
    425         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    426                     "Failed to persist exchange response to /refund in database: %d\n",
    427                     qs);
    428       }
    429       else
    430       {
    431         cr->exchange_pub = rr->details.ok.exchange_pub;
    432         cr->exchange_sig = rr->details.ok.exchange_sig;
    433         qs = TALER_MERCHANTDB_insert_refund_proof (TMH_db,
    434                                                    cr->refund_serial,
    435                                                    &rr->details.ok.exchange_sig,
    436                                                    &rr->details.ok.exchange_pub);
    437         if (0 >= qs)
    438         {
    439           /* generally, this is relatively harmless for the merchant, but let's at
    440              least log this. */
    441           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    442                       "Failed to persist exchange response to /refund in database: %d\n",
    443                       qs);
    444         }
    445         else
    446         {
    447           notify_refund_obtained (cr->prd);
    448         }
    449         GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    450                       TALER_MERCHANTDB_set_instance (
    451                         TMH_db,
    452                         NULL));
    453       }
    454     }
    455     break;
    456   default:
    457     cr->exchange_code = hr->ec;
    458     cr->exchange_reply = json_incref ((json_t*) hr->reply);
    459     break;
    460   }
    461   check_resume_prd (cr->prd);
    462 }
    463 
    464 
    465 /**
    466  * Function called with the result of a
    467  * #TMH_EXCHANGES_keys4exchange()
    468  * operation.
    469  *
    470  * @param cls a `struct CoinRefund *`
    471  * @param keys keys of exchange, NULL on error
    472  * @param exchange representation of the exchange
    473  */
    474 static void
    475 exchange_found_cb (void *cls,
    476                    struct TALER_EXCHANGE_Keys *keys,
    477                    struct TMH_Exchange *exchange)
    478 {
    479   struct CoinRefund *cr = cls;
    480   struct PostRefundData *prd = cr->prd;
    481 
    482   (void) exchange;
    483   cr->fo = NULL;
    484   if (NULL == keys)
    485   {
    486     prd->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
    487     prd->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT;
    488     check_resume_prd (prd);
    489     return;
    490   }
    491   cr->rh = TALER_EXCHANGE_post_coins_refund_create (
    492     TMH_curl_ctx,
    493     cr->exchange_url,
    494     keys,
    495     &cr->refund_amount,
    496     &prd->h_contract_terms,
    497     &cr->coin_pub,
    498     cr->rtransaction_id,
    499     &prd->hc->instance->merchant_priv);
    500   GNUNET_assert (NULL != cr->rh);
    501   GNUNET_assert (TALER_EC_NONE ==
    502                  TALER_EXCHANGE_post_coins_refund_start (cr->rh,
    503                                                          &refund_cb,
    504                                                          cr));
    505 }
    506 
    507 
    508 /**
    509  * Function called with information about a refund.
    510  * It is responsible for summing up the refund amount.
    511  *
    512  * @param cls closure
    513  * @param refund_serial unique serial number of the refund
    514  * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
    515  * @param coin_pub public coin from which the refund comes from
    516  * @param exchange_url URL of the exchange that issued @a coin_pub
    517  * @param rtransaction_id identificator of the refund
    518  * @param reason human-readable explanation of the refund
    519  * @param refund_amount refund amount which is being taken from @a coin_pub
    520  * @param pending true if the this refund was not yet processed by the wallet/exchange
    521  */
    522 static void
    523 process_refunds_cb (void *cls,
    524                     uint64_t refund_serial,
    525                     struct GNUNET_TIME_Timestamp timestamp,
    526                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
    527                     const char *exchange_url,
    528                     uint64_t rtransaction_id,
    529                     const char *reason,
    530                     const struct TALER_Amount *refund_amount,
    531                     bool pending)
    532 {
    533   struct PostRefundData *prd = cls;
    534   struct CoinRefund *cr;
    535 
    536   for (cr = prd->cr_head;
    537        NULL != cr;
    538        cr = cr->next)
    539     if (cr->refund_serial == refund_serial)
    540       return;
    541   /* already known */
    542 
    543   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    544               "Found refund of %s for coin %s with reason `%s' in database\n",
    545               TALER_amount2s (refund_amount),
    546               TALER_B2S (coin_pub),
    547               reason);
    548   cr = GNUNET_new (struct CoinRefund);
    549   cr->refund_serial = refund_serial;
    550   cr->exchange_url = GNUNET_strdup (exchange_url);
    551   cr->prd = prd;
    552   cr->coin_pub = *coin_pub;
    553   cr->rtransaction_id = rtransaction_id;
    554   cr->refund_amount = *refund_amount;
    555   cr->execution_time = timestamp;
    556   GNUNET_CONTAINER_DLL_insert (prd->cr_head,
    557                                prd->cr_tail,
    558                                cr);
    559   if (prd->refunded)
    560   {
    561     GNUNET_assert (0 <=
    562                    TALER_amount_add (&prd->refund_amount,
    563                                      &prd->refund_amount,
    564                                      refund_amount));
    565     return;
    566   }
    567   prd->refund_amount = *refund_amount;
    568   prd->refunded = true;
    569   prd->refund_available |= pending;
    570 }
    571 
    572 
    573 /**
    574  * Obtain refunds for an order.
    575  *
    576  * @param rh context of the handler
    577  * @param connection the MHD connection to handle
    578  * @param[in,out] hc context with further information about the request
    579  * @return MHD result code
    580  */
    581 enum MHD_Result
    582 TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
    583                            struct MHD_Connection *connection,
    584                            struct TMH_HandlerContext *hc)
    585 {
    586   struct PostRefundData *prd = hc->ctx;
    587   enum GNUNET_DB_QueryStatus qs;
    588 
    589   if (NULL == prd)
    590   {
    591     prd = GNUNET_new (struct PostRefundData);
    592     prd->sc.con = connection;
    593     prd->hc = hc;
    594     prd->order_id = hc->infix;
    595     hc->ctx = prd;
    596     hc->cc = &refund_cleanup;
    597     {
    598       enum GNUNET_GenericReturnValue res;
    599 
    600       struct GNUNET_JSON_Specification spec[] = {
    601         GNUNET_JSON_spec_fixed_auto ("h_contract",
    602                                      &prd->h_contract_terms),
    603         GNUNET_JSON_spec_end ()
    604       };
    605       res = TALER_MHD_parse_json_data (connection,
    606                                        hc->request_body,
    607                                        spec);
    608       if (GNUNET_OK != res)
    609         return (GNUNET_NO == res)
    610                ? MHD_YES
    611                : MHD_NO;
    612     }
    613 
    614     TALER_MERCHANTDB_preflight (TMH_db);
    615     {
    616       json_t *contract_terms;
    617       uint64_t order_serial;
    618 
    619       qs = TALER_MERCHANTDB_lookup_contract_terms (TMH_db,
    620                                                    hc->instance->settings.id,
    621                                                    hc->infix,
    622                                                    &contract_terms,
    623                                                    &order_serial,
    624                                                    NULL);
    625       if (0 > qs)
    626       {
    627         /* single, read-only SQL statements should never cause
    628            serialization problems */
    629         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
    630         /* Always report on hard error as well to enable diagnostics */
    631         GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
    632         return TALER_MHD_reply_with_error (connection,
    633                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
    634                                            TALER_EC_GENERIC_DB_FETCH_FAILED,
    635                                            "contract terms");
    636       }
    637       if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    638       {
    639         json_decref (contract_terms);
    640         return TALER_MHD_reply_with_error (connection,
    641                                            MHD_HTTP_NOT_FOUND,
    642                                            TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
    643                                            hc->infix);
    644       }
    645       {
    646         struct TALER_PrivateContractHashP h_contract_terms;
    647 
    648         if (GNUNET_OK !=
    649             TALER_JSON_contract_hash (contract_terms,
    650                                       &h_contract_terms))
    651         {
    652           GNUNET_break (0);
    653           json_decref (contract_terms);
    654           return TALER_MHD_reply_with_error (connection,
    655                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    656                                              TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
    657                                              NULL);
    658         }
    659         json_decref (contract_terms);
    660         if (0 != GNUNET_memcmp (&h_contract_terms,
    661                                 &prd->h_contract_terms))
    662         {
    663           return TALER_MHD_reply_with_error (
    664             connection,
    665             MHD_HTTP_FORBIDDEN,
    666             TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
    667             NULL);
    668         }
    669       }
    670     }
    671   }
    672   if (GNUNET_SYSERR == prd->suspended)
    673     return MHD_NO; /* we are in shutdown */
    674 
    675   if (TALER_EC_NONE != prd->ec)
    676   {
    677     GNUNET_break (0 != prd->http_status);
    678     /* kill pending coin refund operations immediately, just to be
    679        extra sure they don't modify 'prd' after we already created
    680        a reply (this might not be needed, but feels safer). */
    681     for (struct CoinRefund *cr = prd->cr_head;
    682          NULL != cr;
    683          cr = cr->next)
    684     {
    685       if (NULL != cr->fo)
    686       {
    687         TMH_EXCHANGES_keys4exchange_cancel (cr->fo);
    688         cr->fo = NULL;
    689       }
    690       if (NULL != cr->rh)
    691       {
    692         TALER_EXCHANGE_post_coins_refund_cancel (cr->rh);
    693         cr->rh = NULL;
    694       }
    695     }
    696     return TALER_MHD_reply_with_error (connection,
    697                                        prd->http_status,
    698                                        prd->ec,
    699                                        NULL);
    700   }
    701 
    702   qs = TALER_MERCHANTDB_lookup_refunds_detailed (TMH_db,
    703                                                  hc->instance->settings.id,
    704                                                  &prd->h_contract_terms,
    705                                                  &process_refunds_cb,
    706                                                  prd);
    707   if (0 > qs)
    708   {
    709     GNUNET_break (0);
    710     return TALER_MHD_reply_with_error (connection,
    711                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    712                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
    713                                        "detailed refunds");
    714   }
    715   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    716   {
    717     /* We have a contract, which means the order was paid, but there
    718        are no coins (maybe paid with a token?), and thus we cannot
    719        do any refunds */
    720     return TALER_MHD_reply_static (connection,
    721                                    MHD_HTTP_NO_CONTENT,
    722                                    NULL,
    723                                    NULL,
    724                                    0);
    725   }
    726 
    727   /* Now launch exchange interactions, unless we already have the
    728      response in the database! */
    729   for (struct CoinRefund *cr = prd->cr_head;
    730        NULL != cr;
    731        cr = cr->next)
    732   {
    733     qs = TALER_MERCHANTDB_lookup_refund_proof (TMH_db,
    734                                                cr->refund_serial,
    735                                                &cr->exchange_sig,
    736                                                &cr->exchange_pub);
    737     switch (qs)
    738     {
    739     case GNUNET_DB_STATUS_HARD_ERROR:
    740     case GNUNET_DB_STATUS_SOFT_ERROR:
    741       return TALER_MHD_reply_with_error (connection,
    742                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    743                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    744                                          "refund proof");
    745     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    746       if (NULL == cr->exchange_reply)
    747       {
    748         /* We need to talk to the exchange */
    749         cr->fo = TMH_EXCHANGES_keys4exchange (cr->exchange_url,
    750                                               false,
    751                                               &exchange_found_cb,
    752                                               cr);
    753         if (NULL == cr->fo)
    754         {
    755           GNUNET_break (0);
    756           return TALER_MHD_reply_with_error (connection,
    757                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    758                                              TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNTRUSTED,
    759                                              cr->exchange_url);
    760         }
    761       }
    762       break;
    763     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    764       /* We got a reply earlier, set status accordingly */
    765       cr->exchange_status = MHD_HTTP_OK;
    766       break;
    767     }
    768   }
    769 
    770   /* Check if there are still exchange operations pending */
    771   if (exchange_operations_pending (prd))
    772   {
    773     if (GNUNET_NO == prd->suspended)
    774     {
    775       prd->suspended = GNUNET_YES;
    776       MHD_suspend_connection (connection);
    777       GNUNET_CONTAINER_DLL_insert (prd_head,
    778                                    prd_tail,
    779                                    prd);
    780     }
    781     return MHD_YES;   /* we're still talking to the exchange */
    782   }
    783 
    784   {
    785     json_t *ra;
    786 
    787     ra = json_array ();
    788     GNUNET_assert (NULL != ra);
    789     for (struct CoinRefund *cr = prd->cr_head;
    790          NULL != cr;
    791          cr = cr->next)
    792     {
    793       json_t *refund;
    794 
    795       if (MHD_HTTP_OK != cr->exchange_status)
    796       {
    797         if (NULL == cr->exchange_reply)
    798         {
    799           refund = GNUNET_JSON_PACK (
    800             GNUNET_JSON_pack_string ("type",
    801                                      "failure"),
    802             GNUNET_JSON_pack_uint64 ("exchange_status",
    803                                      cr->exchange_status),
    804             GNUNET_JSON_pack_uint64 ("rtransaction_id",
    805                                      cr->rtransaction_id),
    806             GNUNET_JSON_pack_data_auto ("coin_pub",
    807                                         &cr->coin_pub),
    808             TALER_JSON_pack_amount ("refund_amount",
    809                                     &cr->refund_amount),
    810             GNUNET_JSON_pack_timestamp ("execution_time",
    811                                         cr->execution_time));
    812         }
    813         else
    814         {
    815           refund = GNUNET_JSON_PACK (
    816             GNUNET_JSON_pack_string ("type",
    817                                      "failure"),
    818             GNUNET_JSON_pack_uint64 ("exchange_status",
    819                                      cr->exchange_status),
    820             GNUNET_JSON_pack_uint64 ("exchange_code",
    821                                      cr->exchange_code),
    822             GNUNET_JSON_pack_object_incref ("exchange_reply",
    823                                             cr->exchange_reply),
    824             GNUNET_JSON_pack_uint64 ("rtransaction_id",
    825                                      cr->rtransaction_id),
    826             GNUNET_JSON_pack_data_auto ("coin_pub",
    827                                         &cr->coin_pub),
    828             TALER_JSON_pack_amount ("refund_amount",
    829                                     &cr->refund_amount),
    830             GNUNET_JSON_pack_timestamp ("execution_time",
    831                                         cr->execution_time));
    832         }
    833       }
    834       else
    835       {
    836         refund = GNUNET_JSON_PACK (
    837           GNUNET_JSON_pack_string ("type",
    838                                    "success"),
    839           GNUNET_JSON_pack_uint64 ("exchange_status",
    840                                    cr->exchange_status),
    841           GNUNET_JSON_pack_data_auto ("exchange_sig",
    842                                       &cr->exchange_sig),
    843           GNUNET_JSON_pack_data_auto ("exchange_pub",
    844                                       &cr->exchange_pub),
    845           GNUNET_JSON_pack_uint64 ("rtransaction_id",
    846                                    cr->rtransaction_id),
    847           GNUNET_JSON_pack_data_auto ("coin_pub",
    848                                       &cr->coin_pub),
    849           TALER_JSON_pack_amount ("refund_amount",
    850                                   &cr->refund_amount),
    851           GNUNET_JSON_pack_timestamp ("execution_time",
    852                                       cr->execution_time));
    853       }
    854       GNUNET_assert (
    855         0 ==
    856         json_array_append_new (ra,
    857                                refund));
    858     }
    859 
    860     return TALER_MHD_REPLY_JSON_PACK (
    861       connection,
    862       MHD_HTTP_OK,
    863       TALER_JSON_pack_amount ("refund_amount",
    864                               &prd->refund_amount),
    865       GNUNET_JSON_pack_array_steal ("refunds",
    866                                     ra),
    867       GNUNET_JSON_pack_data_auto ("merchant_pub",
    868                                   &hc->instance->merchant_pub));
    869   }
    870 
    871   return MHD_YES;
    872 }
    873 
    874 
    875 /* end of taler-merchant-httpd_post-orders-ORDER_ID-refund.c */