LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd_refund.c (source / functions) Hit Total Coverage
Test: rcoverage.info Lines: 100 165 60.6 %
Date: 2017-09-17 17:24:28 Functions: 5 6 83.3 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2014-2017 Inria and GNUnet e.V.
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU Affero General Public License as published by the Free Software
       7             :   Foundation; either version 3, or (at your option) any later version.
       8             : 
       9             :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10             :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11             :   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU Affero General Public License along with
      14             :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15             : */
      16             : /**
      17             :  * @file taler-exchange-httpd_refund.c
      18             :  * @brief Handle /refund requests; parses the POST and JSON and
      19             :  *        verifies the coin signature before handing things off
      20             :  *        to the database.
      21             :  * @author Florian Dold
      22             :  * @author Benedikt Mueller
      23             :  * @author Christian Grothoff
      24             :  */
      25             : #include "platform.h"
      26             : #include <gnunet/gnunet_util_lib.h>
      27             : #include <gnunet/gnunet_json_lib.h>
      28             : #include <jansson.h>
      29             : #include <microhttpd.h>
      30             : #include <pthread.h>
      31             : #include "taler_json_lib.h"
      32             : #include "taler-exchange-httpd_parsing.h"
      33             : #include "taler-exchange-httpd_refund.h"
      34             : #include "taler-exchange-httpd_responses.h"
      35             : #include "taler-exchange-httpd_keystate.h"
      36             : #include "taler-exchange-httpd_validation.h"
      37             : 
      38             : 
      39             : /**
      40             :  * Generate successful refund confirmation message.
      41             :  *
      42             :  * @param connection connection to the client
      43             :  * @param refund details about the successful refund
      44             :  * @return MHD result code
      45             :  */
      46             : static int
      47           1 : reply_refund_success (struct MHD_Connection *connection,
      48             :                       const struct TALER_EXCHANGEDB_Refund *refund)
      49             : {
      50             :   struct TALER_RefundConfirmationPS rc;
      51             :   struct TALER_ExchangePublicKeyP pub;
      52             :   struct TALER_ExchangeSignatureP sig;
      53             : 
      54           1 :   rc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND);
      55           1 :   rc.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS));
      56           1 :   rc.h_contract_terms = refund->h_contract_terms;
      57           1 :   rc.coin_pub = refund->coin.coin_pub;
      58           1 :   rc.merchant = refund->merchant_pub;
      59           1 :   rc.rtransaction_id = GNUNET_htonll (refund->rtransaction_id);
      60           1 :   TALER_amount_hton (&rc.refund_amount,
      61             :                      &refund->refund_amount);
      62           1 :   TALER_amount_hton (&rc.refund_fee,
      63             :                      &refund->refund_fee);
      64           1 :   TEH_KS_sign (&rc.purpose,
      65             :                &pub,
      66             :                &sig);
      67           1 :   return TEH_RESPONSE_reply_json_pack (connection,
      68             :                                        MHD_HTTP_OK,
      69             :                                        "{s:s, s:o, s:o}",
      70             :                                        "status", "REFUND_OK",
      71             :                                        "sig", GNUNET_JSON_from_data_auto (&sig),
      72             :                                        "pub", GNUNET_JSON_from_data_auto (&pub));
      73             : }
      74             : 
      75             : 
      76             : /**
      77             :  * Generate generic refund failure message. All the details
      78             :  * are in the @a response_code.  The body can be empty.
      79             :  *
      80             :  * @param connection connection to the client
      81             :  * @param response_code response code to generate
      82             :  * @param ec taler error code to include
      83             :  * @return MHD result code
      84             :  */
      85             : static int
      86           1 : reply_refund_failure (struct MHD_Connection *connection,
      87             :                       unsigned int response_code,
      88             :                       enum TALER_ErrorCode ec)
      89             : {
      90           1 :   return TEH_RESPONSE_reply_json_pack (connection,
      91             :                                        response_code,
      92             :                                        "{s:s, s:I}",
      93             :                                        "status", "refund failure",
      94             :                                        "code", (json_int_t) ec);
      95             : }
      96             : 
      97             : 
      98             : /**
      99             :  * Generate refund conflict failure message. Returns the
     100             :  * transaction list @a tl with the details about the conflict.
     101             :  *
     102             :  * @param connection connection to the client
     103             :  * @param tl transaction list showing the conflict
     104             :  * @return MHD result code
     105             :  */
     106             : static int
     107           0 : reply_refund_conflict (struct MHD_Connection *connection,
     108             :                        const struct TALER_EXCHANGEDB_TransactionList *tl)
     109             : {
     110           0 :   return TEH_RESPONSE_reply_json_pack (connection,
     111             :                                        MHD_HTTP_CONFLICT,
     112             :                                        "{s:s, s:I, s:o}",
     113             :                                        "status", "conflicting refund",
     114             :                                        "code", (json_int_t) TALER_EC_REFUND_CONFLICT,
     115             :                                        "history", TEH_RESPONSE_compile_transaction_history (tl));
     116             : }
     117             : 
     118             : 
     119             : /**
     120             :  * Execute a "/refund" transaction.  Returns a confirmation that the
     121             :  * refund was successful, or a failure if we are not aware of a
     122             :  * matching /deposit or if it is too late to do the refund.
     123             :  *
     124             :  * IF it returns a non-error code, the transaction logic MUST
     125             :  * NOT queue a MHD response.  IF it returns an hard error, the
     126             :  * transaction logic MUST queue a MHD response and set @a mhd_ret.  IF
     127             :  * it returns the soft error code, the function MAY be called again to
     128             :  * retry and MUST not queue a MHD response.
     129             :  *
     130             :  * @param cls closure with a `const struct TALER_EXCHANGEDB_Refund *`
     131             :  * @param connection MHD request which triggered the transaction
     132             :  * @param session database session to use
     133             :  * @param[out] mhd_ret set to MHD response status for @a connection,
     134             :  *             if transaction failed (!)
     135             :  * @return transaction status
     136             :  */
     137             : static enum GNUNET_DB_QueryStatus
     138           2 : refund_transaction (void *cls,
     139             :                     struct MHD_Connection *connection,
     140             :                     struct TALER_EXCHANGEDB_Session *session,
     141             :                     int *mhd_ret)
     142             : {
     143           2 :   const struct TALER_EXCHANGEDB_Refund *refund = cls;
     144             :   struct TALER_EXCHANGEDB_TransactionList *tl;
     145             :   const struct TALER_EXCHANGEDB_Deposit *dep;
     146             :   const struct TALER_EXCHANGEDB_Refund *ref;
     147             :   struct TEH_KS_StateHandle *mks;
     148             :   struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
     149             :   struct TALER_Amount expect_fee;
     150             :   enum GNUNET_DB_QueryStatus qs;
     151             :   int deposit_found;
     152             :   int refund_found;
     153             :   int fee_cmp;
     154             : 
     155           2 :   dep = NULL;
     156           2 :   ref = NULL;
     157           2 :   tl = NULL;
     158           2 :   qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
     159             :                                           session,
     160             :                                           &refund->coin.coin_pub,
     161             :                                           &tl);
     162           2 :   if (0 > qs)
     163             :   {
     164           0 :     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     165           0 :       *mhd_ret = reply_refund_failure (connection,
     166             :                                        MHD_HTTP_NOT_FOUND,
     167             :                                        TALER_EC_REFUND_COIN_NOT_FOUND);
     168           0 :     return qs;
     169             :   }
     170           2 :   deposit_found = GNUNET_NO;
     171           2 :   refund_found = GNUNET_NO;
     172           8 :   for (struct TALER_EXCHANGEDB_TransactionList *tlp = tl;
     173             :        NULL != tlp;
     174           4 :        tlp = tlp->next)
     175             :   {
     176           4 :     switch (tlp->type)
     177             :     {
     178             :     case TALER_EXCHANGEDB_TT_DEPOSIT:
     179           3 :       if (GNUNET_NO == deposit_found)
     180             :       {
     181           3 :         if ( (0 == memcmp (&tlp->details.deposit->merchant_pub,
     182           3 :                            &refund->merchant_pub,
     183           2 :                            sizeof (struct TALER_MerchantPublicKeyP))) &&
     184           2 :              (0 == memcmp (&tlp->details.deposit->h_contract_terms,
     185           2 :                            &refund->h_contract_terms,
     186             :                            sizeof (struct GNUNET_HashCode))) )
     187             :         {
     188           2 :           dep = tlp->details.deposit;
     189           2 :           deposit_found = GNUNET_YES;
     190           2 :           break;
     191             :         }
     192             :       }
     193           1 :       break;
     194             :     case TALER_EXCHANGEDB_TT_REFRESH_MELT:
     195             :       /* Melts cannot be refunded, ignore here */
     196           0 :       break;
     197             :     case TALER_EXCHANGEDB_TT_REFUND:
     198           1 :       if (GNUNET_NO == refund_found)
     199             :       {
     200             :         /* First, check if existing refund request is identical */
     201           1 :         if ( (0 == memcmp (&tlp->details.refund->merchant_pub,
     202           1 :                            &refund->merchant_pub,
     203           0 :                            sizeof (struct TALER_MerchantPublicKeyP))) &&
     204           0 :              (0 == memcmp (&tlp->details.refund->h_contract_terms,
     205           0 :                            &refund->h_contract_terms,
     206           0 :                            sizeof (struct GNUNET_HashCode))) &&
     207           0 :              (tlp->details.refund->rtransaction_id == refund->rtransaction_id) )
     208             :         {
     209           0 :           ref = tlp->details.refund;
     210           0 :           refund_found = GNUNET_YES;
     211           0 :           break;
     212             :         }
     213             :         /* Second, check if existing refund request conflicts */
     214           1 :         if ( (0 == memcmp (&tlp->details.refund->merchant_pub,
     215           1 :                            &refund->merchant_pub,
     216           0 :                            sizeof (struct TALER_MerchantPublicKeyP))) &&
     217           0 :              (0 == memcmp (&tlp->details.refund->h_contract_terms,
     218           0 :                            &refund->h_contract_terms,
     219           0 :                            sizeof (struct GNUNET_HashCode))) &&
     220           0 :              (tlp->details.refund->rtransaction_id != refund->rtransaction_id) )
     221             :         {
     222           0 :           GNUNET_break_op (0); /* conflicting refund found */
     223           0 :           refund_found = GNUNET_SYSERR;
     224             :           /* NOTE: Alternatively we could total up all existing
     225             :              refunds and check if the sum still permits the
     226             :              refund requested (thus allowing multiple, partial
     227             :              refunds). Fow now, we keep it simple. */
     228           0 :           break;
     229             :         }
     230             :       }
     231           1 :       break;
     232             :     case TALER_EXCHANGEDB_TT_PAYBACK:
     233             :       /* Paybacks cannot be refunded, ignore here */
     234           0 :       break;
     235             :     }
     236             :   }
     237             :   /* handle if deposit was NOT found */
     238           2 :   if (GNUNET_NO == deposit_found)
     239             :   {
     240           0 :     TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
     241             :                                             tl);
     242           0 :     *mhd_ret = TEH_RESPONSE_reply_transaction_unknown (connection,
     243             :                                                        TALER_EC_REFUND_DEPOSIT_NOT_FOUND);
     244           0 :     return GNUNET_DB_STATUS_HARD_ERROR;    
     245             :   }
     246             :   /* handle if conflicting refund found */
     247           2 :   if (GNUNET_SYSERR == refund_found)
     248             :   {
     249           0 :     *mhd_ret = reply_refund_conflict (connection,
     250             :                                       tl);
     251           0 :     TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
     252             :                                             tl);
     253           0 :     return GNUNET_DB_STATUS_HARD_ERROR; 
     254             :   }
     255             :   /* handle if identical refund found */
     256           2 :   if (GNUNET_YES == refund_found)
     257             :   {
     258             :     /* /refund already done, simply re-transmit confirmation */
     259           0 :     *mhd_ret = reply_refund_success (connection,
     260             :                                      ref);
     261           0 :     TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
     262             :                                             tl);
     263           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     264             :   }
     265             : 
     266             :   /* check currency is compatible */
     267           2 :   if ( (GNUNET_YES !=
     268           2 :         TALER_amount_cmp_currency (&refund->refund_amount,
     269           2 :                                    &dep->amount_with_fee)) ||
     270             :        (GNUNET_YES !=
     271           2 :         TALER_amount_cmp_currency (&refund->refund_fee,
     272             :                                    &dep->deposit_fee)) )
     273             :   {
     274           0 :     GNUNET_break_op (0); /* currency missmatch */
     275           0 :     *mhd_ret = reply_refund_failure (connection,
     276             :                                      MHD_HTTP_PRECONDITION_FAILED,
     277             :                                      TALER_EC_REFUND_CURRENCY_MISSMATCH);
     278           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     279             :   }
     280             : 
     281             :   /* check if we already send the money for the /deposit */
     282             :   // FIXME: DB API...
     283           2 :   qs = TEH_plugin->test_deposit_done (TEH_plugin->cls,
     284             :                                       session,
     285             :                                       dep);
     286           2 :   if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     287             :   {
     288             :     /* Internal error, we first had the deposit in the history,
     289             :        but now it is gone? */
     290           0 :     GNUNET_break (0);
     291           0 :     TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
     292             :                                             tl);
     293           0 :     *mhd_ret = TEH_RESPONSE_reply_internal_error (connection,
     294             :                                                   TALER_EC_REFUND_DB_INCONSISTENT,
     295             :                                                   "database inconsistent");
     296           0 :     return qs;
     297             :   }
     298           2 :   if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     299           0 :     return qs; /* go and retry */
     300             :   
     301           2 :   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     302             :   {
     303             :     /* money was already transferred to merchant, can no longer refund */
     304           1 :     TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
     305             :                                             tl);
     306           1 :     *mhd_ret = reply_refund_failure (connection,
     307             :                                      MHD_HTTP_GONE,
     308             :                                      TALER_EC_REFUND_MERCHANT_ALREADY_PAID);
     309           1 :     return GNUNET_DB_STATUS_HARD_ERROR;
     310             :   }
     311             : 
     312             :   /* check refund amount is sufficiently low */
     313           1 :   if (1 == TALER_amount_cmp (&refund->refund_amount,
     314             :                              &dep->amount_with_fee) )
     315             :   {
     316           0 :     GNUNET_break_op (0); /* cannot refund more than original value */
     317           0 :     TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
     318             :                                             tl);
     319           0 :     *mhd_ret = reply_refund_failure (connection,
     320             :                                      MHD_HTTP_PRECONDITION_FAILED,
     321             :                                      TALER_EC_REFUND_INSUFFICIENT_FUNDS);
     322           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     323             :   }
     324             : 
     325             :   // FIXME: do this outside of transaction function?
     326             :   /* Check refund fee matches fee of denomination key! */
     327           1 :   mks = TEH_KS_acquire ();
     328           1 :   dki = TEH_KS_denomination_key_lookup (mks,
     329             :                                         &dep->coin.denom_pub,
     330             :                                         TEH_KS_DKU_DEPOSIT);
     331           1 :   if (NULL == dki)
     332             :   {
     333             :     /* DKI not found, but we do have a coin with this DK in our database;
     334             :        not good... */
     335           0 :     GNUNET_break (0);
     336           0 :     TEH_KS_release (mks);
     337           0 :     TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
     338             :                                             tl);
     339           0 :     *mhd_ret = TEH_RESPONSE_reply_internal_error (connection,
     340             :                                                   TALER_EC_REFUND_DENOMINATION_KEY_NOT_FOUND,
     341             :                                                   "denomination key not found");
     342           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     343             :   }
     344           1 :   TALER_amount_ntoh (&expect_fee,
     345           1 :                      &dki->issue.properties.fee_refund);
     346           1 :   fee_cmp = TALER_amount_cmp (&refund->refund_fee,
     347             :                               &expect_fee);
     348           1 :   TEH_KS_release (mks);
     349             : 
     350           1 :   if (-1 == fee_cmp)
     351             :   {
     352           0 :     TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
     353             :                                             tl);
     354           0 :     *mhd_ret = TEH_RESPONSE_reply_arg_invalid (connection,
     355             :                                                TALER_EC_REFUND_FEE_TOO_LOW,
     356             :                                                "refund_fee");
     357           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     358             :   }
     359           1 :   if (1 == fee_cmp)
     360             :   {
     361           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     362             :                 "Refund fee proposed by merchant is higher than necessary.\n");
     363             :   }
     364           1 :   TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
     365             :                                           tl);
     366             : 
     367             :   /* Finally, store new refund data */
     368           1 :   qs = TEH_plugin->insert_refund (TEH_plugin->cls,
     369             :                                   session,
     370             :                                   refund);
     371           1 :   if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     372             :   {
     373           0 :     TALER_LOG_WARNING ("Failed to store /refund information in database\n");
     374           0 :     *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     375             :                                                      TALER_EC_REFUND_STORE_DB_ERROR);
     376           0 :     return qs;
     377             :   }
     378             :   /* Success or soft failure */
     379           1 :   return qs;
     380             : }
     381             : 
     382             : 
     383             : /**
     384             :  * We have parsed the JSON information about the refund, do some basic
     385             :  * sanity checks (especially that the signature on the coin is valid)
     386             :  * and then execute the refund.  Note that we need the DB to check
     387             :  * the fee structure, so this is not done here.
     388             :  *
     389             :  * @param connection the MHD connection to handle
     390             :  * @param refund information about the refund
     391             :  * @return MHD result code
     392             :  */
     393             : static int
     394           2 : verify_and_execute_refund (struct MHD_Connection *connection,
     395             :                            const struct TALER_EXCHANGEDB_Refund *refund)
     396             : {
     397             :   struct TALER_RefundRequestPS rr;
     398             :   int mhd_ret;
     399             : 
     400           2 :   rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND);
     401           2 :   rr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS));
     402           2 :   rr.h_contract_terms = refund->h_contract_terms;
     403           2 :   rr.coin_pub = refund->coin.coin_pub;
     404           2 :   rr.merchant = refund->merchant_pub;
     405           2 :   rr.rtransaction_id = GNUNET_htonll (refund->rtransaction_id);
     406           2 :   TALER_amount_hton (&rr.refund_amount,
     407             :                      &refund->refund_amount);
     408           2 :   TALER_amount_hton (&rr.refund_fee,
     409             :                      &refund->refund_fee);
     410           2 :   if (GNUNET_YES !=
     411           2 :       TALER_amount_cmp_currency (&refund->refund_amount,
     412             :                                  &refund->refund_fee) )
     413             :   {
     414           0 :     GNUNET_break_op (0);
     415           0 :     return TEH_RESPONSE_reply_arg_invalid (connection,
     416             :                                            TALER_EC_REFUND_FEE_CURRENCY_MISSMATCH,
     417             :                                            "refund_fee");
     418             :   }
     419           2 :   if (-1 == TALER_amount_cmp (&refund->refund_amount,
     420             :                               &refund->refund_fee) )
     421             :   {
     422           0 :     GNUNET_break_op (0);
     423           0 :     return TEH_RESPONSE_reply_arg_invalid (connection,
     424             :                                            TALER_EC_REFUND_FEE_ABOVE_AMOUNT,
     425             :                                            "refund_amount");
     426             :   }
     427           2 :   if (GNUNET_OK !=
     428           2 :       GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
     429             :                                   &rr.purpose,
     430             :                                   &refund->merchant_sig.eddsa_sig,
     431             :                                   &refund->merchant_pub.eddsa_pub))
     432             :   {
     433           0 :     TALER_LOG_WARNING ("Invalid signature on /refund request\n");
     434           0 :     return TEH_RESPONSE_reply_signature_invalid (connection,
     435             :                                                  TALER_EC_REFUND_MERCHANT_SIGNATURE_INVALID,
     436             :                                                  "merchant_sig");
     437             :   }
     438           2 :   if (GNUNET_OK !=
     439           2 :       TEH_DB_run_transaction (connection,
     440             :                               &mhd_ret,
     441             :                               &refund_transaction,
     442             :                               (void *) refund))
     443           1 :     return mhd_ret;
     444           1 :   return reply_refund_success (connection,
     445             :                                refund);
     446             : }
     447             : 
     448             : 
     449             : /**
     450             :  * Handle a "/refund" request.  Parses the JSON, and, if successful,
     451             :  * passes the JSON data to #verify_and_execute_refund() to
     452             :  * further check the details of the operation specified.  If
     453             :  * everything checks out, this will ultimately lead to the "/refund"
     454             :  * being executed, or rejected.
     455             :  *
     456             :  * @param rh context of the handler
     457             :  * @param connection the MHD connection to handle
     458             :  * @param[in,out] connection_cls the connection's closure (can be updated)
     459             :  * @param upload_data upload data
     460             :  * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
     461             :  * @return MHD result code
     462             :   */
     463             : int
     464           6 : TEH_REFUND_handler_refund (struct TEH_RequestHandler *rh,
     465             :                            struct MHD_Connection *connection,
     466             :                            void **connection_cls,
     467             :                            const char *upload_data,
     468             :                            size_t *upload_data_size)
     469             : {
     470             :   json_t *json;
     471             :   int res;
     472             :   struct TALER_EXCHANGEDB_Refund refund;
     473           6 :   struct GNUNET_JSON_Specification spec[] = {
     474             :     TALER_JSON_spec_amount ("refund_amount", &refund.refund_amount),
     475             :     TALER_JSON_spec_amount ("refund_fee", &refund.refund_fee),
     476             :     GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &refund.h_contract_terms),
     477             :     GNUNET_JSON_spec_fixed_auto ("coin_pub", &refund.coin.coin_pub),
     478             :     GNUNET_JSON_spec_fixed_auto ("merchant_pub", &refund.merchant_pub),
     479             :     GNUNET_JSON_spec_uint64 ("rtransaction_id", &refund.rtransaction_id),
     480             :     GNUNET_JSON_spec_fixed_auto ("merchant_sig", &refund.merchant_sig),
     481             :     GNUNET_JSON_spec_end ()
     482             :   };
     483             : 
     484           6 :   res = TEH_PARSE_post_json (connection,
     485             :                              connection_cls,
     486             :                              upload_data,
     487             :                              upload_data_size,
     488             :                              &json);
     489           6 :   if (GNUNET_SYSERR == res)
     490           0 :     return MHD_NO;
     491           6 :   if ( (GNUNET_NO == res) || (NULL == json) )
     492           4 :     return MHD_YES;
     493           2 :   res = TEH_PARSE_json_data (connection,
     494             :                              json,
     495             :                              spec);
     496           2 :   json_decref (json);
     497           2 :   if (GNUNET_SYSERR == res)
     498           0 :     return MHD_NO; /* hard failure */
     499           2 :   if (GNUNET_NO == res)
     500           0 :     return MHD_YES; /* failure */
     501           2 :   res = verify_and_execute_refund (connection,
     502             :                                    &refund);
     503           2 :   GNUNET_JSON_parse_free (spec);
     504           2 :   return res;
     505             : }
     506             : 
     507             : 
     508             : /* end of taler-exchange-httpd_refund.c */

Generated by: LCOV version 1.13