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

            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           15 : TMH_force_wallet_refund_order_resume (void)
     301              : {
     302              :   struct PostRefundData *prd;
     303              : 
     304           15 :   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           15 : }
     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 2.0-1