LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_post-orders-ID-refund.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 147 197 74.6 %
Date: 2025-06-23 16:22:09 Functions: 9 9 100.0 %

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

Generated by: LCOV version 1.16