merchant

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

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


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