LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_get-orders-ID.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 58.8 % 520 306
Test Date: 2025-10-31 14:20:14 Functions: 85.0 % 20 17

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

Generated by: LCOV version 2.0-1