LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_get-orders-ID.c (source / functions) Hit Total Coverage
Test: GNU Taler merchant coverage report Lines: 327 458 71.4 %
Date: 2021-08-30 06:54:17 Functions: 10 11 90.9 %
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_get-orders-ID.c
      18             :  * @brief implementation of GET /orders/$ID
      19             :  * @author Marcello Stanisci
      20             :  * @author Christian Grothoff
      21             :  */
      22             : #include "platform.h"
      23             : #include <jansson.h>
      24             : #include <gnunet/gnunet_uri_lib.h>
      25             : #include <taler/taler_signatures.h>
      26             : #include <taler/taler_dbevents.h>
      27             : #include <taler/taler_json_lib.h>
      28             : #include <taler/taler_exchange_service.h>
      29             : #include "taler-merchant-httpd_exchanges.h"
      30             : #include "taler-merchant-httpd_get-orders-ID.h"
      31             : #include "taler-merchant-httpd_mhd.h"
      32             : #include "taler-merchant-httpd_qr.h"
      33             : #include "taler-merchant-httpd_templating.h"
      34             : 
      35             : /**
      36             :  * How often do we retry DB transactions on serialization failures?
      37             :  */
      38             : #define MAX_RETRIES 5
      39             : 
      40             : 
      41             : /**
      42             :  * Context for the operation.
      43             :  */
      44             : struct GetOrderData
      45             : {
      46             : 
      47             :   /**
      48             :    * Hashed version of contract terms. All zeros if not provided.
      49             :    */
      50             :   struct GNUNET_HashCode h_contract_terms;
      51             : 
      52             :   /**
      53             :    * Claim token used for access control. All zeros if not provided.
      54             :    */
      55             :   struct TALER_ClaimTokenP claim_token;
      56             : 
      57             :   /**
      58             :    * DLL of (suspended) requests.
      59             :    */
      60             :   struct GetOrderData *next;
      61             : 
      62             :   /**
      63             :    * DLL of (suspended) requests.
      64             :    */
      65             :   struct GetOrderData *prev;
      66             : 
      67             :   /**
      68             :    * Context of the request.
      69             :    */
      70             :   struct TMH_HandlerContext *hc;
      71             : 
      72             :   /**
      73             :    * Entry in the #resume_timeout_heap for this check payment, if we are
      74             :    * suspended.
      75             :    */
      76             :   struct TMH_SuspendedConnection sc;
      77             : 
      78             :   /**
      79             :    * Database event we are waiting on to be resuming.
      80             :    */
      81             :   struct GNUNET_DB_EventHandler *pay_eh;
      82             : 
      83             :   /**
      84             :    * Database event we are waiting on to be resuming.
      85             :    */
      86             :   struct GNUNET_DB_EventHandler *refund_eh;
      87             : 
      88             :   /**
      89             :    * Which merchant instance is this for?
      90             :    */
      91             :   struct MerchantInstance *mi;
      92             : 
      93             :   /**
      94             :    * order ID for the payment
      95             :    */
      96             :   const char *order_id;
      97             : 
      98             :   /**
      99             :    * Where to get the contract
     100             :    */
     101             :   const char *contract_url;
     102             : 
     103             :   /**
     104             :    * fulfillment URL of the contract (valid as long as @e contract_terms is
     105             :    * valid; but can also be NULL if the contract_terms does not come with
     106             :    * a fulfillment URL).
     107             :    */
     108             :   const char *fulfillment_url;
     109             : 
     110             :   /**
     111             :    * session of the client
     112             :    */
     113             :   const char *session_id;
     114             : 
     115             :   /**
     116             :    * Contract terms of the payment we are checking. NULL when they
     117             :    * are not (yet) known.
     118             :    */
     119             :   json_t *contract_terms;
     120             : 
     121             :   /**
     122             :    * Total refunds granted for this payment. Only initialized
     123             :    * if @e refunded is set to true.
     124             :    */
     125             :   struct TALER_Amount refund_amount;
     126             : 
     127             :   /**
     128             :    * Return code: #TALER_EC_NONE if successful.
     129             :    */
     130             :   enum TALER_ErrorCode ec;
     131             : 
     132             :   /**
     133             :    * Did we suspend @a connection and are thus in
     134             :    * the #god_head DLL?
     135             :    */
     136             :   bool suspended;
     137             : 
     138             :   /**
     139             :    * Set to true if we are dealing with a claimed order
     140             :    * (and thus @e h_contract_terms is set, otherwise certain
     141             :    * DB queries will not work).
     142             :    */
     143             :   bool claimed;
     144             : 
     145             :   /**
     146             :    * Set to true if this payment has been refunded and
     147             :    * @e refund_amount is initialized.
     148             :    */
     149             :   bool refunded;
     150             : 
     151             :   /**
     152             :    * Set to true if a refund is still available for the
     153             :    * wallet for this payment.
     154             :    */
     155             :   bool refund_available;
     156             : 
     157             :   /**
     158             :    * Set to true if the client requested HTML, otherwise we generate JSON.
     159             :    */
     160             :   bool generate_html;
     161             : 
     162             : };
     163             : 
     164             : 
     165             : /**
     166             :  * Head of DLL of (suspended) requests.
     167             :  */
     168             : static struct GetOrderData *god_head;
     169             : 
     170             : /**
     171             :  * Tail of DLL of (suspended) requests.
     172             :  */
     173             : static struct GetOrderData *god_tail;
     174             : 
     175             : 
     176             : void
     177          16 : TMH_force_wallet_get_order_resume (void)
     178             : {
     179             :   struct GetOrderData *god;
     180             : 
     181          16 :   while (NULL != (god = god_head))
     182             :   {
     183           0 :     GNUNET_CONTAINER_DLL_remove (god_head,
     184             :                                  god_tail,
     185             :                                  god);
     186           0 :     GNUNET_assert (god->suspended);
     187           0 :     god->suspended = false;
     188           0 :     MHD_resume_connection (god->sc.con);
     189           0 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     190             :   }
     191          16 : }
     192             : 
     193             : 
     194             : /**
     195             :  * We have received a trigger from the database
     196             :  * that we should (possibly) resume the request.
     197             :  *
     198             :  * @param cls a `struct GetOrderData` to resume
     199             :  * @param extra string encoding refund amount (or NULL)
     200             :  * @param extra_size number of bytes in @a extra
     201             :  */
     202             : static void
     203          10 : resume_by_event (void *cls,
     204             :                  const void *extra,
     205             :                  size_t extra_size)
     206             : {
     207          10 :   struct GetOrderData *god = cls;
     208             :   struct GNUNET_AsyncScopeSave old;
     209             : 
     210          10 :   GNUNET_async_scope_enter (&god->hc->async_scope_id,
     211             :                             &old);
     212          10 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     213             :               "Received event for %s with argument `%.*s`\n",
     214             :               god->order_id,
     215             :               (int) extra_size,
     216             :               (const char *) extra);
     217          10 :   if (! god->suspended)
     218             :   {
     219           1 :     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     220             :                 "Not suspended, ignoring event\n");
     221           1 :     GNUNET_async_scope_restore (&old);
     222           4 :     return; /* duplicate event is possible */
     223             :   }
     224           9 :   if (GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout) &&
     225           5 :       god->sc.awaiting_refund)
     226             :   {
     227             :     char *as;
     228             :     struct TALER_Amount a;
     229             : 
     230           4 :     if (0 == extra_size)
     231             :     {
     232           2 :       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     233             :                   "No amount given, but need refund above threshold\n");
     234           2 :       GNUNET_async_scope_restore (&old);
     235           3 :       return; /* not relevant */
     236             :     }
     237           2 :     as = GNUNET_strndup (extra,
     238             :                          extra_size);
     239           2 :     if (GNUNET_OK !=
     240           2 :         TALER_string_to_amount (as,
     241             :                                 &a))
     242             :     {
     243           0 :       GNUNET_break (0);
     244           0 :       GNUNET_async_scope_restore (&old);
     245           0 :       return;
     246             :     }
     247           2 :     if (GNUNET_OK !=
     248           2 :         TALER_amount_cmp_currency (&god->sc.refund_expected,
     249             :                                    &a))
     250             :     {
     251           0 :       GNUNET_break (0);
     252           0 :       GNUNET_async_scope_restore (&old);
     253           0 :       return; /* bad currency!? */
     254             :     }
     255           2 :     if (1 == TALER_amount_cmp (&god->sc.refund_expected,
     256             :                                &a))
     257             :     {
     258           1 :       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     259             :                   "Amount too small to trigger resuming\n");
     260           1 :       GNUNET_async_scope_restore (&old);
     261           1 :       return; /* refund too small */
     262             :     }
     263             :   }
     264           6 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     265             :               "Resuming (%d/%d) by event with argument `%.*s`\n",
     266             :               (int) GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout),
     267             :               god->sc.awaiting_refund,
     268             :               (int) extra_size,
     269             :               (const char *) extra);
     270           6 :   god->suspended = false;
     271           6 :   GNUNET_CONTAINER_DLL_remove (god_head,
     272             :                                god_tail,
     273             :                                god);
     274           6 :   MHD_resume_connection (god->sc.con);
     275           6 :   TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     276           6 :   GNUNET_async_scope_restore (&old);
     277             : }
     278             : 
     279             : 
     280             : /**
     281             :  * Suspend this @a god until the trigger is satisfied.
     282             :  *
     283             :  * @param god request to suspend
     284             :  */
     285             : static void
     286           6 : suspend_god (struct GetOrderData *god)
     287             : {
     288           6 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     289             :               "Suspending GET /orders/%s\n",
     290             :               god->order_id);
     291           6 :   if (NULL != god->contract_terms)
     292             :   {
     293           6 :     json_decref (god->contract_terms);
     294           6 :     god->fulfillment_url = NULL;
     295           6 :     god->contract_terms = NULL;
     296             :   }
     297           6 :   GNUNET_assert (! god->suspended);
     298           6 :   god->suspended = true;
     299           6 :   GNUNET_CONTAINER_DLL_insert (god_head,
     300             :                                god_tail,
     301             :                                god);
     302           6 :   MHD_suspend_connection (god->sc.con);
     303           6 : }
     304             : 
     305             : 
     306             : /**
     307             :  * Create a taler://refund/ URI for the given @a con and @a order_id
     308             :  * and @a instance_id.
     309             :  *
     310             :  * @param merchant_base_url URL to take host and path from;
     311             :  *        we cannot take it from the MHD connection as a browser
     312             :  *        may have changed 'http' to 'https' and we MUST be consistent
     313             :  *        with what the merchant's frontend used initially
     314             :  * @param order_id the order id
     315             :  * @return corresponding taler://refund/ URI, or NULL on missing "host"
     316             :  */
     317             : static char *
     318           0 : make_taler_refund_uri (const char *merchant_base_url,
     319             :                        const char *order_id)
     320             : {
     321           0 :   struct GNUNET_Buffer buf = { 0 };
     322             :   char *url;
     323             :   struct GNUNET_Uri uri;
     324             : 
     325           0 :   url = GNUNET_strdup (merchant_base_url);
     326           0 :   if (-1 == GNUNET_uri_parse (&uri,
     327             :                               url))
     328             :   {
     329           0 :     GNUNET_break (0);
     330           0 :     GNUNET_free (url);
     331           0 :     return NULL;
     332             :   }
     333           0 :   GNUNET_assert (NULL != order_id);
     334           0 :   GNUNET_buffer_write_str (&buf,
     335             :                            "taler");
     336           0 :   if (0 == strcasecmp ("http",
     337           0 :                        uri.scheme))
     338           0 :     GNUNET_buffer_write_str (&buf,
     339             :                              "+http");
     340           0 :   GNUNET_buffer_write_str (&buf,
     341             :                            "://refund/");
     342           0 :   GNUNET_buffer_write_str (&buf,
     343           0 :                            uri.host);
     344           0 :   if (0 != uri.port)
     345           0 :     GNUNET_buffer_write_fstr (&buf,
     346             :                               ":%u",
     347           0 :                               (unsigned int) uri.port);
     348           0 :   if (NULL != uri.path)
     349           0 :     GNUNET_buffer_write_path (&buf,
     350           0 :                               uri.path);
     351           0 :   GNUNET_buffer_write_path (&buf,
     352             :                             order_id);
     353           0 :   GNUNET_buffer_write_path (&buf,
     354             :                             ""); // Trailing slash
     355           0 :   GNUNET_free (url);
     356           0 :   return GNUNET_buffer_reap_str (&buf);
     357             : }
     358             : 
     359             : 
     360             : char *
     361          51 : TMH_make_order_status_url (struct MHD_Connection *con,
     362             :                            const char *order_id,
     363             :                            const char *session_id,
     364             :                            const char *instance_id,
     365             :                            struct TALER_ClaimTokenP *claim_token,
     366             :                            struct GNUNET_HashCode *h_contract)
     367             : {
     368             :   const char *host;
     369             :   const char *forwarded_host;
     370             :   const char *uri_path;
     371          51 :   struct GNUNET_Buffer buf = { 0 };
     372             :   /* Number of query parameters written so far */
     373          51 :   unsigned int num_qp = 0;
     374             : 
     375          51 :   host = MHD_lookup_connection_value (con,
     376             :                                       MHD_HEADER_KIND,
     377             :                                       MHD_HTTP_HEADER_HOST);
     378          51 :   forwarded_host = MHD_lookup_connection_value (con,
     379             :                                                 MHD_HEADER_KIND,
     380             :                                                 "X-Forwarded-Host");
     381          51 :   uri_path = MHD_lookup_connection_value (con,
     382             :                                           MHD_HEADER_KIND,
     383             :                                           "X-Forwarded-Prefix");
     384          51 :   if (NULL != forwarded_host)
     385           0 :     host = forwarded_host;
     386          51 :   if (NULL == host)
     387             :   {
     388           0 :     GNUNET_break (0);
     389           0 :     return NULL;
     390             :   }
     391          51 :   if (NULL != strchr (host, '/'))
     392             :   {
     393           0 :     GNUNET_break_op (0);
     394           0 :     return NULL;
     395             :   }
     396          51 :   GNUNET_assert (NULL != instance_id);
     397          51 :   GNUNET_assert (NULL != order_id);
     398             : 
     399          51 :   if (GNUNET_NO == TALER_mhd_is_https (con))
     400          51 :     GNUNET_buffer_write_str (&buf,
     401             :                              "http://");
     402             :   else
     403           0 :     GNUNET_buffer_write_str (&buf,
     404             :                              "https://");
     405          51 :   GNUNET_buffer_write_str (&buf,
     406             :                            host);
     407          51 :   if (NULL != uri_path)
     408           0 :     GNUNET_buffer_write_path (&buf,
     409             :                               uri_path);
     410          51 :   if (0 != strcmp ("default",
     411             :                    instance_id))
     412             :   {
     413           0 :     GNUNET_buffer_write_path (&buf,
     414             :                               "instances");
     415           0 :     GNUNET_buffer_write_path (&buf,
     416             :                               instance_id);
     417             :   }
     418          51 :   GNUNET_buffer_write_path (&buf,
     419             :                             "/orders");
     420          51 :   GNUNET_buffer_write_path (&buf,
     421             :                             order_id);
     422         102 :   if ((NULL != claim_token) &&
     423          51 :       (GNUNET_NO == GNUNET_is_zero (claim_token)))
     424             :   {
     425             :     /* 'token=' for human readability */
     426          22 :     GNUNET_buffer_write_str (&buf,
     427             :                              "?token=");
     428          22 :     GNUNET_buffer_write_data_encoded (&buf,
     429             :                                       (char *) claim_token,
     430             :                                       sizeof (struct TALER_ClaimTokenP));
     431          22 :     num_qp++;
     432             :   }
     433             : 
     434          51 :   if (NULL != session_id)
     435             :   {
     436          18 :     if (num_qp > 0)
     437          12 :       GNUNET_buffer_write_str (&buf,
     438             :                                "&session_id=");
     439             :     else
     440           6 :       GNUNET_buffer_write_str (&buf,
     441             :                                "?session_id=");
     442          18 :     GNUNET_buffer_write_str (&buf,
     443             :                              session_id);
     444          18 :     num_qp++;
     445             :   }
     446             : 
     447          51 :   if (NULL != h_contract)
     448             :   {
     449           1 :     if (num_qp > 0)
     450           1 :       GNUNET_buffer_write_str (&buf,
     451             :                                "&h_contract=");
     452             :     else
     453           0 :       GNUNET_buffer_write_str (&buf,
     454             :                                "?h_contract=");
     455           1 :     GNUNET_buffer_write_data_encoded (&buf,
     456             :                                       (char *) h_contract,
     457             :                                       sizeof (struct GNUNET_HashCode));
     458             :   }
     459             : 
     460          51 :   return GNUNET_buffer_reap_str (&buf);
     461             : }
     462             : 
     463             : 
     464             : char *
     465          35 : TMH_make_taler_pay_uri (struct MHD_Connection *con,
     466             :                         const char *order_id,
     467             :                         const char *session_id,
     468             :                         const char *instance_id,
     469             :                         struct TALER_ClaimTokenP *claim_token)
     470             : {
     471             :   const char *host;
     472             :   const char *forwarded_host;
     473             :   const char *uri_path;
     474          35 :   struct GNUNET_Buffer buf = { 0 };
     475             : 
     476          35 :   host = MHD_lookup_connection_value (con,
     477             :                                       MHD_HEADER_KIND,
     478             :                                       MHD_HTTP_HEADER_HOST);
     479          35 :   forwarded_host = MHD_lookup_connection_value (con,
     480             :                                                 MHD_HEADER_KIND,
     481             :                                                 "X-Forwarded-Host");
     482          35 :   uri_path = MHD_lookup_connection_value (con,
     483             :                                           MHD_HEADER_KIND,
     484             :                                           "X-Forwarded-Prefix");
     485          35 :   if (NULL != forwarded_host)
     486           0 :     host = forwarded_host;
     487          35 :   if (NULL == host)
     488             :   {
     489           0 :     GNUNET_break (0);
     490           0 :     return NULL;
     491             :   }
     492          35 :   if (NULL != strchr (host, '/'))
     493             :   {
     494           0 :     GNUNET_break_op (0);
     495           0 :     return NULL;
     496             :   }
     497          35 :   GNUNET_assert (NULL != instance_id);
     498          35 :   GNUNET_assert (NULL != order_id);
     499          35 :   GNUNET_buffer_write_str (&buf,
     500             :                            "taler");
     501          35 :   if (GNUNET_NO == TALER_mhd_is_https (con))
     502          35 :     GNUNET_buffer_write_str (&buf,
     503             :                              "+http");
     504          35 :   GNUNET_buffer_write_str (&buf,
     505             :                            "://pay/");
     506          35 :   GNUNET_buffer_write_str (&buf,
     507             :                            host);
     508          35 :   if (NULL != uri_path)
     509           0 :     GNUNET_buffer_write_path (&buf,
     510             :                               uri_path);
     511          35 :   if (0 != strcmp ("default",
     512             :                    instance_id))
     513             :   {
     514           0 :     GNUNET_buffer_write_path (&buf,
     515             :                               "instances");
     516           0 :     GNUNET_buffer_write_path (&buf,
     517             :                               instance_id);
     518             :   }
     519          35 :   GNUNET_buffer_write_path (&buf,
     520             :                             order_id);
     521          35 :   GNUNET_buffer_write_path (&buf,
     522             :                             (session_id == NULL) ? "" : session_id);
     523          70 :   if ((NULL != claim_token) &&
     524          35 :       (GNUNET_NO == GNUNET_is_zero (claim_token)))
     525             :   {
     526             :     /* Just 'c=' because this goes into QR
     527             :        codes, so this is more compact. */
     528          22 :     GNUNET_buffer_write_str (&buf,
     529             :                              "?c=");
     530          22 :     GNUNET_buffer_write_data_encoded (&buf,
     531             :                                       (char *) claim_token,
     532             :                                       sizeof (struct TALER_ClaimTokenP));
     533             :   }
     534             : 
     535          35 :   return GNUNET_buffer_reap_str (&buf);
     536             : }
     537             : 
     538             : 
     539             : /**
     540             :  * Return the order summary of the contract of @a god in the
     541             :  * preferred language of the HTTP client.
     542             :  *
     543             :  * @param god order to extract summary from
     544             :  * @return dummy error message summary if no summary was provided in the contract
     545             :  */
     546             : static const char *
     547           4 : get_order_summary (const struct GetOrderData *god)
     548             : {
     549             :   const char *language_pattern;
     550             :   const char *ret;
     551             : 
     552           4 :   language_pattern = MHD_lookup_connection_value (god->sc.con,
     553             :                                                   MHD_HEADER_KIND,
     554             :                                                   MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
     555           4 :   if (NULL == language_pattern)
     556           4 :     language_pattern = "en";
     557           4 :   ret = json_string_value (TALER_JSON_extract_i18n (god->contract_terms,
     558             :                                                     language_pattern,
     559             :                                                     "summary"));
     560           4 :   if (NULL == ret)
     561             :   {
     562             :     /* Upon order creation (and insertion into the database), the presence
     563             :        of a summary should have been checked. So if we get here, someone
     564             :        did something fishy to our database... */
     565           0 :     GNUNET_break (0);
     566           0 :     ret = "<bug: no summary>";
     567             :   }
     568           4 :   return ret;
     569             : }
     570             : 
     571             : 
     572             : /**
     573             :  * The client did not yet pay, send it the payment request.
     574             :  *
     575             :  * @param god check pay request context
     576             :  * @param already_paid_order_id if for the fulfillment URI there is
     577             :  *          already a paid order, this is the order ID to redirect
     578             :  *          the wallet to; NULL if not applicable
     579             :  * @return #MHD_YES on success
     580             :  */
     581             : static MHD_RESULT
     582          32 : send_pay_request (struct GetOrderData *god,
     583             :                   const char *already_paid_order_id)
     584             : {
     585             :   MHD_RESULT ret;
     586             :   char *taler_pay_uri;
     587             :   char *order_status_url;
     588             :   struct GNUNET_TIME_Relative remaining;
     589             : 
     590          32 :   remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
     591          32 :   if ( (! GNUNET_TIME_relative_is_zero (remaining)) &&
     592             :        (NULL == already_paid_order_id) )
     593             :   {
     594             :     /* long polling: do not queue a response, suspend connection instead */
     595           6 :     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     596             :                 "Suspending request: long polling for payment\n");
     597           6 :     suspend_god (god);
     598           6 :     return MHD_YES;
     599             :   }
     600             : 
     601             :   /* Check if resource_id has been paid for in the same session
     602             :    * with another order_id.
     603             :    */
     604          26 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     605             :               "Sending payment request\n");
     606          26 :   taler_pay_uri = TMH_make_taler_pay_uri (god->sc.con,
     607             :                                           god->order_id,
     608             :                                           god->session_id,
     609          26 :                                           god->hc->instance->settings.id,
     610             :                                           &god->claim_token);
     611          26 :   order_status_url = TMH_make_order_status_url (god->sc.con,
     612             :                                                 god->order_id,
     613             :                                                 god->session_id,
     614          26 :                                                 god->hc->instance->settings.id,
     615             :                                                 &god->claim_token,
     616             :                                                 NULL);
     617          26 :   if ( (NULL == taler_pay_uri) ||
     618             :        (NULL == order_status_url) )
     619             :   {
     620           0 :     GNUNET_break_op (0);
     621           0 :     GNUNET_free (taler_pay_uri);
     622           0 :     GNUNET_free (order_status_url);
     623           0 :     return TALER_MHD_reply_with_error (god->sc.con,
     624             :                                        MHD_HTTP_BAD_REQUEST,
     625             :                                        TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
     626             :                                        "host");
     627             :   }
     628          26 :   if (god->generate_html)
     629             :   {
     630           4 :     if (NULL != already_paid_order_id)
     631             :     {
     632             :       struct MHD_Response *reply;
     633             : 
     634           2 :       GNUNET_assert (NULL != god->fulfillment_url);
     635           2 :       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     636             :                   "Redirecting to already paid order %s via fulfillment URL %s\n",
     637             :                   already_paid_order_id,
     638             :                   god->fulfillment_url);
     639           2 :       reply = MHD_create_response_from_buffer (0,
     640             :                                                NULL,
     641             :                                                MHD_RESPMEM_PERSISTENT);
     642           2 :       if (NULL == reply)
     643             :       {
     644           0 :         GNUNET_break (0);
     645           0 :         return MHD_NO;
     646             :       }
     647           2 :       GNUNET_break (MHD_YES ==
     648             :                     MHD_add_response_header (reply,
     649             :                                              MHD_HTTP_HEADER_LOCATION,
     650             :                                              god->fulfillment_url));
     651             :       {
     652             :         MHD_RESULT ret;
     653             : 
     654           2 :         ret = MHD_queue_response (god->sc.con,
     655             :                                   MHD_HTTP_FOUND,
     656             :                                   reply);
     657           2 :         MHD_destroy_response (reply);
     658           2 :         return ret;
     659             :       }
     660             :     }
     661             : 
     662             :     {
     663             :       char *qr;
     664             : 
     665           2 :       qr = TMH_create_qrcode (taler_pay_uri);
     666           2 :       if (NULL == qr)
     667             :       {
     668           0 :         GNUNET_break (0);
     669           0 :         return MHD_NO;
     670             :       }
     671             :       {
     672             :         enum GNUNET_GenericReturnValue res;
     673             :         json_t *context;
     674             : 
     675           2 :         context = GNUNET_JSON_PACK (
     676             :           GNUNET_JSON_pack_string ("taler_pay_uri",
     677             :                                    taler_pay_uri),
     678             :           GNUNET_JSON_pack_string ("order_status_url",
     679             :                                    order_status_url),
     680             :           GNUNET_JSON_pack_string ("taler_pay_qrcode_svg",
     681             :                                    qr),
     682             :           GNUNET_JSON_pack_string ("order_summary",
     683             :                                    get_order_summary (god)));
     684           2 :         res = TMH_return_from_template (god->sc.con,
     685             :                                         MHD_HTTP_PAYMENT_REQUIRED,
     686             :                                         "request_payment",
     687           2 :                                         god->hc->instance->settings.id,
     688             :                                         taler_pay_uri,
     689             :                                         context);
     690           2 :         if (GNUNET_SYSERR == res)
     691             :         {
     692           0 :           GNUNET_break (0);
     693           0 :           ret = MHD_NO;
     694             :         }
     695             :         else
     696             :         {
     697           2 :           ret = MHD_YES;
     698             :         }
     699           2 :         json_decref (context);
     700             :       }
     701           2 :       GNUNET_free (qr);
     702             :     }
     703             :   }
     704             :   else /* end of 'generate HTML' */
     705             :   {
     706          22 :     ret = TALER_MHD_REPLY_JSON_PACK (
     707             :       god->sc.con,
     708             :       MHD_HTTP_PAYMENT_REQUIRED,
     709             :       GNUNET_JSON_pack_string ("taler_pay_uri",
     710             :                                taler_pay_uri),
     711             :       GNUNET_JSON_pack_allow_null (
     712             :         GNUNET_JSON_pack_string ("fulfillment_url",
     713             :                                  god->fulfillment_url)),
     714             :       GNUNET_JSON_pack_allow_null (
     715             :         GNUNET_JSON_pack_string ("already_paid_order_id",
     716             :                                  already_paid_order_id)));
     717             :   }
     718          24 :   GNUNET_free (taler_pay_uri);
     719          24 :   GNUNET_free (order_status_url);
     720          24 :   return ret;
     721             : }
     722             : 
     723             : 
     724             : /**
     725             :  * Function called with detailed information about a refund.
     726             :  * It is responsible for packing up the data to return.
     727             :  *
     728             :  * @param cls closure
     729             :  * @param refund_serial unique serial number of the refund
     730             :  * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
     731             :  * @param coin_pub public coin from which the refund comes from
     732             :  * @param exchange_url URL of the exchange that issued @a coin_pub
     733             :  * @param rtransaction_id identificator of the refund
     734             :  * @param reason human-readable explanation of the refund
     735             :  * @param refund_amount refund amount which is being taken from @a coin_pub
     736             :  * @param pending true if the this refund was not yet processed by the wallet/exchange
     737             :  */
     738             : static void
     739          10 : process_refunds_cb (void *cls,
     740             :                     uint64_t refund_serial,
     741             :                     struct GNUNET_TIME_Absolute timestamp,
     742             :                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
     743             :                     const char *exchange_url,
     744             :                     uint64_t rtransaction_id,
     745             :                     const char *reason,
     746             :                     const struct TALER_Amount *refund_amount,
     747             :                     bool pending)
     748             : {
     749          10 :   struct GetOrderData *god = cls;
     750             : 
     751          10 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     752             :               "Found refund of %s for coin %s with reason `%s' in database\n",
     753             :               TALER_amount2s (refund_amount),
     754             :               TALER_B2S (coin_pub),
     755             :               reason);
     756          10 :   god->refund_available |= pending;
     757          10 :   if (god->refunded)
     758             :   {
     759           3 :     GNUNET_assert (0 <=
     760             :                    TALER_amount_add (&god->refund_amount,
     761             :                                      &god->refund_amount,
     762             :                                      refund_amount));
     763           3 :     return;
     764             :   }
     765           7 :   god->refund_amount = *refund_amount;
     766           7 :   god->refunded = true;
     767             : }
     768             : 
     769             : 
     770             : /**
     771             :  * Clean up the session state for a GET /orders/$ID request.
     772             :  *
     773             :  * @param cls must be a `struct GetOrderData *`
     774             :  */
     775             : static void
     776          58 : god_cleanup (void *cls)
     777             : {
     778          58 :   struct GetOrderData *god = cls;
     779             : 
     780          58 :   if (NULL != god->contract_terms)
     781             :   {
     782          56 :     json_decref (god->contract_terms);
     783          56 :     god->contract_terms = NULL;
     784             :   }
     785          58 :   if (NULL != god->refund_eh)
     786             :   {
     787           2 :     TMH_db->event_listen_cancel (god->refund_eh);
     788           2 :     god->refund_eh = NULL;
     789             :   }
     790          58 :   if (NULL != god->pay_eh)
     791             :   {
     792           6 :     TMH_db->event_listen_cancel (god->pay_eh);
     793           6 :     god->pay_eh = NULL;
     794             :   }
     795          58 :   GNUNET_free (god);
     796          58 : }
     797             : 
     798             : 
     799             : MHD_RESULT
     800          64 : TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
     801             :                    struct MHD_Connection *connection,
     802             :                    struct TMH_HandlerContext *hc)
     803             : {
     804          64 :   struct GetOrderData *god = hc->ctx;
     805          64 :   const char *order_id = hc->infix;
     806             :   enum GNUNET_DB_QueryStatus qs;
     807          64 :   bool contract_match = false;
     808          64 :   bool token_match = false;
     809          64 :   bool h_contract_provided = false;
     810          64 :   bool claim_token_provided = false;
     811          64 :   bool contract_available = false;
     812             :   const char *merchant_base_url;
     813             : 
     814          64 :   if (NULL == god)
     815             :   {
     816          58 :     god = GNUNET_new (struct GetOrderData);
     817          58 :     hc->ctx = god;
     818          58 :     hc->cc = &god_cleanup;
     819          58 :     god->sc.con = connection;
     820          58 :     god->hc = hc;
     821          58 :     god->order_id = order_id;
     822          58 :     god->generate_html = TMH_MHD_test_html_desired (connection);
     823             : 
     824             : 
     825             :     /* first-time initialization / sanity checks */
     826             :     {
     827             :       const char *cts;
     828             : 
     829          58 :       cts = MHD_lookup_connection_value (connection,
     830             :                                          MHD_GET_ARGUMENT_KIND,
     831             :                                          "h_contract");
     832          79 :       if ( (NULL != cts) &&
     833             :            (GNUNET_OK !=
     834          21 :             GNUNET_CRYPTO_hash_from_string (cts,
     835             :                                             &god->h_contract_terms)) )
     836             :       {
     837             :         /* cts has wrong encoding */
     838           0 :         GNUNET_break_op (0);
     839           0 :         return TALER_MHD_reply_with_error (connection,
     840             :                                            MHD_HTTP_BAD_REQUEST,
     841             :                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
     842             :                                            "h_contract");
     843             :       }
     844          58 :       if (NULL != cts)
     845          21 :         h_contract_provided = true;
     846             :     }
     847             : 
     848             :     {
     849             :       const char *ct;
     850             : 
     851          58 :       ct = MHD_lookup_connection_value (connection,
     852             :                                         MHD_GET_ARGUMENT_KIND,
     853             :                                         "token");
     854          79 :       if ( (NULL != ct) &&
     855             :            (GNUNET_OK !=
     856          21 :             GNUNET_STRINGS_string_to_data (ct,
     857             :                                            strlen (ct),
     858          21 :                                            &god->claim_token,
     859             :                                            sizeof (god->claim_token))) )
     860             :       {
     861             :         /* ct has wrong encoding */
     862           0 :         GNUNET_break_op (0);
     863           0 :         return TALER_MHD_reply_with_error (connection,
     864             :                                            MHD_HTTP_BAD_REQUEST,
     865             :                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
     866             :                                            "token");
     867             :       }
     868          58 :       if (NULL != ct)
     869          21 :         claim_token_provided = true;
     870             :     }
     871          58 :     god->session_id = MHD_lookup_connection_value (connection,
     872             :                                                    MHD_GET_ARGUMENT_KIND,
     873             :                                                    "session_id");
     874             : 
     875             :     /* process await_refund_obtained argument */
     876             :     {
     877             :       const char *await_refund_obtained_s;
     878             : 
     879             :       await_refund_obtained_s =
     880          58 :         MHD_lookup_connection_value (connection,
     881             :                                      MHD_GET_ARGUMENT_KIND,
     882             :                                      "await_refund_obtained");
     883          58 :       god->sc.awaiting_refund_obtained =
     884             :         (NULL != await_refund_obtained_s)
     885           0 :         ? 0 == strcasecmp (await_refund_obtained_s,
     886             :                            "yes")
     887          58 :         : false;
     888          58 :       if (god->sc.awaiting_refund_obtained)
     889           0 :         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     890             :                     "Awaiting refund obtained\n");
     891             :     }
     892             : 
     893             :     {
     894             :       const char *min_refund;
     895             : 
     896          58 :       min_refund = MHD_lookup_connection_value (connection,
     897             :                                                 MHD_GET_ARGUMENT_KIND,
     898             :                                                 "refund");
     899          58 :       if (NULL != min_refund)
     900             :       {
     901           2 :         if ( (GNUNET_OK !=
     902           2 :               TALER_string_to_amount (min_refund,
     903           2 :                                       &god->sc.refund_expected)) ||
     904           2 :              (0 != strcasecmp (god->sc.refund_expected.currency,
     905             :                                TMH_currency) ) )
     906             :         {
     907           0 :           GNUNET_break_op (0);
     908           0 :           return TALER_MHD_reply_with_error (connection,
     909             :                                              MHD_HTTP_BAD_REQUEST,
     910             :                                              TALER_EC_GENERIC_PARAMETER_MALFORMED,
     911             :                                              "refund");
     912             :         }
     913           2 :         god->sc.awaiting_refund = true;
     914           2 :         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     915             :                     "Awaiting minimum refund of %s\n",
     916             :                     min_refund);
     917             :       }
     918             :     }
     919             : 
     920             : 
     921             :     /* process timeout_ms argument */
     922             :     {
     923             :       const char *long_poll_timeout_ms;
     924             : 
     925          58 :       long_poll_timeout_ms = MHD_lookup_connection_value (connection,
     926             :                                                           MHD_GET_ARGUMENT_KIND,
     927             :                                                           "timeout_ms");
     928          58 :       if (NULL != long_poll_timeout_ms)
     929             :       {
     930             :         unsigned int timeout_ms;
     931             :         char dummy;
     932             : 
     933           6 :         if (1 != sscanf (long_poll_timeout_ms,
     934             :                          "%u%c",
     935             :                          &timeout_ms,
     936             :                          &dummy))
     937             :         {
     938           0 :           GNUNET_break_op (0);
     939           0 :           return TALER_MHD_reply_with_error (connection,
     940             :                                              MHD_HTTP_BAD_REQUEST,
     941             :                                              TALER_EC_GENERIC_PARAMETER_MALFORMED,
     942             :                                              "timeout_ms (must be non-negative number)");
     943             :         }
     944             :         /* If HTML is requested, we never long poll. Makes no sense */
     945           6 :         if (! god->generate_html)
     946             :         {
     947             :           struct GNUNET_TIME_Relative timeout;
     948             : 
     949           6 :           timeout = GNUNET_TIME_relative_multiply (
     950             :             GNUNET_TIME_UNIT_MILLISECONDS,
     951             :             timeout_ms);
     952             :           god->sc.long_poll_timeout
     953           6 :             = GNUNET_TIME_relative_to_absolute (timeout);
     954           6 :           if (! GNUNET_TIME_relative_is_zero (timeout))
     955             :           {
     956           6 :             if (god->sc.awaiting_refund ||
     957           4 :                 god->sc.awaiting_refund_obtained)
     958             :             {
     959             : #ifndef TALER_API_VERSION
     960             : #define TALER_DBEVENT_MERCHANT_REFUND_OBTAINED 1104
     961             : #endif
     962           4 :               struct TMH_OrderPayEventP refund_eh = {
     963           2 :                 .header.size = htons (sizeof (refund_eh)),
     964           2 :                 .header.type = htons (god->sc.awaiting_refund_obtained
     965             :                                       ? TALER_DBEVENT_MERCHANT_REFUND_OBTAINED
     966             :                                       : TALER_DBEVENT_MERCHANT_ORDER_REFUND),
     967           2 :                 .merchant_pub = hc->instance->merchant_pub
     968             :               };
     969             : 
     970           2 :               GNUNET_CRYPTO_hash (god->order_id,
     971             :                                   strlen (god->order_id),
     972             :                                   &refund_eh.h_order_id);
     973           2 :               GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     974             :                           "Subscribing to refunds on %s\n",
     975             :                           god->order_id);
     976           2 :               god->refund_eh = TMH_db->event_listen (TMH_db->cls,
     977             :                                                      &refund_eh.header,
     978             :                                                      timeout,
     979             :                                                      &resume_by_event,
     980             :                                                      god);
     981             :             }
     982             :             {
     983           6 :               struct TMH_OrderPayEventP pay_eh = {
     984           6 :                 .header.size = htons (sizeof (pay_eh)),
     985           6 :                 .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
     986           6 :                 .merchant_pub = hc->instance->merchant_pub
     987             :               };
     988             : 
     989           6 :               GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     990             :                           "Subscribing to payments on %s\n",
     991             :                           god->order_id);
     992           6 :               GNUNET_CRYPTO_hash (god->order_id,
     993             :                                   strlen (god->order_id),
     994             :                                   &pay_eh.h_order_id);
     995           6 :               god->pay_eh = TMH_db->event_listen (TMH_db->cls,
     996             :                                                   &pay_eh.header,
     997             :                                                   timeout,
     998             :                                                   &resume_by_event,
     999             :                                                   god);
    1000             :             }
    1001             :           } /* end of timeout non-zero */
    1002             :         } /* end of HTML generation NOT requested */
    1003             :       } /* end of timeout_ms argument provided */
    1004             :     } /* end of timeout_ms argument handling */
    1005             : 
    1006             :   } /* end of first-time initialization / sanity checks */
    1007             : 
    1008          64 :   if (god->suspended)
    1009             :   {
    1010           0 :     god->suspended = false;
    1011           0 :     GNUNET_CONTAINER_DLL_remove (god_head,
    1012             :                                  god_tail,
    1013             :                                  god);
    1014             :   }
    1015             : 
    1016             :   /* Convert order_id to h_contract_terms */
    1017          64 :   TMH_db->preflight (TMH_db->cls);
    1018          64 :   if (NULL == god->contract_terms)
    1019             :   {
    1020             :     uint64_t order_serial;
    1021             :     struct TALER_ClaimTokenP db_claim_token;
    1022             : 
    1023          64 :     qs = TMH_db->lookup_contract_terms (TMH_db->cls,
    1024          64 :                                         hc->instance->settings.id,
    1025             :                                         order_id,
    1026             :                                         &god->contract_terms,
    1027             :                                         &order_serial,
    1028             :                                         &db_claim_token);
    1029          64 :     if (0 > qs)
    1030             :     {
    1031             :       /* single, read-only SQL statements should never cause
    1032             :          serialization problems */
    1033           0 :       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
    1034             :       /* Always report on hard error as well to enable diagnostics */
    1035           0 :       GNUNET_break (0);
    1036           0 :       return TALER_MHD_reply_with_error (connection,
    1037             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    1038             :                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    1039             :                                          "lookup_contract_terms");
    1040             :     }
    1041             : 
    1042             :     /* Note: when "!ord.requireClaimToken" and the client does not provide
    1043             :        a claim token (all zeros!), then token_match==TRUE below: */
    1044          64 :     token_match = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
    1045          64 :                   && (0 == GNUNET_memcmp (&db_claim_token,
    1046             :                                           &god->claim_token));
    1047             :   }
    1048             : 
    1049             :   /* Check if client provided the right hash code of the contract terms */
    1050          64 :   if (NULL != god->contract_terms)
    1051             :   {
    1052             :     struct GNUNET_HashCode h;
    1053             : 
    1054          45 :     contract_available = true;
    1055          45 :     if (GNUNET_OK !=
    1056          45 :         TALER_JSON_contract_hash (god->contract_terms,
    1057             :                                   &h))
    1058             :     {
    1059           0 :       GNUNET_break (0);
    1060           4 :       return TALER_MHD_reply_with_error (connection,
    1061             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    1062             :                                          TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
    1063             :                                          "contract terms");
    1064             :     }
    1065          45 :     contract_match = (0 ==
    1066          45 :                       GNUNET_memcmp (&h,
    1067             :                                      &god->h_contract_terms));
    1068          45 :     if ( (GNUNET_NO ==
    1069          45 :           GNUNET_is_zero (&god->h_contract_terms)) &&
    1070          26 :          (! contract_match) )
    1071             :     {
    1072           4 :       GNUNET_break_op (0);
    1073           4 :       return TALER_MHD_reply_with_error (
    1074             :         connection,
    1075             :         MHD_HTTP_FORBIDDEN,
    1076             :         TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
    1077             :         NULL);
    1078             :     }
    1079             :   }
    1080             : 
    1081          60 :   if (contract_available)
    1082             :   {
    1083          41 :     god->claimed = true;
    1084             :   }
    1085             :   else
    1086             :   {
    1087             :     struct TALER_ClaimTokenP db_claim_token;
    1088             :     struct GNUNET_HashCode unused;
    1089             : 
    1090          19 :     qs = TMH_db->lookup_order (TMH_db->cls,
    1091          19 :                                hc->instance->settings.id,
    1092             :                                order_id,
    1093             :                                &db_claim_token,
    1094             :                                &unused,
    1095          19 :                                (NULL == god->contract_terms)
    1096             :                                ? &god->contract_terms
    1097             :                                : NULL);
    1098          19 :     if (0 > qs)
    1099             :     {
    1100             :       /* single, read-only SQL statements should never cause
    1101             :          serialization problems */
    1102           0 :       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
    1103             :       /* Always report on hard error as well to enable diagnostics */
    1104           0 :       GNUNET_break (0);
    1105           2 :       return TALER_MHD_reply_with_error (connection,
    1106             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    1107             :                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    1108             :                                          "lookup_order");
    1109             :     }
    1110          19 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    1111             :     {
    1112           2 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1113             :                   "Unknown order id given: `%s'\n",
    1114             :                   order_id);
    1115           2 :       return TALER_MHD_reply_with_error (connection,
    1116             :                                          MHD_HTTP_NOT_FOUND,
    1117             :                                          TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
    1118             :                                          order_id);
    1119             :     }
    1120             :     /* Note: when "!ord.requireClaimToken" and the client does not provide
    1121             :        a claim token (all zeros!), then token_match==TRUE below: */
    1122          34 :     token_match = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
    1123          17 :                   (0 == GNUNET_memcmp (&db_claim_token,
    1124             :                                        &god->claim_token));
    1125             :   } /* end unclaimed order logic */
    1126             : 
    1127          58 :   GNUNET_assert (NULL != god->contract_terms);
    1128          58 :   merchant_base_url = json_string_value (json_object_get (god->contract_terms,
    1129             :                                                           "merchant_base_url"));
    1130          58 :   if (NULL == merchant_base_url)
    1131             :   {
    1132           0 :     GNUNET_break (0);
    1133           0 :     return TALER_MHD_reply_with_error (connection,
    1134             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    1135             :                                        TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
    1136             :                                        order_id);
    1137             :   }
    1138             : 
    1139          58 :   if (NULL == god->fulfillment_url)
    1140          58 :     god->fulfillment_url = json_string_value (json_object_get (
    1141          58 :                                                 god->contract_terms,
    1142             :                                                 "fulfillment_url"));
    1143          58 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1144             :               "Token match: %d, contract_available: %d, contract match: %d, claimed: %d\n",
    1145             :               token_match,
    1146             :               contract_available,
    1147             :               contract_match,
    1148             :               god->claimed);
    1149             : 
    1150          58 :   if (claim_token_provided && ! token_match)
    1151             :   {
    1152             :     /* Authentication provided but wrong. */
    1153           4 :     GNUNET_break_op (0);
    1154           4 :     return TALER_MHD_reply_with_error (connection,
    1155             :                                        MHD_HTTP_FORBIDDEN,
    1156             :                                        TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
    1157             :                                        "authentication with claim token provided but wrong");
    1158             :   }
    1159             : 
    1160          54 :   if (h_contract_provided && ! contract_match)
    1161             :   {
    1162             :     /* Authentication provided but wrong. */
    1163           0 :     GNUNET_break_op (0);
    1164             :     /* FIXME: use better error code */
    1165           0 :     return TALER_MHD_reply_with_error (connection,
    1166             :                                        MHD_HTTP_FORBIDDEN,
    1167             :                                        TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
    1168             :                                        "authentication with h_contract provided but wrong");
    1169             :   }
    1170             : 
    1171          54 :   if (! (token_match ||
    1172             :          contract_match) )
    1173             :   {
    1174             :     const char *public_reorder_url;
    1175             : 
    1176           5 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1177             :                 "Neither claim token nor contract matched\n");
    1178           5 :     public_reorder_url = json_string_value (json_object_get (
    1179           5 :                                               god->contract_terms,
    1180             :                                               "public_reorder_url"));
    1181             :     /* Client has no rights to this order */
    1182           5 :     if (NULL == public_reorder_url)
    1183             :     {
    1184             :       /* We cannot give the client a new order, just fail */
    1185           2 :       if (! GNUNET_is_zero (&god->h_contract_terms))
    1186             :       {
    1187           0 :         GNUNET_break_op (0);
    1188           0 :         return TALER_MHD_reply_with_error (
    1189             :           connection,
    1190             :           MHD_HTTP_FORBIDDEN,
    1191             :           TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
    1192             :           NULL);
    1193             :       }
    1194           2 :       return TALER_MHD_reply_with_error (connection,
    1195             :                                          MHD_HTTP_FORBIDDEN,
    1196             :                                          TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
    1197             :                                          "no 'public_reorder_url'");
    1198             :     }
    1199             :     /* We have a fulfillment URL, redirect the client there, maybe
    1200             :        the frontend can generate a fresh order for this new customer */
    1201           3 :     if (god->generate_html)
    1202             :     {
    1203             :       /* Contract was claimed (maybe by another device), so this client
    1204             :          cannot get the status information. Redirect to fulfillment page,
    1205             :          where the client may be able to pickup a fresh order -- or might
    1206             :          be able authenticate via session ID */
    1207             :       struct MHD_Response *reply;
    1208             :       MHD_RESULT ret;
    1209             : 
    1210           0 :       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1211             :                   "Contract claimed, redirecting to fulfillment page for order %s\n",
    1212             :                   order_id);
    1213           0 :       reply = MHD_create_response_from_buffer (0,
    1214             :                                                NULL,
    1215             :                                                MHD_RESPMEM_PERSISTENT);
    1216           0 :       if (NULL == reply)
    1217             :       {
    1218           0 :         GNUNET_break (0);
    1219           0 :         return MHD_NO;
    1220             :       }
    1221           0 :       GNUNET_break (MHD_YES ==
    1222             :                     MHD_add_response_header (reply,
    1223             :                                              MHD_HTTP_HEADER_LOCATION,
    1224             :                                              public_reorder_url));
    1225           0 :       ret = MHD_queue_response (connection,
    1226             :                                 MHD_HTTP_FOUND,
    1227             :                                 reply);
    1228           0 :       MHD_destroy_response (reply);
    1229           0 :       return ret;
    1230             :     }
    1231             :     /* Need to generate JSON reply */
    1232           3 :     return TALER_MHD_REPLY_JSON_PACK (
    1233             :       connection,
    1234             :       MHD_HTTP_ACCEPTED,
    1235             :       GNUNET_JSON_pack_string ("public_reorder_url",
    1236             :                                public_reorder_url));
    1237             :   }
    1238             : 
    1239          49 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1240             :               "Claim token or contract matched\n");
    1241             : 
    1242          49 :   if ( (NULL != god->session_id) &&
    1243          17 :        (NULL != god->fulfillment_url) )
    1244             :   {
    1245             :     /* Check if client paid for this fulfillment article
    1246             :        already within this session, but using a different
    1247             :        order ID. If so, redirect the client to the order
    1248             :        it already paid.  Allows, for example, the case
    1249             :        where a mobile phone pays for a browser's session,
    1250             :        where the mobile phone has a different order
    1251             :        ID (because it purchased the article earlier)
    1252             :        than the one that the browser is waiting for. */
    1253          17 :     char *already_paid_order_id = NULL;
    1254             : 
    1255          17 :     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1256             :                 "Running re-purchase detection for %s/%s\n",
    1257             :                 god->session_id,
    1258             :                 god->fulfillment_url);
    1259          17 :     qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
    1260          17 :                                               hc->instance->settings.id,
    1261             :                                               god->fulfillment_url,
    1262             :                                               god->session_id,
    1263             :                                               &already_paid_order_id);
    1264          17 :     if (qs < 0)
    1265             :     {
    1266             :       /* single, read-only SQL statements should never cause
    1267             :          serialization problems */
    1268           0 :       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
    1269             :       /* Always report on hard error as well to enable diagnostics */
    1270           0 :       GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
    1271          16 :       return TALER_MHD_reply_with_error (connection,
    1272             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    1273             :                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    1274             :                                          "order by fulfillment");
    1275             :     }
    1276          17 :     if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
    1277           7 :          (0 != strcmp (order_id,
    1278             :                        already_paid_order_id)) )
    1279             :     {
    1280             :       MHD_RESULT ret;
    1281             : 
    1282          16 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1283             :                   "Sending pay request for order %s (already paid: %s)\n",
    1284             :                   order_id,
    1285             :                   already_paid_order_id);
    1286          16 :       ret = send_pay_request (god,
    1287             :                               already_paid_order_id);
    1288          16 :       GNUNET_free (already_paid_order_id);
    1289          16 :       return ret;
    1290             :     }
    1291           1 :     GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
    1292           1 :     GNUNET_free (already_paid_order_id);
    1293             :   }
    1294             : 
    1295          33 :   if (! god->claimed)
    1296             :   {
    1297             :     /* Order is unclaimed, no need to check for payments or even
    1298             :        refunds, simply always generate payment request */
    1299           7 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1300             :                 "Order unclaimed, sending pay request for order %s\n",
    1301             :                 order_id);
    1302           7 :     return send_pay_request (god,
    1303             :                              NULL);
    1304             :   }
    1305             : 
    1306             :   {
    1307             :     /* Check if paid. */
    1308             :     struct GNUNET_HashCode h_contract;
    1309             :     bool paid;
    1310             : 
    1311          26 :     qs = TMH_db->lookup_order_status (TMH_db->cls,
    1312          26 :                                       hc->instance->settings.id,
    1313             :                                       order_id,
    1314             :                                       &h_contract,
    1315             :                                       &paid);
    1316          26 :     if (0 >= qs)
    1317             :     {
    1318             :       /* Always report on hard error as well to enable diagnostics */
    1319           0 :       GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
    1320           9 :       return TALER_MHD_reply_with_error (connection,
    1321             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    1322             :                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    1323             :                                          "lookup_order_status");
    1324             :     }
    1325          26 :     GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
    1326          26 :     if (! paid)
    1327             :     {
    1328           9 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1329             :                   "Order claimed but unpaid, sending pay request for order %s\n",
    1330             :                   order_id);
    1331           9 :       return send_pay_request (god,
    1332             :                                NULL);
    1333             :     }
    1334             :   }
    1335             : 
    1336             :   /* At this point, we know the contract was paid. Let's check for
    1337             :      refunds. First, clear away refunds found from previous invocations. */
    1338          17 :   GNUNET_assert (GNUNET_OK ==
    1339             :                  TALER_amount_set_zero (TMH_currency,
    1340             :                                         &god->refund_amount));
    1341          17 :   qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
    1342          17 :                                         hc->instance->settings.id,
    1343          17 :                                         &god->h_contract_terms,
    1344             :                                         &process_refunds_cb,
    1345             :                                         god);
    1346          17 :   if (0 > qs)
    1347             :   {
    1348           0 :     GNUNET_break (0);
    1349           0 :     return TALER_MHD_reply_with_error (connection,
    1350             :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    1351             :                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
    1352             :                                        "lookup_refunds_detailed");
    1353             :   }
    1354             : 
    1355          17 :   if ( ((god->sc.awaiting_refund) &&
    1356           4 :         ( (! god->refunded) ||
    1357           2 :           (1 != TALER_amount_cmp (&god->refund_amount,
    1358           2 :                                   &god->sc.refund_expected)) )) ||
    1359          17 :        ( (god->sc.awaiting_refund_obtained) &&
    1360           0 :          (god->refund_available) ) )
    1361             :   {
    1362             :     /* Client is waiting for a refund larger than what we have, suspend
    1363             :        until timeout */
    1364             :     struct GNUNET_TIME_Relative remaining;
    1365             : 
    1366           0 :     remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
    1367           0 :     if (! GNUNET_TIME_relative_is_zero (remaining))
    1368             :     {
    1369             :       /* yes, indeed suspend */
    1370           0 :       if (god->sc.awaiting_refund)
    1371           0 :         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1372             :                     "Awaiting refund exceeding %s\n",
    1373             :                     TALER_amount2s (&god->sc.refund_expected));
    1374           0 :       if (god->sc.awaiting_refund_obtained)
    1375           0 :         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1376             :                     "Awaiting pending refunds\n");
    1377           0 :       suspend_god (god);
    1378           0 :       return MHD_YES;
    1379             :     }
    1380             :   }
    1381             : 
    1382             :   /* All operations done, build final response */
    1383          17 :   if (god->generate_html)
    1384             :   {
    1385             :     enum GNUNET_GenericReturnValue res;
    1386             : 
    1387           2 :     if (god->refund_available)
    1388             :     {
    1389             :       char *qr;
    1390             :       char *uri;
    1391             : 
    1392           0 :       GNUNET_assert (NULL != god->contract_terms);
    1393           0 :       uri = make_taler_refund_uri (merchant_base_url,
    1394             :                                    order_id);
    1395           0 :       if (NULL == uri)
    1396             :       {
    1397           0 :         GNUNET_break (0);
    1398           0 :         return TALER_MHD_reply_with_error (god->sc.con,
    1399             :                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
    1400             :                                            TALER_EC_GENERIC_ALLOCATION_FAILURE,
    1401             :                                            "refund URI");
    1402             :       }
    1403           0 :       qr = TMH_create_qrcode (uri);
    1404           0 :       if (NULL == qr)
    1405             :       {
    1406           0 :         GNUNET_break (0);
    1407           0 :         GNUNET_free (uri);
    1408           0 :         return TALER_MHD_reply_with_error (god->sc.con,
    1409             :                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
    1410             :                                            TALER_EC_GENERIC_ALLOCATION_FAILURE,
    1411             :                                            "qr code");
    1412             :       }
    1413             :       {
    1414             :         json_t *context;
    1415             : 
    1416           0 :         context = GNUNET_JSON_PACK (
    1417             :           GNUNET_JSON_pack_string ("order_summary",
    1418             :                                    get_order_summary (god)),
    1419             :           TALER_JSON_pack_amount ("refund_amount",
    1420             :                                   &god->refund_amount),
    1421             :           GNUNET_JSON_pack_string ("taler_refund_uri",
    1422             :                                    uri),
    1423             :           GNUNET_JSON_pack_string ("taler_refund_qrcode_svg",
    1424             :                                    qr));
    1425           0 :         res = TMH_return_from_template (god->sc.con,
    1426             :                                         MHD_HTTP_OK,
    1427             :                                         "offer_refund",
    1428           0 :                                         hc->instance->settings.id,
    1429             :                                         uri,
    1430             :                                         context);
    1431           0 :         json_decref (context);
    1432             :       }
    1433           0 :       GNUNET_free (uri);
    1434           0 :       GNUNET_free (qr);
    1435             :     }
    1436             :     else
    1437             :     {
    1438             :       json_t *context;
    1439             : 
    1440           2 :       context = GNUNET_JSON_PACK (
    1441             :         GNUNET_JSON_pack_object_incref ("contract_terms",
    1442             :                                         god->contract_terms),
    1443             :         GNUNET_JSON_pack_string ("order_summary",
    1444             :                                  get_order_summary (god)),
    1445             :         TALER_JSON_pack_amount ("refund_amount",
    1446             :                                 &god->refund_amount));
    1447           2 :       res = TMH_return_from_template (god->sc.con,
    1448             :                                       MHD_HTTP_OK,
    1449             :                                       "show_order_details",
    1450           2 :                                       hc->instance->settings.id,
    1451             :                                       NULL,
    1452             :                                       context);
    1453           2 :       json_decref (context);
    1454             :     }
    1455           2 :     if (GNUNET_SYSERR == res)
    1456             :     {
    1457           0 :       GNUNET_break (0);
    1458           0 :       return MHD_NO;
    1459             :     }
    1460           2 :     return MHD_YES;
    1461             :   }
    1462          15 :   return TALER_MHD_REPLY_JSON_PACK (
    1463             :     connection,
    1464             :     MHD_HTTP_OK,
    1465             :     GNUNET_JSON_pack_allow_null (
    1466             :       GNUNET_JSON_pack_string ("fulfillment_url",
    1467             :                                god->fulfillment_url)),
    1468             :     GNUNET_JSON_pack_bool ("refunded",
    1469             :                            god->refunded),
    1470             :     GNUNET_JSON_pack_bool ("refund_pending",
    1471             :                            god->refund_available),
    1472             :     TALER_JSON_pack_amount ("refund_amount",
    1473             :                             &god->refund_amount));
    1474             : }
    1475             : 
    1476             : 
    1477             : /* end of taler-merchant-httpd_get-orders-ID.c */

Generated by: LCOV version 1.14