merchant

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

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


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