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.1 % 201 149
Test Date: 2025-12-29 21:26:17 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 :   switch (hr->http_status)
     404              :   {
     405            4 :   case MHD_HTTP_OK:
     406              :     {
     407              :       enum GNUNET_DB_QueryStatus qs;
     408              : 
     409            4 :       cr->exchange_pub = rr->details.ok.exchange_pub;
     410            4 :       cr->exchange_sig = rr->details.ok.exchange_sig;
     411            4 :       qs = TMH_db->insert_refund_proof (TMH_db->cls,
     412              :                                         cr->refund_serial,
     413              :                                         &rr->details.ok.exchange_sig,
     414              :                                         &rr->details.ok.exchange_pub);
     415            4 :       if (0 >= qs)
     416              :       {
     417              :         /* generally, this is relatively harmless for the merchant, but let's at
     418              :            least log this. */
     419            0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     420              :                     "Failed to persist exchange response to /refund in database: %d\n",
     421              :                     qs);
     422              :       }
     423              :       else
     424              :       {
     425            4 :         notify_refund_obtained (cr->prd);
     426              :       }
     427              :     }
     428            4 :     break;
     429            0 :   default:
     430            0 :     cr->exchange_code = hr->ec;
     431            0 :     cr->exchange_reply = json_incref ((json_t*) hr->reply);
     432            0 :     break;
     433              :   }
     434            4 :   check_resume_prd (cr->prd);
     435            4 : }
     436              : 
     437              : 
     438              : /**
     439              :  * Function called with the result of a
     440              :  * #TMH_EXCHANGES_keys4exchange()
     441              :  * operation.
     442              :  *
     443              :  * @param cls a `struct CoinRefund *`
     444              :  * @param keys keys of exchange, NULL on error
     445              :  * @param exchange representation of the exchange
     446              :  */
     447              : static void
     448            4 : exchange_found_cb (void *cls,
     449              :                    struct TALER_EXCHANGE_Keys *keys,
     450              :                    struct TMH_Exchange *exchange)
     451              : {
     452            4 :   struct CoinRefund *cr = cls;
     453            4 :   struct PostRefundData *prd = cr->prd;
     454              : 
     455              :   (void) exchange;
     456            4 :   cr->fo = NULL;
     457            4 :   if (NULL == keys)
     458              :   {
     459            0 :     prd->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
     460            0 :     prd->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT;
     461            0 :     check_resume_prd (prd);
     462            0 :     return;
     463              :   }
     464            4 :   cr->rh = TALER_EXCHANGE_refund (
     465              :     TMH_curl_ctx,
     466            4 :     cr->exchange_url,
     467              :     keys,
     468            4 :     &cr->refund_amount,
     469            4 :     &prd->h_contract_terms,
     470            4 :     &cr->coin_pub,
     471              :     cr->rtransaction_id,
     472            4 :     &prd->hc->instance->merchant_priv,
     473              :     &refund_cb,
     474              :     cr);
     475              : }
     476              : 
     477              : 
     478              : /**
     479              :  * Function called with information about a refund.
     480              :  * It is responsible for summing up the refund amount.
     481              :  *
     482              :  * @param cls closure
     483              :  * @param refund_serial unique serial number of the refund
     484              :  * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
     485              :  * @param coin_pub public coin from which the refund comes from
     486              :  * @param exchange_url URL of the exchange that issued @a coin_pub
     487              :  * @param rtransaction_id identificator of the refund
     488              :  * @param reason human-readable explanation of the refund
     489              :  * @param refund_amount refund amount which is being taken from @a coin_pub
     490              :  * @param pending true if the this refund was not yet processed by the wallet/exchange
     491              :  */
     492              : static void
     493            8 : process_refunds_cb (void *cls,
     494              :                     uint64_t refund_serial,
     495              :                     struct GNUNET_TIME_Timestamp timestamp,
     496              :                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
     497              :                     const char *exchange_url,
     498              :                     uint64_t rtransaction_id,
     499              :                     const char *reason,
     500              :                     const struct TALER_Amount *refund_amount,
     501              :                     bool pending)
     502              : {
     503            8 :   struct PostRefundData *prd = cls;
     504              :   struct CoinRefund *cr;
     505              : 
     506            8 :   for (cr = prd->cr_head;
     507           12 :        NULL != cr;
     508            4 :        cr = cr->next)
     509            8 :     if (cr->refund_serial == refund_serial)
     510            4 :       return;
     511              :   /* already known */
     512              : 
     513            4 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     514              :               "Found refund of %s for coin %s with reason `%s' in database\n",
     515              :               TALER_amount2s (refund_amount),
     516              :               TALER_B2S (coin_pub),
     517              :               reason);
     518            4 :   cr = GNUNET_new (struct CoinRefund);
     519            4 :   cr->refund_serial = refund_serial;
     520            4 :   cr->exchange_url = GNUNET_strdup (exchange_url);
     521            4 :   cr->prd = prd;
     522            4 :   cr->coin_pub = *coin_pub;
     523            4 :   cr->rtransaction_id = rtransaction_id;
     524            4 :   cr->refund_amount = *refund_amount;
     525            4 :   cr->execution_time = timestamp;
     526            4 :   GNUNET_CONTAINER_DLL_insert (prd->cr_head,
     527              :                                prd->cr_tail,
     528              :                                cr);
     529            4 :   if (prd->refunded)
     530              :   {
     531            2 :     GNUNET_assert (0 <=
     532              :                    TALER_amount_add (&prd->refund_amount,
     533              :                                      &prd->refund_amount,
     534              :                                      refund_amount));
     535            2 :     return;
     536              :   }
     537            2 :   prd->refund_amount = *refund_amount;
     538            2 :   prd->refunded = true;
     539            2 :   prd->refund_available |= pending;
     540              : }
     541              : 
     542              : 
     543              : /**
     544              :  * Obtain refunds for an order.
     545              :  *
     546              :  * @param rh context of the handler
     547              :  * @param connection the MHD connection to handle
     548              :  * @param[in,out] hc context with further information about the request
     549              :  * @return MHD result code
     550              :  */
     551              : MHD_RESULT
     552            4 : TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
     553              :                            struct MHD_Connection *connection,
     554              :                            struct TMH_HandlerContext *hc)
     555              : {
     556            4 :   struct PostRefundData *prd = hc->ctx;
     557              :   enum GNUNET_DB_QueryStatus qs;
     558              : 
     559            4 :   if (NULL == prd)
     560              :   {
     561            2 :     prd = GNUNET_new (struct PostRefundData);
     562            2 :     prd->sc.con = connection;
     563            2 :     prd->hc = hc;
     564            2 :     prd->order_id = hc->infix;
     565            2 :     hc->ctx = prd;
     566            2 :     hc->cc = &refund_cleanup;
     567              :     {
     568              :       enum GNUNET_GenericReturnValue res;
     569              : 
     570              :       struct GNUNET_JSON_Specification spec[] = {
     571            2 :         GNUNET_JSON_spec_fixed_auto ("h_contract",
     572              :                                      &prd->h_contract_terms),
     573            2 :         GNUNET_JSON_spec_end ()
     574              :       };
     575            2 :       res = TALER_MHD_parse_json_data (connection,
     576            2 :                                        hc->request_body,
     577              :                                        spec);
     578            2 :       if (GNUNET_OK != res)
     579              :         return (GNUNET_NO == res)
     580              :                ? MHD_YES
     581            0 :                : MHD_NO;
     582              :     }
     583              : 
     584            2 :     TMH_db->preflight (TMH_db->cls);
     585              :     {
     586              :       json_t *contract_terms;
     587              :       uint64_t order_serial;
     588              : 
     589            2 :       qs = TMH_db->lookup_contract_terms (TMH_db->cls,
     590            2 :                                           hc->instance->settings.id,
     591            2 :                                           hc->infix,
     592              :                                           &contract_terms,
     593              :                                           &order_serial,
     594              :                                           NULL);
     595            2 :       if (0 > qs)
     596              :       {
     597              :         /* single, read-only SQL statements should never cause
     598              :            serialization problems */
     599            0 :         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
     600              :         /* Always report on hard error as well to enable diagnostics */
     601            0 :         GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
     602            0 :         return TALER_MHD_reply_with_error (connection,
     603              :                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
     604              :                                            TALER_EC_GENERIC_DB_FETCH_FAILED,
     605              :                                            "contract terms");
     606              :       }
     607            2 :       if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     608              :       {
     609            0 :         json_decref (contract_terms);
     610            0 :         return TALER_MHD_reply_with_error (connection,
     611              :                                            MHD_HTTP_NOT_FOUND,
     612              :                                            TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
     613            0 :                                            hc->infix);
     614              :       }
     615              :       {
     616              :         struct TALER_PrivateContractHashP h_contract_terms;
     617              : 
     618            2 :         if (GNUNET_OK !=
     619            2 :             TALER_JSON_contract_hash (contract_terms,
     620              :                                       &h_contract_terms))
     621              :         {
     622            0 :           GNUNET_break (0);
     623            0 :           json_decref (contract_terms);
     624            0 :           return TALER_MHD_reply_with_error (connection,
     625              :                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
     626              :                                              TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
     627              :                                              NULL);
     628              :         }
     629            2 :         json_decref (contract_terms);
     630            2 :         if (0 != GNUNET_memcmp (&h_contract_terms,
     631              :                                 &prd->h_contract_terms))
     632              :         {
     633            0 :           return TALER_MHD_reply_with_error (
     634              :             connection,
     635              :             MHD_HTTP_FORBIDDEN,
     636              :             TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
     637              :             NULL);
     638              :         }
     639              :       }
     640              :     }
     641              :   }
     642            4 :   if (GNUNET_SYSERR == prd->suspended)
     643            0 :     return MHD_NO; /* we are in shutdown */
     644              : 
     645            4 :   if (TALER_EC_NONE != prd->ec)
     646              :   {
     647            0 :     GNUNET_break (0 != prd->http_status);
     648              :     /* kill pending coin refund operations immediately, just to be
     649              :        extra sure they don't modify 'prd' after we already created
     650              :        a reply (this might not be needed, but feels safer). */
     651            0 :     for (struct CoinRefund *cr = prd->cr_head;
     652            0 :          NULL != cr;
     653            0 :          cr = cr->next)
     654              :     {
     655            0 :       if (NULL != cr->fo)
     656              :       {
     657            0 :         TMH_EXCHANGES_keys4exchange_cancel (cr->fo);
     658            0 :         cr->fo = NULL;
     659              :       }
     660            0 :       if (NULL != cr->rh)
     661              :       {
     662            0 :         TALER_EXCHANGE_refund_cancel (cr->rh);
     663            0 :         cr->rh = NULL;
     664              :       }
     665              :     }
     666            0 :     return TALER_MHD_reply_with_error (connection,
     667              :                                        prd->http_status,
     668              :                                        prd->ec,
     669              :                                        NULL);
     670              :   }
     671              : 
     672            4 :   qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
     673            4 :                                         hc->instance->settings.id,
     674            4 :                                         &prd->h_contract_terms,
     675              :                                         &process_refunds_cb,
     676              :                                         prd);
     677            4 :   if (0 > qs)
     678              :   {
     679            0 :     GNUNET_break (0);
     680            0 :     return TALER_MHD_reply_with_error (connection,
     681              :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     682              :                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
     683              :                                        "detailed refunds");
     684              :   }
     685            4 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     686              :   {
     687            0 :     GNUNET_break (0);
     688            0 :     return TALER_MHD_reply_with_error (connection,
     689              :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     690              :                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
     691              :                                        "no coins found that could be refunded");
     692              :   }
     693              : 
     694              :   /* Now launch exchange interactions, unless we already have the
     695              :      response in the database! */
     696            4 :   for (struct CoinRefund *cr = prd->cr_head;
     697           12 :        NULL != cr;
     698            8 :        cr = cr->next)
     699              :   {
     700            8 :     qs = TMH_db->lookup_refund_proof (TMH_db->cls,
     701              :                                       cr->refund_serial,
     702              :                                       &cr->exchange_sig,
     703              :                                       &cr->exchange_pub);
     704            8 :     switch (qs)
     705              :     {
     706            0 :     case GNUNET_DB_STATUS_HARD_ERROR:
     707              :     case GNUNET_DB_STATUS_SOFT_ERROR:
     708            0 :       return TALER_MHD_reply_with_error (connection,
     709              :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     710              :                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
     711              :                                          "refund proof");
     712            4 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     713            4 :       if (NULL == cr->exchange_reply)
     714              :       {
     715              :         /* We need to talk to the exchange */
     716            4 :         cr->fo = TMH_EXCHANGES_keys4exchange (cr->exchange_url,
     717              :                                               false,
     718              :                                               &exchange_found_cb,
     719              :                                               cr);
     720            4 :         if (NULL == cr->fo)
     721              :         {
     722            0 :           GNUNET_break (0);
     723            0 :           return TALER_MHD_reply_with_error (connection,
     724              :                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
     725              :                                              TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNTRUSTED,
     726            0 :                                              cr->exchange_url);
     727              :         }
     728              :       }
     729            4 :       break;
     730            4 :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     731              :       /* We got a reply earlier, set status accordingly */
     732            4 :       cr->exchange_status = MHD_HTTP_OK;
     733            4 :       break;
     734              :     }
     735              :   }
     736              : 
     737              :   /* Check if there are still exchange operations pending */
     738            4 :   if (exchange_operations_pending (prd))
     739              :   {
     740            2 :     if (GNUNET_NO == prd->suspended)
     741              :     {
     742            2 :       prd->suspended = GNUNET_YES;
     743            2 :       MHD_suspend_connection (connection);
     744            2 :       GNUNET_CONTAINER_DLL_insert (prd_head,
     745              :                                    prd_tail,
     746              :                                    prd);
     747              :     }
     748            2 :     return MHD_YES;   /* we're still talking to the exchange */
     749              :   }
     750              : 
     751              :   {
     752              :     json_t *ra;
     753              : 
     754            2 :     ra = json_array ();
     755            2 :     GNUNET_assert (NULL != ra);
     756            2 :     for (struct CoinRefund *cr = prd->cr_head;
     757            6 :          NULL != cr;
     758            4 :          cr = cr->next)
     759              :     {
     760              :       json_t *refund;
     761              : 
     762            4 :       if (MHD_HTTP_OK != cr->exchange_status)
     763              :       {
     764            0 :         if (NULL == cr->exchange_reply)
     765              :         {
     766            0 :           refund = GNUNET_JSON_PACK (
     767              :             GNUNET_JSON_pack_string ("type",
     768              :                                      "failure"),
     769              :             GNUNET_JSON_pack_uint64 ("exchange_status",
     770              :                                      cr->exchange_status),
     771              :             GNUNET_JSON_pack_uint64 ("rtransaction_id",
     772              :                                      cr->rtransaction_id),
     773              :             GNUNET_JSON_pack_data_auto ("coin_pub",
     774              :                                         &cr->coin_pub),
     775              :             TALER_JSON_pack_amount ("refund_amount",
     776              :                                     &cr->refund_amount),
     777              :             GNUNET_JSON_pack_timestamp ("execution_time",
     778              :                                         cr->execution_time));
     779              :         }
     780              :         else
     781              :         {
     782            0 :           refund = GNUNET_JSON_PACK (
     783              :             GNUNET_JSON_pack_string ("type",
     784              :                                      "failure"),
     785              :             GNUNET_JSON_pack_uint64 ("exchange_status",
     786              :                                      cr->exchange_status),
     787              :             GNUNET_JSON_pack_uint64 ("exchange_code",
     788              :                                      cr->exchange_code),
     789              :             GNUNET_JSON_pack_object_incref ("exchange_reply",
     790              :                                             cr->exchange_reply),
     791              :             GNUNET_JSON_pack_uint64 ("rtransaction_id",
     792              :                                      cr->rtransaction_id),
     793              :             GNUNET_JSON_pack_data_auto ("coin_pub",
     794              :                                         &cr->coin_pub),
     795              :             TALER_JSON_pack_amount ("refund_amount",
     796              :                                     &cr->refund_amount),
     797              :             GNUNET_JSON_pack_timestamp ("execution_time",
     798              :                                         cr->execution_time));
     799              :         }
     800              :       }
     801              :       else
     802              :       {
     803            4 :         refund = GNUNET_JSON_PACK (
     804              :           GNUNET_JSON_pack_string ("type",
     805              :                                    "success"),
     806              :           GNUNET_JSON_pack_uint64 ("exchange_status",
     807              :                                    cr->exchange_status),
     808              :           GNUNET_JSON_pack_data_auto ("exchange_sig",
     809              :                                       &cr->exchange_sig),
     810              :           GNUNET_JSON_pack_data_auto ("exchange_pub",
     811              :                                       &cr->exchange_pub),
     812              :           GNUNET_JSON_pack_uint64 ("rtransaction_id",
     813              :                                    cr->rtransaction_id),
     814              :           GNUNET_JSON_pack_data_auto ("coin_pub",
     815              :                                       &cr->coin_pub),
     816              :           TALER_JSON_pack_amount ("refund_amount",
     817              :                                   &cr->refund_amount),
     818              :           GNUNET_JSON_pack_timestamp ("execution_time",
     819              :                                       cr->execution_time));
     820              :       }
     821            4 :       GNUNET_assert (
     822              :         0 ==
     823              :         json_array_append_new (ra,
     824              :                                refund));
     825              :     }
     826              : 
     827            2 :     return TALER_MHD_REPLY_JSON_PACK (
     828              :       connection,
     829              :       MHD_HTTP_OK,
     830              :       TALER_JSON_pack_amount ("refund_amount",
     831              :                               &prd->refund_amount),
     832              :       GNUNET_JSON_pack_array_steal ("refunds",
     833              :                                     ra),
     834              :       GNUNET_JSON_pack_data_auto ("merchant_pub",
     835              :                                   &hc->instance->merchant_pub));
     836              :   }
     837              : 
     838              :   return MHD_YES;
     839              : }
     840              : 
     841              : 
     842              : /* end of taler-merchant-httpd_post-orders-ID-refund.c */
        

Generated by: LCOV version 2.0-1