LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_post-orders-ID-refund.c (source / functions) Hit Total Coverage
Test: GNU Taler merchant coverage report Lines: 141 188 75.0 %
Date: 2021-08-30 06:54:17 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   (C) 2020-2021 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify
       6             :   it under the terms of the GNU Affero General Public License as
       7             :   published by the Free Software Foundation; either version 3,
       8             :   or (at your option) any later version.
       9             : 
      10             :   TALER is distributed in the hope that it will be useful, but
      11             :   WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :   GNU General Public License for more details.
      14             : 
      15             :   You should have received a copy of the GNU General Public
      16             :   License along with TALER; see the file COPYING.  If not,
      17             :   see <http://www.gnu.org/licenses/>
      18             : */
      19             : 
      20             : /**
      21             :  * @file taler-merchant-httpd_post-orders-ID-refund.c
      22             :  * @brief handling of POST /orders/$ID/refund requests
      23             :  * @author Jonathan Buchanan
      24             :  */
      25             : #include "platform.h"
      26             : #include <taler/taler_dbevents.h>
      27             : #include <taler/taler_signatures.h>
      28             : #include <taler/taler_json_lib.h>
      29             : #include <taler/taler_exchange_service.h>
      30             : #include "taler-merchant-httpd.h"
      31             : #include "taler-merchant-httpd_auditors.h"
      32             : #include "taler-merchant-httpd_exchanges.h"
      33             : #include "taler-merchant-httpd_post-orders-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_FindOperation *fo;
      56             : 
      57             :   /**
      58             :    * Handle for the refund operation with the exchange.
      59             :    */
      60             :   struct TALER_EXCHANGE_RefundHandle *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_Absolute 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 GNUNET_HashCode 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?
     205             :    */
     206             :   bool suspended;
     207             : 
     208             :   /**
     209             :    * Return code: #TALER_EC_NONE if successful.
     210             :    */
     211             :   enum TALER_ErrorCode ec;
     212             : 
     213             :   /**
     214             :    * HTTP status to use for the reply, 0 if not yet known.
     215             :    */
     216             :   unsigned int http_status;
     217             : 
     218             :   /**
     219             :    * Set to true if we are dealing with an unclaimed order
     220             :    * (and thus @e h_contract_terms is not set, and certain
     221             :    * DB queries will not work).
     222             :    */
     223             :   bool unclaimed;
     224             : 
     225             :   /**
     226             :    * Set to true if this payment has been refunded and
     227             :    * @e refund_amount is initialized.
     228             :    */
     229             :   bool refunded;
     230             : 
     231             :   /**
     232             :    * Set to true if a refund is still available for the
     233             :    * wallet for this payment.
     234             :    */
     235             :   bool refund_available;
     236             : 
     237             :   /**
     238             :    * Set to true if the client requested HTML, otherwise
     239             :    * we generate JSON.
     240             :    */
     241             :   bool generate_html;
     242             : 
     243             : };
     244             : 
     245             : 
     246             : /**
     247             :  * Head of DLL of (suspended) requests.
     248             :  */
     249             : static struct PostRefundData *prd_head;
     250             : 
     251             : /**
     252             :  * Tail of DLL of (suspended) requests.
     253             :  */
     254             : static struct PostRefundData *prd_tail;
     255             : 
     256             : 
     257             : /**
     258             :  * Function called when we are done processing a refund request.
     259             :  * Frees memory associated with @a ctx.
     260             :  *
     261             :  * @param ctx a `struct PostRefundData`
     262             :  */
     263             : static void
     264           1 : refund_cleanup (void *ctx)
     265             : {
     266           1 :   struct PostRefundData *prd = ctx;
     267             :   struct CoinRefund *cr;
     268             : 
     269           3 :   while (NULL != (cr = prd->cr_head))
     270             :   {
     271           2 :     GNUNET_CONTAINER_DLL_remove (prd->cr_head,
     272             :                                  prd->cr_tail,
     273             :                                  cr);
     274           2 :     json_decref (cr->exchange_reply);
     275           2 :     GNUNET_free (cr->exchange_url);
     276           2 :     if (NULL != cr->fo)
     277             :     {
     278           0 :       TMH_EXCHANGES_find_exchange_cancel (cr->fo);
     279           0 :       cr->fo = NULL;
     280             :     }
     281           2 :     if (NULL != cr->rh)
     282             :     {
     283           0 :       TALER_EXCHANGE_refund_cancel (cr->rh);
     284           0 :       cr->rh = NULL;
     285             :     }
     286           2 :     GNUNET_free (cr);
     287             :   }
     288           1 :   json_decref (prd->contract_terms);
     289           1 :   GNUNET_free (prd);
     290           1 : }
     291             : 
     292             : 
     293             : /**
     294             :  * Force resuming all suspended order lookups, needed during shutdown.
     295             :  */
     296             : void
     297          16 : TMH_force_wallet_refund_order_resume (void)
     298             : {
     299             :   struct PostRefundData *prd;
     300             : 
     301          16 :   while (NULL != (prd = prd_head))
     302             :   {
     303           0 :     GNUNET_CONTAINER_DLL_remove (prd_head,
     304             :                                  prd_tail,
     305             :                                  prd);
     306           0 :     GNUNET_assert (prd->suspended);
     307           0 :     prd->suspended = false;
     308           0 :     MHD_resume_connection (prd->sc.con);
     309             :   }
     310          16 : }
     311             : 
     312             : 
     313             : /**
     314             :  * Check if @a prd has exchange requests still pending.
     315             :  *
     316             :  * @param prd state to check
     317             :  * @return true if activities are still pending
     318             :  */
     319             : static bool
     320           4 : exchange_operations_pending (struct PostRefundData *prd)
     321             : {
     322           8 :   for (struct CoinRefund *cr = prd->cr_head;
     323             :        NULL != cr;
     324           4 :        cr = cr->next)
     325             :   {
     326           6 :     if ( (NULL != cr->fo) ||
     327           5 :          (NULL != cr->rh) )
     328           2 :       return true;
     329             :   }
     330           2 :   return false;
     331             : }
     332             : 
     333             : 
     334             : /**
     335             :  * Check if @a prd is ready to be resumed, and if so, do it.
     336             :  *
     337             :  * @param prd refund request to be possibly ready
     338             :  */
     339             : static void
     340           2 : check_resume_prd (struct PostRefundData *prd)
     341             : {
     342           4 :   if ( (TALER_EC_NONE == prd->ec) &&
     343           2 :        exchange_operations_pending (prd) )
     344           1 :     return;
     345           1 :   GNUNET_CONTAINER_DLL_remove (prd_head,
     346             :                                prd_tail,
     347             :                                prd);
     348           1 :   GNUNET_assert (prd->suspended);
     349           1 :   prd->suspended = false;
     350           1 :   MHD_resume_connection (prd->sc.con);
     351           1 :   TALER_MHD_daemon_trigger ();
     352             : }
     353             : 
     354             : 
     355             : /**
     356             :  * Notify applications waiting for a client to obtain
     357             :  * a refund.
     358             :  *
     359             :  * @param prd refund request with the change
     360             :  */
     361             : static void
     362           2 : notify_refund_obtained (struct PostRefundData *prd)
     363             : {
     364           2 :   struct TMH_OrderPayEventP refund_eh = {
     365           2 :     .header.size = htons (sizeof (refund_eh)),
     366           2 :     .header.type = htons (TALER_DBEVENT_MERCHANT_REFUND_OBTAINED),
     367           2 :     .merchant_pub = prd->hc->instance->merchant_pub
     368             :   };
     369             : 
     370           2 :   GNUNET_CRYPTO_hash (prd->order_id,
     371             :                       strlen (prd->order_id),
     372             :                       &refund_eh.h_order_id);
     373           2 :   TMH_db->event_notify (TMH_db->cls,
     374             :                         &refund_eh.header,
     375             :                         NULL,
     376             :                         0);
     377           2 : }
     378             : 
     379             : 
     380             : /**
     381             :  * Callbacks of this type are used to serve the result of submitting a
     382             :  * refund request to an exchange.
     383             :  *
     384             :  * @param cls a `struct CoinRefund`
     385             :  * @param hr HTTP response data
     386             :  * @param exchange_pub exchange key used to sign refund confirmation
     387             :  * @param exchange_sig exchange's signature over refund
     388             :  */
     389             : static void
     390           2 : refund_cb (void *cls,
     391             :            const struct TALER_EXCHANGE_HttpResponse *hr,
     392             :            const struct TALER_ExchangePublicKeyP *exchange_pub,
     393             :            const struct TALER_ExchangeSignatureP *exchange_sig)
     394             : {
     395           2 :   struct CoinRefund *cr = cls;
     396             : 
     397           2 :   cr->rh = NULL;
     398           2 :   cr->exchange_status = hr->http_status;
     399           2 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     400             :               "Exchange refund status for coin %s is %u\n",
     401             :               TALER_B2S (&cr->coin_pub),
     402             :               hr->http_status);
     403           2 :   if (MHD_HTTP_OK != hr->http_status)
     404             :   {
     405           0 :     cr->exchange_code = hr->ec;
     406           0 :     cr->exchange_reply = json_incref ((json_t*) hr->reply);
     407             :   }
     408             :   else
     409             :   {
     410             :     enum GNUNET_DB_QueryStatus qs;
     411             : 
     412           2 :     cr->exchange_pub = *exchange_pub;
     413           2 :     cr->exchange_sig = *exchange_sig;
     414           2 :     qs = TMH_db->insert_refund_proof (TMH_db->cls,
     415             :                                       cr->refund_serial,
     416             :                                       exchange_sig,
     417             :                                       exchange_pub);
     418           2 :     if (0 >= qs)
     419             :     {
     420             :       /* generally, this is relatively harmless for the merchant, but let's at
     421             :          least log this. */
     422           0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     423             :                   "Failed to persist exchange response to /refund in database: %d\n",
     424             :                   qs);
     425             :     }
     426             :     else
     427             :     {
     428           2 :       notify_refund_obtained (cr->prd);
     429             :     }
     430             :   }
     431           2 :   check_resume_prd (cr->prd);
     432           2 : }
     433             : 
     434             : 
     435             : /**
     436             :  * Function called with the result of a #TMH_EXCHANGES_find_exchange()
     437             :  * operation.
     438             :  *
     439             :  * @param cls a `struct CoinRefund *`
     440             :  * @param hr HTTP response details
     441             :  * @param eh handle to the exchange context
     442             :  * @param payto_uri payto://-URI of the exchange
     443             :  * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
     444             :  * @param exchange_trusted true if this exchange is trusted by config
     445             :  */
     446             : static void
     447           2 : exchange_found_cb (void *cls,
     448             :                    const struct TALER_EXCHANGE_HttpResponse *hr,
     449             :                    struct TALER_EXCHANGE_Handle *eh,
     450             :                    const char *payto_uri,
     451             :                    const struct TALER_Amount *wire_fee,
     452             :                    bool exchange_trusted)
     453             : {
     454           2 :   struct CoinRefund *cr = cls;
     455           2 :   struct PostRefundData *prd = cr->prd;
     456             : 
     457             :   (void) payto_uri;
     458             :   (void) wire_fee;
     459             :   (void) exchange_trusted;
     460           2 :   cr->fo = NULL;
     461           2 :   if (NULL == hr)
     462             :   {
     463           0 :     prd->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
     464           0 :     prd->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT;
     465           0 :     check_resume_prd (prd);
     466           0 :     return;
     467             :   }
     468           2 :   if (NULL == eh)
     469             :   {
     470           0 :     prd->http_status = MHD_HTTP_BAD_GATEWAY;
     471           0 :     prd->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE;
     472           0 :     check_resume_prd (prd);
     473           0 :     return;
     474             :   }
     475           2 :   cr->rh = TALER_EXCHANGE_refund (eh,
     476           2 :                                   &cr->refund_amount,
     477           2 :                                   &prd->h_contract_terms,
     478           2 :                                   &cr->coin_pub,
     479             :                                   cr->rtransaction_id,
     480           2 :                                   &prd->hc->instance->merchant_priv,
     481             :                                   &refund_cb,
     482             :                                   cr);
     483             : }
     484             : 
     485             : 
     486             : /**
     487             :  * Function called with information about a refund.
     488             :  * It is responsible for summing up the refund amount.
     489             :  *
     490             :  * @param cls closure
     491             :  * @param refund_serial unique serial number of the refund
     492             :  * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
     493             :  * @param coin_pub public coin from which the refund comes from
     494             :  * @param exchange_url URL of the exchange that issued @a coin_pub
     495             :  * @param rtransaction_id identificator of the refund
     496             :  * @param reason human-readable explanation of the refund
     497             :  * @param refund_amount refund amount which is being taken from @a coin_pub
     498             :  * @param pending true if the this refund was not yet processed by the wallet/exchange
     499             :  */
     500             : static void
     501           4 : process_refunds_cb (void *cls,
     502             :                     uint64_t refund_serial,
     503             :                     struct GNUNET_TIME_Absolute timestamp,
     504             :                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
     505             :                     const char *exchange_url,
     506             :                     uint64_t rtransaction_id,
     507             :                     const char *reason,
     508             :                     const struct TALER_Amount *refund_amount,
     509             :                     bool pending)
     510             : {
     511           4 :   struct PostRefundData *prd = cls;
     512             :   struct CoinRefund *cr;
     513             : 
     514           6 :   for (cr = prd->cr_head;
     515             :        NULL != cr;
     516           2 :        cr = cr->next)
     517           4 :     if (cr->refund_serial == refund_serial)
     518           2 :       return;
     519             :   /* already known */
     520             : 
     521           2 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     522             :               "Found refund of %s for coin %s with reason `%s' in database\n",
     523             :               TALER_amount2s (refund_amount),
     524             :               TALER_B2S (coin_pub),
     525             :               reason);
     526           2 :   cr = GNUNET_new (struct CoinRefund);
     527           2 :   cr->refund_serial = refund_serial;
     528           2 :   cr->exchange_url = GNUNET_strdup (exchange_url);
     529           2 :   cr->prd = prd;
     530           2 :   cr->coin_pub = *coin_pub;
     531           2 :   cr->rtransaction_id = rtransaction_id;
     532           2 :   cr->refund_amount = *refund_amount;
     533           2 :   cr->execution_time = timestamp;
     534           2 :   GNUNET_CONTAINER_DLL_insert (prd->cr_head,
     535             :                                prd->cr_tail,
     536             :                                cr);
     537           2 :   if (prd->refunded)
     538             :   {
     539           1 :     GNUNET_assert (0 <=
     540             :                    TALER_amount_add (&prd->refund_amount,
     541             :                                      &prd->refund_amount,
     542             :                                      refund_amount));
     543           1 :     return;
     544             :   }
     545           1 :   prd->refund_amount = *refund_amount;
     546           1 :   prd->refunded = true;
     547           1 :   prd->refund_available |= pending;
     548             : }
     549             : 
     550             : 
     551             : /**
     552             :  * Obtain refunds for an order.
     553             :  *
     554             :  * @param rh context of the handler
     555             :  * @param connection the MHD connection to handle
     556             :  * @param[in,out] hc context with further information about the request
     557             :  * @return MHD result code
     558             :  */
     559             : MHD_RESULT
     560           2 : TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
     561             :                            struct MHD_Connection *connection,
     562             :                            struct TMH_HandlerContext *hc)
     563             : {
     564           2 :   struct PostRefundData *prd = hc->ctx;
     565             :   enum GNUNET_DB_QueryStatus qs;
     566             : 
     567           2 :   if (NULL == prd)
     568             :   {
     569           1 :     prd = GNUNET_new (struct PostRefundData);
     570           1 :     prd->sc.con = connection;
     571           1 :     prd->hc = hc;
     572           1 :     prd->order_id = hc->infix;
     573           1 :     hc->ctx = prd;
     574           1 :     hc->cc = &refund_cleanup;
     575             :     {
     576             :       enum GNUNET_GenericReturnValue res;
     577             : 
     578             :       struct GNUNET_JSON_Specification spec[] = {
     579           1 :         GNUNET_JSON_spec_fixed_auto ("h_contract", &prd->h_contract_terms),
     580           1 :         GNUNET_JSON_spec_end ()
     581             :       };
     582           1 :       res = TALER_MHD_parse_json_data (connection,
     583           1 :                                        hc->request_body,
     584             :                                        spec);
     585           1 :       if (GNUNET_OK != res)
     586             :         return (GNUNET_NO == res)
     587             :                ? MHD_YES
     588           0 :                : MHD_NO;
     589             :     }
     590             : 
     591           1 :     TMH_db->preflight (TMH_db->cls);
     592             :     {
     593             :       json_t *contract_terms;
     594             :       uint64_t order_serial;
     595             : 
     596           1 :       qs = TMH_db->lookup_contract_terms (TMH_db->cls,
     597           1 :                                           hc->instance->settings.id,
     598           1 :                                           hc->infix,
     599             :                                           &contract_terms,
     600             :                                           &order_serial,
     601             :                                           NULL);
     602           1 :       if (0 > qs)
     603             :       {
     604             :         /* single, read-only SQL statements should never cause
     605             :            serialization problems */
     606           0 :         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
     607             :         /* Always report on hard error as well to enable diagnostics */
     608           0 :         GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
     609           0 :         return TALER_MHD_reply_with_error (connection,
     610             :                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
     611             :                                            TALER_EC_GENERIC_DB_FETCH_FAILED,
     612             :                                            "contract terms");
     613             :       }
     614           1 :       if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     615             :       {
     616           0 :         json_decref (contract_terms);
     617           0 :         return TALER_MHD_reply_with_error (connection,
     618             :                                            MHD_HTTP_NOT_FOUND,
     619             :                                            TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
     620           0 :                                            hc->infix);
     621             :       }
     622             :       {
     623             :         struct GNUNET_HashCode h_contract_terms;
     624           1 :         if (GNUNET_OK !=
     625           1 :             TALER_JSON_contract_hash (contract_terms,
     626             :                                       &h_contract_terms))
     627             :         {
     628           0 :           GNUNET_break (0);
     629           0 :           json_decref (contract_terms);
     630           0 :           return TALER_MHD_reply_with_error (connection,
     631             :                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
     632             :                                              TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
     633             :                                              NULL);
     634             :         }
     635           1 :         json_decref (contract_terms);
     636           1 :         if (0 != GNUNET_memcmp (&h_contract_terms,
     637             :                                 &prd->h_contract_terms))
     638             :         {
     639           0 :           return TALER_MHD_reply_with_error (
     640             :             connection,
     641             :             MHD_HTTP_FORBIDDEN,
     642             :             TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
     643             :             NULL);
     644             :         }
     645             :       }
     646             :     }
     647             :   }
     648             : 
     649           2 :   if (TALER_EC_NONE != prd->ec)
     650             :   {
     651           0 :     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           0 :     for (struct CoinRefund *cr = prd->cr_head;
     656             :          NULL != cr;
     657           0 :          cr = cr->next)
     658             :     {
     659           0 :       if (NULL != cr->fo)
     660             :       {
     661           0 :         TMH_EXCHANGES_find_exchange_cancel (cr->fo);
     662           0 :         cr->fo = NULL;
     663             :       }
     664           0 :       if (NULL != cr->rh)
     665             :       {
     666           0 :         TALER_EXCHANGE_refund_cancel (cr->rh);
     667           0 :         cr->rh = NULL;
     668             :       }
     669             :     }
     670           0 :     return TALER_MHD_reply_with_error (connection,
     671             :                                        prd->http_status,
     672             :                                        prd->ec,
     673             :                                        NULL);
     674             :   }
     675             : 
     676             :   {
     677           2 :     GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (TMH_currency,
     678             :                                                        &prd->refund_amount));
     679           2 :     qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
     680           2 :                                           hc->instance->settings.id,
     681           2 :                                           &prd->h_contract_terms,
     682             :                                           &process_refunds_cb,
     683             :                                           prd);
     684           2 :     if (0 > qs)
     685             :     {
     686           0 :       GNUNET_break (0);
     687           0 :       return TALER_MHD_reply_with_error (connection,
     688             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     689             :                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
     690             :                                          "detailed refunds");
     691             :     }
     692             :   }
     693             : 
     694             :   /* Now launch exchange interactions, unless we already have the
     695             :      response in the database! */
     696           6 :   for (struct CoinRefund *cr = prd->cr_head;
     697             :        NULL != cr;
     698           4 :        cr = cr->next)
     699             :   {
     700             :     enum GNUNET_DB_QueryStatus qs;
     701             : 
     702           4 :     qs = TMH_db->lookup_refund_proof (TMH_db->cls,
     703             :                                       cr->refund_serial,
     704             :                                       &cr->exchange_sig,
     705             :                                       &cr->exchange_pub);
     706           4 :     switch (qs)
     707             :     {
     708           0 :     case GNUNET_DB_STATUS_HARD_ERROR:
     709             :     case GNUNET_DB_STATUS_SOFT_ERROR:
     710           0 :       return TALER_MHD_reply_with_error (connection,
     711             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     712             :                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
     713             :                                          "refund proof");
     714           2 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     715           2 :       if (NULL == cr->exchange_reply)
     716             :       {
     717             :         /* We need to talk to the exchange */
     718           2 :         cr->fo = TMH_EXCHANGES_find_exchange (cr->exchange_url,
     719             :                                               NULL,
     720             :                                               GNUNET_NO,
     721             :                                               &exchange_found_cb,
     722             :                                               cr);
     723             :       }
     724           2 :       break;
     725           2 :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     726             :       /* We got a reply earlier, set status accordingly */
     727           2 :       cr->exchange_status = MHD_HTTP_OK;
     728           2 :       break;
     729             :     }
     730           4 :   }
     731             : 
     732             :   /* Check if there are still exchange operations pending */
     733           2 :   if (exchange_operations_pending (prd))
     734             :   {
     735           1 :     if (! prd->suspended)
     736             :     {
     737           1 :       prd->suspended = true;
     738           1 :       MHD_suspend_connection (connection);
     739           1 :       GNUNET_CONTAINER_DLL_insert (prd_head,
     740             :                                    prd_tail,
     741             :                                    prd);
     742             :     }
     743           1 :     return MHD_YES;   /* we're still talking to the exchange */
     744             :   }
     745             : 
     746             :   {
     747             :     json_t *ra;
     748             : 
     749           1 :     ra = json_array ();
     750           1 :     GNUNET_assert (NULL != ra);
     751           3 :     for (struct CoinRefund *cr = prd->cr_head;
     752             :          NULL != cr;
     753           2 :          cr = cr->next)
     754             :     {
     755             :       json_t *refund;
     756             : 
     757           2 :       if (MHD_HTTP_OK != cr->exchange_status)
     758             :       {
     759           0 :         if (NULL == cr->exchange_reply)
     760             :         {
     761           0 :           refund = GNUNET_JSON_PACK (
     762             :             GNUNET_JSON_pack_string ("type",
     763             :                                      "failure"),
     764             :             GNUNET_JSON_pack_uint64 ("exchange_status",
     765             :                                      cr->exchange_status),
     766             :             GNUNET_JSON_pack_uint64 ("rtransaction_id",
     767             :                                      cr->rtransaction_id),
     768             :             GNUNET_JSON_pack_data_auto ("coin_pub",
     769             :                                         &cr->coin_pub),
     770             :             TALER_JSON_pack_amount ("refund_amount",
     771             :                                     &cr->refund_amount),
     772             :             GNUNET_JSON_pack_time_abs ("execution_time",
     773             :                                        cr->execution_time));
     774             :         }
     775             :         else
     776             :         {
     777           0 :           refund = GNUNET_JSON_PACK (
     778             :             GNUNET_JSON_pack_string ("type",
     779             :                                      "failure"),
     780             :             GNUNET_JSON_pack_uint64 ("exchange_status",
     781             :                                      cr->exchange_status),
     782             :             GNUNET_JSON_pack_uint64 ("exchange_code",
     783             :                                      cr->exchange_code),
     784             :             GNUNET_JSON_pack_object_incref ("exchange_reply",
     785             :                                             cr->exchange_reply),
     786             :             GNUNET_JSON_pack_uint64 ("rtransaction_id",
     787             :                                      cr->rtransaction_id),
     788             :             GNUNET_JSON_pack_data_auto ("coin_pub",
     789             :                                         &cr->coin_pub),
     790             :             TALER_JSON_pack_amount ("refund_amount",
     791             :                                     &cr->refund_amount),
     792             :             GNUNET_JSON_pack_time_abs ("execution_time",
     793             :                                        cr->execution_time));
     794             :         }
     795             :       }
     796             :       else
     797             :       {
     798           2 :         refund = GNUNET_JSON_PACK (
     799             :           GNUNET_JSON_pack_string ("type",
     800             :                                    "success"),
     801             :           GNUNET_JSON_pack_uint64 ("exchange_status",
     802             :                                    cr->exchange_status),
     803             :           GNUNET_JSON_pack_data_auto ("exchange_sig",
     804             :                                       &cr->exchange_sig),
     805             :           GNUNET_JSON_pack_data_auto ("exchange_pub",
     806             :                                       &cr->exchange_pub),
     807             :           GNUNET_JSON_pack_uint64 ("rtransaction_id",
     808             :                                    cr->rtransaction_id),
     809             :           GNUNET_JSON_pack_data_auto ("coin_pub",
     810             :                                       &cr->coin_pub),
     811             :           TALER_JSON_pack_amount ("refund_amount",
     812             :                                   &cr->refund_amount),
     813             :           GNUNET_JSON_pack_time_abs ("execution_time",
     814             :                                      cr->execution_time));
     815             :       }
     816           2 :       GNUNET_assert (
     817             :         0 ==
     818             :         json_array_append_new (ra,
     819             :                                refund));
     820             :     }
     821             : 
     822           1 :     return TALER_MHD_REPLY_JSON_PACK (
     823             :       connection,
     824             :       MHD_HTTP_OK,
     825             :       TALER_JSON_pack_amount ("refund_amount",
     826             :                               &prd->refund_amount),
     827             :       GNUNET_JSON_pack_array_steal ("refunds",
     828             :                                     ra),
     829             :       GNUNET_JSON_pack_data_auto ("merchant_pub",
     830             :                                   &hc->instance->merchant_pub));
     831             :   }
     832             : 
     833             :   return MHD_YES;
     834             : }
     835             : 
     836             : 
     837             : /* end of taler-merchant-httpd_post-orders-ID-refund.c */

Generated by: LCOV version 1.14