LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_get-orders-ID.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 307 521 58.9 %
Date: 2025-06-23 16:22:09 Functions: 17 20 85.0 %

          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          14 : TMH_force_wallet_get_order_resume (void)
     272             : {
     273             :   struct GetOrderData *god;
     274             : 
     275          14 :   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          14 : }
     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          32 : god_cleanup (void *cls)
     334             : {
     335          32 :   struct GetOrderData *god = cls;
     336             : 
     337          32 :   if (NULL != god->contract_terms_json)
     338             :   {
     339          32 :     json_decref (god->contract_terms_json);
     340          32 :     god->contract_terms_json = NULL;
     341             :   }
     342          32 :   if (NULL != god->contract_terms)
     343             :   {
     344          32 :     TALER_MERCHANT_contract_free (god->contract_terms);
     345          32 :     god->contract_terms = NULL;
     346             :   }
     347          32 :   if (NULL != god->session_eh)
     348             :   {
     349           8 :     TMH_db->event_listen_cancel (god->session_eh);
     350           8 :     god->session_eh = NULL;
     351             :   }
     352          32 :   if (NULL != god->refund_eh)
     353             :   {
     354           4 :     TMH_db->event_listen_cancel (god->refund_eh);
     355           4 :     god->refund_eh = NULL;
     356             :   }
     357          32 :   if (NULL != god->pay_eh)
     358             :   {
     359           8 :     TMH_db->event_listen_cancel (god->pay_eh);
     360           8 :     god->pay_eh = NULL;
     361             :   }
     362          32 :   GNUNET_free (god);
     363          32 : }
     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          32 : phase_end (struct GetOrderData *god,
     375             :            MHD_RESULT mret)
     376             : {
     377          32 :   god->phase = (MHD_YES == mret)
     378             :     ? GOP_RETURN_MHD_YES
     379          32 :     : GOP_RETURN_MHD_NO;
     380          32 : }
     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          32 : phase_init (struct GetOrderData *god)
     506             : {
     507          32 :   god->phase++;
     508          32 :   if (god->generate_html)
     509           0 :     return; /* If HTML is requested, we never actually long poll. */
     510          32 :   if (! GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout))
     511          24 :     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          40 : 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          40 :   TMH_db->preflight (TMH_db->cls);
     580          40 :   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          40 :     qs = TMH_db->lookup_contract_terms3 (
     589          40 :       TMH_db->cls,
     590          40 :       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          40 :     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          40 :       = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     617          40 :         && (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          40 :   if (NULL != god->contract_terms_json)
     623             :   {
     624          36 :     god->contract_available = true;
     625          36 :     if (GNUNET_YES ==
     626          36 :         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          36 :       if (GNUNET_OK !=
     645          36 :           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          36 :       god->contract_match = (0 ==
     656          36 :                              GNUNET_memcmp (&h,
     657             :                                             &god->h_contract_terms));
     658          36 :       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          40 :   if (god->contract_available)
     671             :   {
     672          36 :     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           4 :         (0 == GNUNET_memcmp (&db_claim_token,
     717             :                              &god->claim_token));
     718             :   } /* end unclaimed order logic */
     719          40 :   god->phase++;
     720             : }
     721             : 
     722             : 
     723             : /**
     724             :  * Parse contract terms.
     725             :  *
     726             :  * @param[in,out] god request context
     727             :  */
     728             : static void
     729          40 : phase_parse_contract (struct GetOrderData *god)
     730             : {
     731          40 :   GNUNET_break (NULL == god->contract_terms);
     732          40 :   god->contract_terms = TALER_MERCHANT_contract_parse (
     733             :     god->contract_terms_json,
     734             :     true);
     735             : 
     736          40 :   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          40 :   god->contract_parsed = true;
     745          40 :   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          40 :   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          40 : phase_check_client_access (struct GetOrderData *god)
     784             : {
     785          40 :   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          40 :   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          40 :   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          40 :   if (! (god->token_match ||
     815          36 :          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          40 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     888             :               "Claim token or contract matched\n");
     889          40 :   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          20 : 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          20 :   remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
     945          20 :   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          12 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     959             :               "Sending payment request\n");
     960          12 :   taler_pay_uri = TMH_make_taler_pay_uri (
     961             :     god->sc.con,
     962             :     god->order_id,
     963             :     god->session_id,
     964          12 :     god->hc->instance->settings.id,
     965             :     &god->claim_token);
     966          12 :   order_status_url = TMH_make_order_status_url (
     967             :     god->sc.con,
     968             :     god->order_id,
     969             :     god->session_id,
     970          12 :     god->hc->instance->settings.id,
     971             :     &god->claim_token,
     972             :     NULL);
     973          12 :   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          12 :   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          12 :     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          12 :   GNUNET_free (taler_pay_uri);
    1082          12 :   GNUNET_free (order_status_url);
    1083          12 :   phase_end (god,
    1084             :              ret);
    1085          12 :   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          40 : phase_check_paid (struct GetOrderData *god)
    1096             : {
    1097             :   enum GNUNET_DB_QueryStatus qs;
    1098             :   struct TALER_PrivateContractHashP h_contract;
    1099             : 
    1100          40 :   god->paid = false;
    1101          40 :   qs = TMH_db->lookup_order_status (
    1102          40 :     TMH_db->cls,
    1103          40 :     god->hc->instance->settings.id,
    1104             :     god->order_id,
    1105             :     &h_contract,
    1106             :     &god->paid);
    1107          40 :   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          40 :   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          40 : phase_redirect_to_paid_order (struct GetOrderData *god)
    1131             : {
    1132          40 :   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_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
    1187           2 :     GNUNET_free (already_paid_order_id);
    1188             :   }
    1189          32 :   god->phase++;
    1190          32 :   return false;
    1191             : }
    1192             : 
    1193             : 
    1194             : /**
    1195             :  * Check if the order has been paid, and if not
    1196             :  * request payment.
    1197             :  *
    1198             :  * @param[in,out] god request context
    1199             :  * @return true to exit due to suspension
    1200             :  */
    1201             : static bool
    1202          32 : phase_handle_unpaid (struct GetOrderData *god)
    1203             : {
    1204          32 :   if (god->paid)
    1205             :   {
    1206          20 :     god->phase++;
    1207          20 :     return false;
    1208             :   }
    1209          12 :   if (god->claimed)
    1210             :   {
    1211           8 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1212             :                 "Order claimed but unpaid, sending pay request for order %s\n",
    1213             :                 god->order_id);
    1214             :   }
    1215             :   else
    1216             :   {
    1217           4 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1218             :                 "Order unclaimed, sending pay request for order %s\n",
    1219             :                 god->order_id);
    1220             :   }
    1221          12 :   return send_pay_request (god,
    1222             :                            NULL);
    1223             : }
    1224             : 
    1225             : 
    1226             : /**
    1227             :  * Function called with detailed information about a refund.
    1228             :  * It is responsible for packing up the data to return.
    1229             :  *
    1230             :  * @param cls closure
    1231             :  * @param refund_serial unique serial number of the refund
    1232             :  * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
    1233             :  * @param coin_pub public coin from which the refund comes from
    1234             :  * @param exchange_url URL of the exchange that issued @a coin_pub
    1235             :  * @param rtransaction_id identificator of the refund
    1236             :  * @param reason human-readable explanation of the refund
    1237             :  * @param refund_amount refund amount which is being taken from @a coin_pub
    1238             :  * @param pending true if the this refund was not yet processed by the wallet/exchange
    1239             :  */
    1240             : static void
    1241          20 : process_refunds_cb (void *cls,
    1242             :                     uint64_t refund_serial,
    1243             :                     struct GNUNET_TIME_Timestamp timestamp,
    1244             :                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
    1245             :                     const char *exchange_url,
    1246             :                     uint64_t rtransaction_id,
    1247             :                     const char *reason,
    1248             :                     const struct TALER_Amount *refund_amount,
    1249             :                     bool pending)
    1250             : {
    1251          20 :   struct GetOrderData *god = cls;
    1252             : 
    1253             :   (void) refund_serial;
    1254             :   (void) timestamp;
    1255             :   (void) exchange_url;
    1256             :   (void) rtransaction_id;
    1257          20 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1258             :               "Found refund of %s for coin %s with reason `%s' in database\n",
    1259             :               TALER_amount2s (refund_amount),
    1260             :               TALER_B2S (coin_pub),
    1261             :               reason);
    1262          20 :   god->refund_pending |= pending;
    1263          20 :   if ( (GNUNET_OK !=
    1264          20 :         TALER_amount_cmp_currency (&god->refund_taken,
    1265          20 :                                    refund_amount)) ||
    1266             :        (GNUNET_OK !=
    1267          20 :         TALER_amount_cmp_currency (&god->refund_amount,
    1268             :                                    refund_amount)) )
    1269             :   {
    1270           0 :     god->bad_refund_currency_in_db = true;
    1271           0 :     return;
    1272             :   }
    1273          20 :   if (! pending)
    1274             :   {
    1275           4 :     GNUNET_assert (0 <=
    1276             :                    TALER_amount_add (&god->refund_taken,
    1277             :                                      &god->refund_taken,
    1278             :                                      refund_amount));
    1279             :   }
    1280          20 :   GNUNET_assert (0 <=
    1281             :                  TALER_amount_add (&god->refund_amount,
    1282             :                                    &god->refund_amount,
    1283             :                                    refund_amount));
    1284          20 :   god->refunded = true;
    1285             : }
    1286             : 
    1287             : 
    1288             : /**
    1289             :  * Check if the order has been refunded.
    1290             :  *
    1291             :  * @param[in,out] god request context
    1292             :  * @return true to exit due to suspension
    1293             :  */
    1294             : static bool
    1295          20 : phase_check_refunded (struct GetOrderData *god)
    1296             : {
    1297             :   enum GNUNET_DB_QueryStatus qs;
    1298             :   struct TALER_Amount refund_amount;
    1299             :   const char *refund_currency;
    1300             : 
    1301          20 :   switch (god->contract_terms->version)
    1302             :   {
    1303          20 :   case TALER_MERCHANT_CONTRACT_VERSION_0:
    1304          20 :     refund_amount = god->contract_terms->details.v0.brutto;
    1305          20 :     refund_currency = god->contract_terms->details.v0.brutto.currency;
    1306          20 :     break;
    1307           0 :   case TALER_MERCHANT_CONTRACT_VERSION_1:
    1308           0 :     if (god->choice_index < 0)
    1309             :     {
    1310             :       // order was not paid, no refund to be checked
    1311           0 :       god->phase++;
    1312           0 :       return false;
    1313             :     }
    1314           0 :     GNUNET_assert (god->choice_index <
    1315             :                    god->contract_terms->details.v1.choices_len);
    1316           0 :     refund_currency = god->contract_terms->details.v1.choices[god->choice_index]
    1317             :                       .amount.currency;
    1318           0 :     GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (refund_currency,
    1319             :                                                        &refund_amount));
    1320           0 :     break;
    1321           0 :   default:
    1322             :     {
    1323           0 :       GNUNET_break (0);
    1324           0 :       phase_fail (god,
    1325             :                   MHD_HTTP_INTERNAL_SERVER_ERROR,
    1326             :                   TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION,
    1327             :                   NULL);
    1328           0 :       return false;
    1329             :     }
    1330             :   }
    1331             : 
    1332          24 :   if ( (god->sc.awaiting_refund) &&
    1333             :        (GNUNET_OK !=
    1334           4 :         TALER_amount_cmp_currency (&refund_amount,
    1335           4 :                                    &god->sc.refund_expected)) )
    1336             :   {
    1337           0 :     GNUNET_break (0);
    1338           0 :     phase_fail (god,
    1339             :                 MHD_HTTP_CONFLICT,
    1340             :                 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
    1341             :                 refund_currency);
    1342           0 :     return false;
    1343             :   }
    1344             : 
    1345             :   /* At this point, we know the contract was paid. Let's check for
    1346             :      refunds. First, clear away refunds found from previous invocations. */
    1347          20 :   GNUNET_assert (GNUNET_OK ==
    1348             :                  TALER_amount_set_zero (refund_currency,
    1349             :                                         &god->refund_amount));
    1350          20 :   GNUNET_assert (GNUNET_OK ==
    1351             :                  TALER_amount_set_zero (refund_currency,
    1352             :                                         &god->refund_taken));
    1353          20 :   qs = TMH_db->lookup_refunds_detailed (
    1354          20 :     TMH_db->cls,
    1355          20 :     god->hc->instance->settings.id,
    1356          20 :     &god->h_contract_terms,
    1357             :     &process_refunds_cb,
    1358             :     god);
    1359          20 :   if (0 > qs)
    1360             :   {
    1361           0 :     GNUNET_break (0);
    1362           0 :     phase_fail (god,
    1363             :                 MHD_HTTP_INTERNAL_SERVER_ERROR,
    1364             :                 TALER_EC_GENERIC_DB_FETCH_FAILED,
    1365             :                 "lookup_refunds_detailed");
    1366           0 :     return false;
    1367             :   }
    1368          20 :   if (god->bad_refund_currency_in_db)
    1369             :   {
    1370           0 :     GNUNET_break (0);
    1371           0 :     phase_fail (god,
    1372             :                 MHD_HTTP_INTERNAL_SERVER_ERROR,
    1373             :                 TALER_EC_GENERIC_DB_FETCH_FAILED,
    1374             :                 "currency mix-up between contract price and refunds in database");
    1375           0 :     return false;
    1376             :   }
    1377          20 :   if ( ((god->sc.awaiting_refund) &&
    1378           8 :         ( (! god->refunded) ||
    1379           4 :           (1 != TALER_amount_cmp (&god->refund_amount,
    1380           4 :                                   &god->sc.refund_expected)) )) ||
    1381          20 :        ( (god->sc.awaiting_refund_obtained) &&
    1382           0 :          (god->refund_pending) ) )
    1383             :   {
    1384             :     /* Client is waiting for a refund larger than what we have, suspend
    1385             :        until timeout */
    1386             :     struct GNUNET_TIME_Relative remaining;
    1387             : 
    1388           0 :     remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
    1389           0 :     if ( (! GNUNET_TIME_relative_is_zero (remaining)) &&
    1390           0 :          (! god->generate_html) )
    1391             :     {
    1392             :       /* yes, indeed suspend */
    1393           0 :       if (god->sc.awaiting_refund)
    1394           0 :         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1395             :                     "Awaiting refund exceeding %s\n",
    1396             :                     TALER_amount2s (&god->sc.refund_expected));
    1397           0 :       if (god->sc.awaiting_refund_obtained)
    1398           0 :         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1399             :                     "Awaiting pending refunds\n");
    1400           0 :       suspend_god (god);
    1401           0 :       return true;
    1402             :     }
    1403             :   }
    1404          20 :   god->phase++;
    1405          20 :   return false;
    1406             : }
    1407             : 
    1408             : 
    1409             : /**
    1410             :  * Create a taler://refund/ URI for the given @a con and @a order_id
    1411             :  * and @a instance_id.
    1412             :  *
    1413             :  * @param merchant_base_url URL to take host and path from;
    1414             :  *        we cannot take it from the MHD connection as a browser
    1415             :  *        may have changed 'http' to 'https' and we MUST be consistent
    1416             :  *        with what the merchant's frontend used initially
    1417             :  * @param order_id the order id
    1418             :  * @return corresponding taler://refund/ URI, or NULL on missing "host"
    1419             :  */
    1420             : static char *
    1421           0 : make_taler_refund_uri (const char *merchant_base_url,
    1422             :                        const char *order_id)
    1423             : {
    1424           0 :   struct GNUNET_Buffer buf = { 0 };
    1425             :   char *url;
    1426             :   struct GNUNET_Uri uri;
    1427             : 
    1428           0 :   url = GNUNET_strdup (merchant_base_url);
    1429           0 :   if (-1 == GNUNET_uri_parse (&uri,
    1430             :                               url))
    1431             :   {
    1432           0 :     GNUNET_break (0);
    1433           0 :     GNUNET_free (url);
    1434           0 :     return NULL;
    1435             :   }
    1436           0 :   GNUNET_assert (NULL != order_id);
    1437           0 :   GNUNET_buffer_write_str (&buf,
    1438             :                            "taler");
    1439           0 :   if (0 == strcasecmp ("http",
    1440           0 :                        uri.scheme))
    1441           0 :     GNUNET_buffer_write_str (&buf,
    1442             :                              "+http");
    1443           0 :   GNUNET_buffer_write_str (&buf,
    1444             :                            "://refund/");
    1445           0 :   GNUNET_buffer_write_str (&buf,
    1446           0 :                            uri.host);
    1447           0 :   if (0 != uri.port)
    1448           0 :     GNUNET_buffer_write_fstr (&buf,
    1449             :                               ":%u",
    1450           0 :                               (unsigned int) uri.port);
    1451           0 :   if (NULL != uri.path)
    1452           0 :     GNUNET_buffer_write_path (&buf,
    1453           0 :                               uri.path);
    1454           0 :   GNUNET_buffer_write_path (&buf,
    1455             :                             order_id);
    1456           0 :   GNUNET_buffer_write_path (&buf,
    1457             :                             ""); // Trailing slash
    1458           0 :   GNUNET_free (url);
    1459           0 :   return GNUNET_buffer_reap_str (&buf);
    1460             : }
    1461             : 
    1462             : 
    1463             : /**
    1464             :  * Generate the order status response.
    1465             :  *
    1466             :  * @param[in,out] god request context
    1467             :  */
    1468             : static void
    1469          20 : phase_return_status (struct GetOrderData *god)
    1470             : {
    1471             :   /* All operations done, build final response */
    1472          20 :   if (! god->generate_html)
    1473             :   {
    1474          20 :     phase_end (god,
    1475          20 :                TALER_MHD_REPLY_JSON_PACK (
    1476             :                  god->sc.con,
    1477             :                  MHD_HTTP_OK,
    1478             :                  GNUNET_JSON_pack_allow_null (
    1479             :                    GNUNET_JSON_pack_string ("fulfillment_url",
    1480             :                                             god->contract_terms->fulfillment_url
    1481             :                                             )),
    1482             :                  GNUNET_JSON_pack_bool ("refunded",
    1483             :                                         god->refunded),
    1484             :                  GNUNET_JSON_pack_bool ("refund_pending",
    1485             :                                         god->refund_pending),
    1486             :                  TALER_JSON_pack_amount ("refund_taken",
    1487             :                                          &god->refund_taken),
    1488             :                  TALER_JSON_pack_amount ("refund_amount",
    1489             :                                          &god->refund_amount)));
    1490          20 :     return;
    1491             :   }
    1492             : 
    1493           0 :   if (god->refund_pending)
    1494             :   {
    1495             :     char *qr;
    1496             :     char *uri;
    1497             : 
    1498           0 :     GNUNET_assert (NULL != god->contract_terms_json);
    1499           0 :     uri = make_taler_refund_uri (god->contract_terms->merchant_base_url,
    1500             :                                  god->order_id);
    1501           0 :     if (NULL == uri)
    1502             :     {
    1503           0 :       GNUNET_break (0);
    1504           0 :       phase_fail (god,
    1505             :                   MHD_HTTP_INTERNAL_SERVER_ERROR,
    1506             :                   TALER_EC_GENERIC_ALLOCATION_FAILURE,
    1507             :                   "refund URI");
    1508           0 :       return;
    1509             :     }
    1510           0 :     qr = TMH_create_qrcode (uri);
    1511           0 :     if (NULL == qr)
    1512             :     {
    1513           0 :       GNUNET_break (0);
    1514           0 :       GNUNET_free (uri);
    1515           0 :       phase_fail (god,
    1516             :                   MHD_HTTP_INTERNAL_SERVER_ERROR,
    1517             :                   TALER_EC_GENERIC_ALLOCATION_FAILURE,
    1518             :                   "qr code");
    1519           0 :       return;
    1520             :     }
    1521             : 
    1522             :     {
    1523             :       enum GNUNET_GenericReturnValue res;
    1524             :       json_t *context;
    1525             : 
    1526           0 :       context = GNUNET_JSON_PACK (
    1527             :         GNUNET_JSON_pack_string ("order_summary",
    1528             :                                  get_order_summary (god)),
    1529             :         TALER_JSON_pack_amount ("refund_amount",
    1530             :                                 &god->refund_amount),
    1531             :         TALER_JSON_pack_amount ("refund_taken",
    1532             :                                 &god->refund_taken),
    1533             :         GNUNET_JSON_pack_string ("taler_refund_uri",
    1534             :                                  uri),
    1535             :         GNUNET_JSON_pack_string ("taler_refund_qrcode_svg",
    1536             :                                  qr));
    1537           0 :       res = TALER_TEMPLATING_reply (
    1538             :         god->sc.con,
    1539             :         MHD_HTTP_OK,
    1540             :         "offer_refund",
    1541           0 :         god->hc->instance->settings.id,
    1542             :         uri,
    1543             :         context);
    1544           0 :       GNUNET_break (GNUNET_OK == res);
    1545           0 :       json_decref (context);
    1546           0 :       phase_end (god,
    1547             :                  (GNUNET_SYSERR == res)
    1548             :                  ? MHD_NO
    1549             :                  : MHD_YES);
    1550             :     }
    1551           0 :     GNUNET_free (uri);
    1552           0 :     GNUNET_free (qr);
    1553           0 :     return;
    1554             :   }
    1555             : 
    1556             :   {
    1557             :     enum GNUNET_GenericReturnValue res;
    1558             :     json_t *context;
    1559             : 
    1560           0 :     context = GNUNET_JSON_PACK (
    1561             :       GNUNET_JSON_pack_object_incref ("contract_terms",
    1562             :                                       god->contract_terms_json),
    1563             :       GNUNET_JSON_pack_string ("order_summary",
    1564             :                                get_order_summary (god)),
    1565             :       TALER_JSON_pack_amount ("refund_amount",
    1566             :                               &god->refund_amount),
    1567             :       TALER_JSON_pack_amount ("refund_taken",
    1568             :                               &god->refund_taken));
    1569           0 :     res = TALER_TEMPLATING_reply (
    1570             :       god->sc.con,
    1571             :       MHD_HTTP_OK,
    1572             :       "show_order_details",
    1573           0 :       god->hc->instance->settings.id,
    1574             :       NULL,
    1575             :       context);
    1576           0 :     GNUNET_break (GNUNET_OK == res);
    1577           0 :     json_decref (context);
    1578           0 :     phase_end (god,
    1579             :                (GNUNET_SYSERR == res)
    1580             :                ? MHD_NO
    1581             :                : MHD_YES);
    1582             :   }
    1583             : }
    1584             : 
    1585             : 
    1586             : MHD_RESULT
    1587          40 : TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
    1588             :                    struct MHD_Connection *connection,
    1589             :                    struct TMH_HandlerContext *hc)
    1590             : {
    1591          40 :   struct GetOrderData *god = hc->ctx;
    1592             : 
    1593             :   (void) rh;
    1594          40 :   if (NULL == god)
    1595             :   {
    1596          32 :     god = GNUNET_new (struct GetOrderData);
    1597          32 :     hc->ctx = god;
    1598          32 :     hc->cc = &god_cleanup;
    1599          32 :     god->sc.con = connection;
    1600          32 :     god->hc = hc;
    1601          32 :     god->order_id = hc->infix;
    1602             :     god->generate_html
    1603          32 :       = TMH_MHD_test_html_desired (connection);
    1604             : 
    1605             :     /* first-time initialization / sanity checks */
    1606          32 :     TALER_MHD_parse_request_arg_auto (connection,
    1607             :                                       "h_contract",
    1608             :                                       &god->h_contract_terms,
    1609             :                                       god->h_contract_provided);
    1610          32 :     TALER_MHD_parse_request_arg_auto (connection,
    1611             :                                       "token",
    1612             :                                       &god->claim_token,
    1613             :                                       god->claim_token_provided);
    1614          32 :     if (! (TALER_MHD_arg_to_yna (connection,
    1615             :                                  "allow_refunded_for_repurchase",
    1616             :                                  TALER_EXCHANGE_YNA_NO,
    1617             :                                  &god->allow_refunded_for_repurchase)) )
    1618           0 :       return TALER_MHD_reply_with_error (connection,
    1619             :                                          MHD_HTTP_BAD_REQUEST,
    1620             :                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1621             :                                          "allow_refunded_for_repurchase");
    1622          32 :     god->session_id = MHD_lookup_connection_value (connection,
    1623             :                                                    MHD_GET_ARGUMENT_KIND,
    1624             :                                                    "session_id");
    1625             : 
    1626             :     /* process await_refund_obtained argument */
    1627             :     {
    1628             :       const char *await_refund_obtained_s;
    1629             : 
    1630             :       await_refund_obtained_s =
    1631          32 :         MHD_lookup_connection_value (connection,
    1632             :                                      MHD_GET_ARGUMENT_KIND,
    1633             :                                      "await_refund_obtained");
    1634          32 :       god->sc.awaiting_refund_obtained =
    1635             :         (NULL != await_refund_obtained_s)
    1636           0 :         ? 0 == strcasecmp (await_refund_obtained_s,
    1637             :                            "yes")
    1638          32 :         : false;
    1639          32 :       if (god->sc.awaiting_refund_obtained)
    1640           0 :         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1641             :                     "Awaiting refund obtained\n");
    1642             :     }
    1643             : 
    1644          32 :     TALER_MHD_parse_request_amount (connection,
    1645             :                                     "refund",
    1646             :                                     &god->sc.refund_expected);
    1647          32 :     if (TALER_amount_is_valid (&god->sc.refund_expected))
    1648             :     {
    1649           4 :       god->sc.awaiting_refund = true;
    1650           4 :       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1651             :                   "Awaiting minimum refund of %s\n",
    1652             :                   TALER_amount2s (&god->sc.refund_expected));
    1653             :     }
    1654          32 :     TALER_MHD_parse_request_timeout (connection,
    1655             :                                      &god->sc.long_poll_timeout);
    1656             :   }
    1657             : 
    1658          40 :   if (GNUNET_SYSERR == god->suspended)
    1659           0 :     return MHD_NO; /* we are in shutdown */
    1660          40 :   if (GNUNET_YES == god->suspended)
    1661             :   {
    1662           0 :     god->suspended = GNUNET_NO;
    1663           0 :     GNUNET_CONTAINER_DLL_remove (god_head,
    1664             :                                  god_tail,
    1665             :                                  god);
    1666             :   }
    1667             : 
    1668             :   while (1)
    1669             :   {
    1670         632 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1671             :                 "Handling request in phase %d\n",
    1672             :                 (int) god->phase);
    1673         336 :     switch (god->phase)
    1674             :     {
    1675          32 :     case GOP_INIT:
    1676          32 :       phase_init (god);
    1677          32 :       break;
    1678          40 :     case GOP_LOOKUP_TERMS:
    1679          40 :       phase_lookup_terms (god);
    1680          40 :       break;
    1681          40 :     case GOP_PARSE_CONTRACT:
    1682          40 :       phase_parse_contract (god);
    1683          40 :       break;
    1684          40 :     case GOP_CHECK_CLIENT_ACCESS:
    1685          40 :       phase_check_client_access (god);
    1686          40 :       break;
    1687          40 :     case GOP_CHECK_PAID:
    1688          40 :       phase_check_paid (god);
    1689          40 :       break;
    1690          40 :     case GOP_REDIRECT_TO_PAID_ORDER:
    1691          40 :       if (phase_redirect_to_paid_order (god))
    1692           2 :         return MHD_YES;
    1693          38 :       break;
    1694          32 :     case GOP_HANDLE_UNPAID:
    1695          32 :       if (phase_handle_unpaid (god))
    1696           6 :         return MHD_YES;
    1697          26 :       break;
    1698          20 :     case GOP_CHECK_REFUNDED:
    1699          20 :       if (phase_check_refunded (god))
    1700           0 :         return MHD_YES;
    1701          20 :       break;
    1702          20 :     case GOP_RETURN_STATUS:
    1703          20 :       phase_return_status (god);
    1704          20 :       break;
    1705          32 :     case GOP_RETURN_MHD_YES:
    1706          32 :       return MHD_YES;
    1707           0 :     case GOP_RETURN_MHD_NO:
    1708           0 :       return MHD_NO;
    1709             :     }
    1710             :   }
    1711             : }
    1712             : 
    1713             : 
    1714             : /* end of taler-merchant-httpd_get-orders-ID.c */

Generated by: LCOV version 1.16