merchant

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

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


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