merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_get-orders-ORDER_ID.c (50689B)


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