merchant

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

taler-merchant-httpd_post-orders-ID-refund.c (24053B)


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