LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_private-post-orders-ID-refund.c (source / functions) Hit Total Coverage
Test: GNU Taler merchant coverage report Lines: 0 125 0.0 %
Date: 2022-06-30 06:15:34 Functions: 0 3 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   (C) 2014-2021 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU Affero General Public License as published by the Free Software
       7             :   Foundation; either version 3, or (at your option) any later version.
       8             : 
       9             :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10             :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11             :   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU General Public License along with
      14             :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15             : */
      16             : /**
      17             :  * @file taler-merchant-httpd_private-post-orders-ID-refund.c
      18             :  * @brief Handle request to increase the refund for an order
      19             :  * @author Marcello Stanisci
      20             :  * @author Christian Grothoff
      21             :  */
      22             : #include "platform.h"
      23             : #include <jansson.h>
      24             : #include <taler/taler_dbevents.h>
      25             : #include <taler/taler_signatures.h>
      26             : #include <taler/taler_json_lib.h>
      27             : #include "taler-merchant-httpd_private-post-orders-ID-refund.h"
      28             : #include "taler-merchant-httpd_private-get-orders.h"
      29             : 
      30             : 
      31             : /**
      32             :  * How often do we retry the non-trivial refund INSERT database
      33             :  * transaction?
      34             :  */
      35             : #define MAX_RETRIES 5
      36             : 
      37             : 
      38             : /**
      39             :  * Use database to notify other clients about the
      40             :  * @a order_id being refunded
      41             :  *
      42             :  * @param hc handler context we operate in
      43             :  * @param amount the (total) refunded amount
      44             :  */
      45             : static void
      46           0 : trigger_refund_notification (struct TMH_HandlerContext *hc,
      47             :                              const struct TALER_Amount *amount)
      48             : {
      49             :   const char *as;
      50           0 :   struct TMH_OrderRefundEventP refund_eh = {
      51           0 :     .header.size = htons (sizeof (refund_eh)),
      52           0 :     .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_REFUND),
      53           0 :     .merchant_pub = hc->instance->merchant_pub
      54             :   };
      55             : 
      56             :   /* Resume clients that may wait for this refund */
      57           0 :   as = TALER_amount2s (amount);
      58           0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
      59             :               "Awakening clients on %s waiting for refund of no more than %s\n",
      60             :               hc->infix,
      61             :               as);
      62           0 :   GNUNET_CRYPTO_hash (hc->infix,
      63           0 :                       strlen (hc->infix),
      64             :                       &refund_eh.h_order_id);
      65           0 :   TMH_db->event_notify (TMH_db->cls,
      66             :                         &refund_eh.header,
      67             :                         as,
      68             :                         strlen (as));
      69           0 : }
      70             : 
      71             : 
      72             : /**
      73             :  * Make a taler://refund URI
      74             :  *
      75             :  * @param connection MHD connection to take host and path from
      76             :  * @param instance_id merchant's instance ID, must not be NULL
      77             :  * @param order_id order ID to show a refund for, must not be NULL
      78             :  * @returns the URI, must be freed with #GNUNET_free
      79             :  */
      80             : static char *
      81           0 : make_taler_refund_uri (struct MHD_Connection *connection,
      82             :                        const char *instance_id,
      83             :                        const char *order_id)
      84             : {
      85             :   const char *host;
      86             :   const char *forwarded_host;
      87             :   const char *uri_path;
      88           0 :   struct GNUNET_Buffer buf = { 0 };
      89             : 
      90           0 :   GNUNET_assert (NULL != instance_id);
      91           0 :   GNUNET_assert (NULL != order_id);
      92           0 :   host = MHD_lookup_connection_value (connection,
      93             :                                       MHD_HEADER_KIND,
      94             :                                       "Host");
      95           0 :   forwarded_host = MHD_lookup_connection_value (connection,
      96             :                                                 MHD_HEADER_KIND,
      97             :                                                 "X-Forwarded-Host");
      98           0 :   uri_path = MHD_lookup_connection_value (connection,
      99             :                                           MHD_HEADER_KIND,
     100             :                                           "X-Forwarded-Prefix");
     101           0 :   if (NULL != forwarded_host)
     102           0 :     host = forwarded_host;
     103           0 :   if (NULL == host)
     104             :   {
     105             :     /* Should never happen, at least the host header should be defined */
     106           0 :     GNUNET_break (0);
     107           0 :     return NULL;
     108             :   }
     109           0 :   GNUNET_buffer_write_str (&buf, "taler");
     110           0 :   if (GNUNET_NO == TALER_mhd_is_https (connection))
     111           0 :     GNUNET_buffer_write_str (&buf, "+http");
     112           0 :   GNUNET_buffer_write_str (&buf, "://refund/");
     113           0 :   GNUNET_buffer_write_str (&buf, host);
     114           0 :   if (NULL != uri_path)
     115           0 :     GNUNET_buffer_write_path (&buf, uri_path);
     116           0 :   if (0 != strcmp ("default", instance_id))
     117             :   {
     118           0 :     GNUNET_buffer_write_path (&buf, "instances");
     119           0 :     GNUNET_buffer_write_path (&buf, instance_id);
     120             :   }
     121           0 :   GNUNET_buffer_write_path (&buf, order_id);
     122           0 :   GNUNET_buffer_write_path (&buf,
     123             :                             ""); /* Trailing slash */
     124           0 :   return GNUNET_buffer_reap_str (&buf);
     125             : }
     126             : 
     127             : 
     128             : /**
     129             :  * Handle request for increasing the refund associated with
     130             :  * a contract.
     131             :  *
     132             :  * @param rh context of the handler
     133             :  * @param connection the MHD connection to handle
     134             :  * @param[in,out] hc context with further information about the request
     135             :  * @return MHD result code
     136             :  */
     137             : MHD_RESULT
     138           0 : TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
     139             :                                    struct MHD_Connection *connection,
     140             :                                    struct TMH_HandlerContext *hc)
     141             : {
     142             :   struct TALER_Amount refund;
     143             :   const char *reason;
     144             :   struct GNUNET_JSON_Specification spec[] = {
     145           0 :     TALER_JSON_spec_amount ("refund",
     146             :                             TMH_currency,
     147             :                             &refund),
     148           0 :     GNUNET_JSON_spec_string ("reason",
     149             :                              &reason),
     150           0 :     GNUNET_JSON_spec_end ()
     151             :   };
     152             :   enum TALER_MERCHANTDB_RefundStatus rs;
     153             :   struct TALER_PrivateContractHashP h_contract;
     154             : 
     155             :   {
     156             :     enum GNUNET_DB_QueryStatus qs;
     157             :     json_t *contract_terms;
     158             :     uint64_t order_serial;
     159             :     struct GNUNET_TIME_Timestamp refund_deadline;
     160             :     struct GNUNET_TIME_Timestamp timestamp;
     161           0 :     bool paid = false;
     162             : 
     163           0 :     qs = TMH_db->lookup_contract_terms (TMH_db->cls,
     164           0 :                                         hc->instance->settings.id,
     165           0 :                                         hc->infix,
     166             :                                         &contract_terms,
     167             :                                         &order_serial,
     168             :                                         &paid,
     169             :                                         NULL);
     170           0 :     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     171             :     {
     172             :       struct GNUNET_JSON_Specification spec[] = {
     173           0 :         GNUNET_JSON_spec_timestamp ("refund_deadline",
     174             :                                     &refund_deadline),
     175           0 :         GNUNET_JSON_spec_timestamp ("timestamp",
     176             :                                     &timestamp),
     177           0 :         GNUNET_JSON_spec_end ()
     178             :       };
     179             : 
     180           0 :       if (GNUNET_YES !=
     181           0 :           GNUNET_JSON_parse (contract_terms,
     182             :                              spec,
     183             :                              NULL, NULL))
     184             :       {
     185           0 :         GNUNET_break (0);
     186           0 :         GNUNET_JSON_parse_free (spec);
     187           0 :         json_decref (contract_terms);
     188           0 :         return TALER_MHD_reply_with_error (
     189             :           connection,
     190             :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     191             :           TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
     192             :           "mandatory fields missing");
     193             :       }
     194           0 :       json_decref (contract_terms);
     195           0 :       if (GNUNET_TIME_timestamp_cmp (timestamp,
     196             :                                      ==,
     197             :                                      refund_deadline))
     198             :       {
     199             :         /* refund was never allowed, so we should refuse hard */
     200           0 :         return TALER_MHD_reply_with_error (
     201             :           connection,
     202             :           MHD_HTTP_FORBIDDEN,
     203             :           TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_NOT_ALLOWED_BY_CONTRACT,
     204             :           NULL);
     205             :       }
     206           0 :       if (GNUNET_TIME_absolute_is_past (refund_deadline.abs_time))
     207             :       {
     208             :         /* it is too late for refunds */
     209             :         /* NOTE: We MAY still be lucky that the exchange did not yet
     210             :            wire the funds, so we will try to give the refund anyway */
     211             :       }
     212             :     }
     213             :     else
     214             :     {
     215           0 :       return TALER_MHD_reply_with_error (connection,
     216             :                                          MHD_HTTP_NOT_FOUND,
     217             :                                          TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
     218           0 :                                          hc->infix);
     219             :     }
     220             :   }
     221             : 
     222             :   {
     223             :     enum GNUNET_GenericReturnValue res;
     224             : 
     225           0 :     res = TALER_MHD_parse_json_data (connection,
     226           0 :                                      hc->request_body,
     227             :                                      spec);
     228           0 :     if (GNUNET_OK != res)
     229             :       return (GNUNET_NO == res)
     230             :              ? MHD_YES
     231           0 :              : MHD_NO;
     232             :   }
     233             : 
     234           0 :   TMH_db->preflight (TMH_db->cls);
     235           0 :   for (unsigned int i = 0; i<MAX_RETRIES; i++)
     236             :   {
     237           0 :     if (GNUNET_OK !=
     238           0 :         TMH_db->start (TMH_db->cls,
     239             :                        "increase refund"))
     240             :     {
     241           0 :       GNUNET_break (0);
     242           0 :       return TALER_MHD_reply_with_error (connection,
     243             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     244             :                                          TALER_EC_GENERIC_DB_START_FAILED,
     245             :                                          NULL);
     246             :     }
     247           0 :     rs = TMH_db->increase_refund (TMH_db->cls,
     248           0 :                                   hc->instance->settings.id,
     249           0 :                                   hc->infix,
     250             :                                   &refund,
     251             :                                   reason);
     252           0 :     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     253             :                 "increase refund returned %d\n",
     254             :                 rs);
     255           0 :     if (TALER_MERCHANTDB_RS_SUCCESS != rs)
     256           0 :       TMH_db->rollback (TMH_db->cls);
     257           0 :     if (TALER_MERCHANTDB_RS_SOFT_ERROR == rs)
     258           0 :       continue;
     259           0 :     if (TALER_MERCHANTDB_RS_SUCCESS == rs)
     260             :     {
     261             :       enum GNUNET_DB_QueryStatus qs;
     262             : 
     263           0 :       qs = TMH_db->commit (TMH_db->cls);
     264           0 :       if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     265             :       {
     266           0 :         GNUNET_break (0);
     267           0 :         rs = TALER_MERCHANTDB_RS_HARD_ERROR;
     268           0 :         break;
     269             :       }
     270           0 :       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     271           0 :         continue;
     272           0 :       trigger_refund_notification (hc,
     273             :                                    &refund);
     274             :     }
     275           0 :     break;
     276             :   } /* retries loop */
     277             : 
     278           0 :   switch (rs)
     279             :   {
     280           0 :   case TALER_MERCHANTDB_RS_TOO_HIGH:
     281           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     282             :                 "Refusing refund amount %s that is larger than original payment\n",
     283             :                 TALER_amount2s (&refund));
     284           0 :     return TALER_MHD_reply_with_error (connection,
     285             :                                        MHD_HTTP_CONFLICT,
     286             :                                        TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT,
     287             :                                        "Amount above payment");
     288           0 :   case TALER_MERCHANTDB_RS_SOFT_ERROR:
     289             :   case TALER_MERCHANTDB_RS_HARD_ERROR:
     290           0 :     return TALER_MHD_reply_with_error (connection,
     291             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     292             :                                        TALER_EC_GENERIC_DB_COMMIT_FAILED,
     293             :                                        NULL);
     294           0 :   case TALER_MERCHANTDB_RS_NO_SUCH_ORDER:
     295             :     {
     296             :       /* We know the order exists from the
     297             :          "lookup_contract_terms" at the beginning;
     298             :          so if we get 'no such order' here, it
     299             :          must be read as "no PAID order" */
     300           0 :       return TALER_MHD_reply_with_error (
     301             :         connection,
     302             :         MHD_HTTP_CONFLICT,
     303             :         TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_ORDER_UNPAID,
     304           0 :         hc->infix);
     305             :     }
     306           0 :   case TALER_MERCHANTDB_RS_SUCCESS:
     307             :     {
     308             :       enum GNUNET_DB_QueryStatus qs;
     309             :       json_t *contract_terms;
     310             :       uint64_t order_serial;
     311             :       bool paid;
     312             : 
     313           0 :       qs = TMH_db->lookup_contract_terms (TMH_db->cls,
     314           0 :                                           hc->instance->settings.id,
     315           0 :                                           hc->infix,
     316             :                                           &contract_terms,
     317             :                                           &order_serial,
     318             :                                           &paid,
     319             :                                           NULL);
     320           0 :       if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     321             :       {
     322           0 :         return TALER_MHD_reply_with_error (connection,
     323             :                                            MHD_HTTP_NOT_FOUND,
     324             :                                            TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
     325           0 :                                            hc->infix);
     326             :       }
     327           0 :       if (GNUNET_OK !=
     328           0 :           TALER_JSON_contract_hash (contract_terms,
     329             :                                     &h_contract))
     330             :       {
     331           0 :         GNUNET_break (0);
     332           0 :         json_decref (contract_terms);
     333           0 :         return TALER_MHD_reply_with_error (connection,
     334             :                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
     335             :                                            TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
     336             :                                            "Could not hash contract terms");
     337             :       }
     338           0 :       json_decref (contract_terms);
     339             :     }
     340           0 :     break;
     341             :   }
     342             : 
     343           0 :   {
     344             :     struct GNUNET_TIME_Timestamp timestamp;
     345             :     uint64_t order_serial;
     346             :     enum GNUNET_DB_QueryStatus qs;
     347             : 
     348           0 :     qs = TMH_db->lookup_order_summary (TMH_db->cls,
     349           0 :                                        hc->instance->settings.id,
     350           0 :                                        hc->infix,
     351             :                                        &timestamp,
     352             :                                        &order_serial);
     353           0 :     if (0 >= qs)
     354             :     {
     355           0 :       GNUNET_break (0);
     356           0 :       return TALER_MHD_reply_with_error (connection,
     357             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     358             :                                          TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
     359             :                                          NULL);
     360             :     }
     361           0 :     TMH_notify_order_change (hc->instance,
     362             :                              TMH_OSF_CLAIMED
     363             :                              | TMH_OSF_PAID
     364             :                              | TMH_OSF_REFUNDED,
     365             :                              timestamp,
     366             :                              order_serial);
     367             :   }
     368             :   {
     369             :     MHD_RESULT ret;
     370             :     char *taler_refund_uri;
     371             : 
     372           0 :     taler_refund_uri = make_taler_refund_uri (connection,
     373           0 :                                               hc->instance->settings.id,
     374           0 :                                               hc->infix);
     375           0 :     ret = TALER_MHD_REPLY_JSON_PACK (
     376             :       connection,
     377             :       MHD_HTTP_OK,
     378             :       GNUNET_JSON_pack_string ("taler_refund_uri",
     379             :                                taler_refund_uri),
     380             :       GNUNET_JSON_pack_data_auto ("h_contract",
     381             :                                   &h_contract));
     382           0 :     GNUNET_free (taler_refund_uri);
     383           0 :     return ret;
     384             :   }
     385             : }
     386             : 
     387             : 
     388             : /* end of taler-merchant-httpd_private-post-orders-ID-refund.c */

Generated by: LCOV version 1.14