merchant

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

taler-merchant-httpd_get-orders-ID.c (50222B)


      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 TMH_force_wallet_get_order_resume (void)
    272 {
    273   struct GetOrderData *god;
    274 
    275   while (NULL != (god = god_head))
    276   {
    277     GNUNET_CONTAINER_DLL_remove (god_head,
    278                                  god_tail,
    279                                  god);
    280     GNUNET_assert (god->suspended);
    281     god->suspended = GNUNET_SYSERR;
    282     MHD_resume_connection (god->sc.con);
    283     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
    284   }
    285 }
    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 suspend_god (struct GetOrderData *god)
    295 {
    296   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   if (NULL != god->contract_terms_json)
    303   {
    304     json_decref (god->contract_terms_json);
    305     god->contract_terms_json = NULL;
    306     god->contract_parsed = false;
    307   }
    308   if (NULL != god->contract_terms)
    309   {
    310     TALER_MERCHANT_contract_free (god->contract_terms);
    311     god->contract_terms = NULL;
    312   }
    313   GNUNET_assert (! god->suspended);
    314   god->contract_parsed = false;
    315   god->contract_match = false;
    316   god->token_match = false;
    317   god->contract_available = false;
    318   god->phase = GOP_LOOKUP_TERMS;
    319   god->suspended = GNUNET_YES;
    320   GNUNET_CONTAINER_DLL_insert (god_head,
    321                                god_tail,
    322                                god);
    323   MHD_suspend_connection (god->sc.con);
    324 }
    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 god_cleanup (void *cls)
    334 {
    335   struct GetOrderData *god = cls;
    336 
    337   if (NULL != god->contract_terms_json)
    338   {
    339     json_decref (god->contract_terms_json);
    340     god->contract_terms_json = NULL;
    341   }
    342   if (NULL != god->contract_terms)
    343   {
    344     TALER_MERCHANT_contract_free (god->contract_terms);
    345     god->contract_terms = NULL;
    346   }
    347   if (NULL != god->session_eh)
    348   {
    349     TMH_db->event_listen_cancel (god->session_eh);
    350     god->session_eh = NULL;
    351   }
    352   if (NULL != god->refund_eh)
    353   {
    354     TMH_db->event_listen_cancel (god->refund_eh);
    355     god->refund_eh = NULL;
    356   }
    357   if (NULL != god->pay_eh)
    358   {
    359     TMH_db->event_listen_cancel (god->pay_eh);
    360     god->pay_eh = NULL;
    361   }
    362   GNUNET_free (god);
    363 }
    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 phase_end (struct GetOrderData *god,
    375            MHD_RESULT mret)
    376 {
    377   god->phase = (MHD_YES == mret)
    378     ? GOP_RETURN_MHD_YES
    379     : GOP_RETURN_MHD_NO;
    380 }
    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 phase_fail (struct GetOrderData *god,
    394             unsigned int http_status,
    395             enum TALER_ErrorCode ec,
    396             const char *message)
    397 {
    398   phase_end (god,
    399              TALER_MHD_reply_with_error (god->sc.con,
    400                                          http_status,
    401                                          ec,
    402                                          message));
    403 }
    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 resume_by_event (void *cls,
    416                  const void *extra,
    417                  size_t extra_size)
    418 {
    419   struct GetOrderData *god = cls;
    420   struct GNUNET_AsyncScopeSave old;
    421 
    422   GNUNET_async_scope_enter (&god->hc->async_scope_id,
    423                             &old);
    424   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   if (! god->suspended)
    430   {
    431     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    432                 "Not suspended, ignoring event\n");
    433     GNUNET_async_scope_restore (&old);
    434     return; /* duplicate event is possible */
    435   }
    436   if (GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout) &&
    437       god->sc.awaiting_refund)
    438   {
    439     char *as;
    440     struct TALER_Amount a;
    441 
    442     if (0 == extra_size)
    443     {
    444       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    445                   "No amount given, but need refund above threshold\n");
    446       GNUNET_async_scope_restore (&old);
    447       return; /* not relevant */
    448     }
    449     as = GNUNET_strndup (extra,
    450                          extra_size);
    451     if (GNUNET_OK !=
    452         TALER_string_to_amount (as,
    453                                 &a))
    454     {
    455       GNUNET_break (0);
    456       GNUNET_async_scope_restore (&old);
    457       GNUNET_free (as);
    458       return;
    459     }
    460     GNUNET_free (as);
    461     if (GNUNET_OK !=
    462         TALER_amount_cmp_currency (&god->sc.refund_expected,
    463                                    &a))
    464     {
    465       GNUNET_break (0);
    466       GNUNET_async_scope_restore (&old);
    467       return; /* bad currency!? */
    468     }
    469     if (1 == TALER_amount_cmp (&god->sc.refund_expected,
    470                                &a))
    471     {
    472       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    473                   "Amount too small to trigger resuming\n");
    474       GNUNET_async_scope_restore (&old);
    475       return; /* refund too small */
    476     }
    477   }
    478   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   god->suspended = GNUNET_NO;
    489   GNUNET_CONTAINER_DLL_remove (god_head,
    490                                god_tail,
    491                                god);
    492   MHD_resume_connection (god->sc.con);
    493   TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
    494   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 phase_init (struct GetOrderData *god)
    506 {
    507   god->phase++;
    508   if (god->generate_html)
    509     return; /* If HTML is requested, we never actually long poll. */
    510   if (! GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout))
    511     return; /* long polling not requested */
    512 
    513   if (god->sc.awaiting_refund ||
    514       god->sc.awaiting_refund_obtained)
    515   {
    516     struct TMH_OrderPayEventP refund_eh = {
    517       .header.size = htons (sizeof (refund_eh)),
    518       .header.type = htons (god->sc.awaiting_refund_obtained
    519                                     ? TALER_DBEVENT_MERCHANT_REFUND_OBTAINED
    520                                     : TALER_DBEVENT_MERCHANT_ORDER_REFUND),
    521       .merchant_pub = god->hc->instance->merchant_pub
    522     };
    523 
    524     GNUNET_CRYPTO_hash (god->order_id,
    525                         strlen (god->order_id),
    526                         &refund_eh.h_order_id);
    527     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       = TMH_db->event_listen (
    533           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     struct TMH_OrderPayEventP pay_eh = {
    542       .header.size = htons (sizeof (pay_eh)),
    543       .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
    544       .merchant_pub = god->hc->instance->merchant_pub
    545     };
    546 
    547     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    548                 "Subscribing to payments on %s\n",
    549                 god->order_id);
    550     GNUNET_CRYPTO_hash (god->order_id,
    551                         strlen (god->order_id),
    552                         &pay_eh.h_order_id);
    553     god->pay_eh
    554       = TMH_db->event_listen (
    555           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 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   TMH_db->preflight (TMH_db->cls);
    580   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     qs = TMH_db->lookup_contract_terms3 (
    589       TMH_db->cls,
    590       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     if (0 > qs)
    601     {
    602       /* single, read-only SQL statements should never cause
    603          serialization problems */
    604       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
    605       /* Always report on hard error as well to enable diagnostics */
    606       GNUNET_break (0);
    607       phase_fail (god,
    608                   MHD_HTTP_INTERNAL_SERVER_ERROR,
    609                   TALER_EC_GENERIC_DB_FETCH_FAILED,
    610                   "lookup_contract_terms");
    611       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       = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
    617         && (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   if (NULL != god->contract_terms_json)
    623   {
    624     god->contract_available = true;
    625     if (GNUNET_YES ==
    626         GNUNET_is_zero (&god->h_contract_terms))
    627     {
    628       if (GNUNET_OK !=
    629           TALER_JSON_contract_hash (god->contract_terms_json,
    630                                     &god->h_contract_terms))
    631       {
    632         GNUNET_break (0);
    633         phase_fail (god,
    634                     MHD_HTTP_INTERNAL_SERVER_ERROR,
    635                     TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
    636                     "contract terms");
    637         return;
    638       }
    639     }
    640     else
    641     {
    642       struct TALER_PrivateContractHashP h;
    643 
    644       if (GNUNET_OK !=
    645           TALER_JSON_contract_hash (god->contract_terms_json,
    646                                     &h))
    647       {
    648         GNUNET_break (0);
    649         phase_fail (god,
    650                     MHD_HTTP_INTERNAL_SERVER_ERROR,
    651                     TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
    652                     "contract terms");
    653         return;
    654       }
    655       god->contract_match = (0 ==
    656                              GNUNET_memcmp (&h,
    657                                             &god->h_contract_terms));
    658       if (! god->contract_match)
    659       {
    660         GNUNET_break_op (0);
    661         phase_fail (god,
    662                     MHD_HTTP_FORBIDDEN,
    663                     TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
    664                     NULL);
    665         return;
    666       }
    667     }
    668   }
    669 
    670   if (god->contract_available)
    671   {
    672     god->claimed = true;
    673   }
    674   else
    675   {
    676     struct TALER_MerchantPostDataHashP unused;
    677     enum GNUNET_DB_QueryStatus qs;
    678 
    679     qs = TMH_db->lookup_order (
    680       TMH_db->cls,
    681       god->hc->instance->settings.id,
    682       god->order_id,
    683       &db_claim_token,
    684       &unused,
    685       (NULL == god->contract_terms_json)
    686       ? &god->contract_terms_json
    687       : NULL);
    688     if (0 > qs)
    689     {
    690       /* single, read-only SQL statements should never cause
    691          serialization problems */
    692       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
    693       /* Always report on hard error as well to enable diagnostics */
    694       GNUNET_break (0);
    695       phase_fail (god,
    696                   MHD_HTTP_INTERNAL_SERVER_ERROR,
    697                   TALER_EC_GENERIC_DB_FETCH_FAILED,
    698                   "lookup_order");
    699       return;
    700     }
    701     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    702     {
    703       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    704                   "Unknown order id given: `%s'\n",
    705                   god->order_id);
    706       phase_fail (god,
    707                   MHD_HTTP_NOT_FOUND,
    708                   TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
    709                   god->order_id);
    710       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       = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
    716         (0 == GNUNET_memcmp (&db_claim_token,
    717                              &god->claim_token));
    718   } /* end unclaimed order logic */
    719   god->phase++;
    720 }
    721 
    722 
    723 /**
    724  * Parse contract terms.
    725  *
    726  * @param[in,out] god request context
    727  */
    728 static void
    729 phase_parse_contract (struct GetOrderData *god)
    730 {
    731   GNUNET_break (NULL == god->contract_terms);
    732   god->contract_terms = TALER_MERCHANT_contract_parse (
    733     god->contract_terms_json,
    734     true);
    735 
    736   if (NULL == god->contract_terms)
    737   {
    738     phase_fail (god,
    739                 MHD_HTTP_INTERNAL_SERVER_ERROR,
    740                 TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
    741                 god->order_id);
    742     return;
    743   }
    744   god->contract_parsed = true;
    745   if ( (NULL != god->session_id) &&
    746        (NULL != god->contract_terms->fulfillment_url) &&
    747        (NULL == god->session_eh) )
    748   {
    749     struct TMH_SessionEventP session_eh = {
    750       .header.size = htons (sizeof (session_eh)),
    751       .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
    752       .merchant_pub = god->hc->instance->merchant_pub
    753     };
    754 
    755     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    756                 "Subscribing to session triggers for %p\n",
    757                 god);
    758     GNUNET_CRYPTO_hash (god->session_id,
    759                         strlen (god->session_id),
    760                         &session_eh.h_session_id);
    761     GNUNET_CRYPTO_hash (god->contract_terms->fulfillment_url,
    762                         strlen (god->contract_terms->fulfillment_url),
    763                         &session_eh.h_fulfillment_url);
    764     god->session_eh
    765       = TMH_db->event_listen (
    766           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   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 phase_check_client_access (struct GetOrderData *god)
    784 {
    785   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   if (god->claim_token_provided && ! god->token_match)
    793   {
    794     /* Authentication provided but wrong. */
    795     GNUNET_break_op (0);
    796     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     return;
    801   }
    802 
    803   if (god->h_contract_provided && ! god->contract_match)
    804   {
    805     /* Authentication provided but wrong. */
    806     GNUNET_break_op (0);
    807     phase_fail (god,
    808                 MHD_HTTP_FORBIDDEN,
    809                 TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_HASH,
    810                 NULL);
    811     return;
    812   }
    813 
    814   if (! (god->token_match ||
    815          god->contract_match) )
    816   {
    817 
    818     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    819                 "Neither claim token nor contract matched\n");
    820     /* Client has no rights to this order */
    821     if (NULL == god->contract_terms->public_reorder_url)
    822     {
    823       /* We cannot give the client a new order, just fail */
    824       if (! GNUNET_is_zero (&god->h_contract_terms))
    825       {
    826         GNUNET_break_op (0);
    827         phase_fail (god,
    828                     MHD_HTTP_FORBIDDEN,
    829                     TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
    830                     NULL);
    831         return;
    832       }
    833       GNUNET_break_op (0);
    834       phase_fail (god,
    835                   MHD_HTTP_FORBIDDEN,
    836                   TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
    837                   "no 'public_reorder_url'");
    838       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     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       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    852                   "Contract claimed, redirecting to fulfillment page for order %s\n",
    853                   god->order_id);
    854       reply = MHD_create_response_from_buffer (0,
    855                                                NULL,
    856                                                MHD_RESPMEM_PERSISTENT);
    857       if (NULL == reply)
    858       {
    859         GNUNET_break (0);
    860         phase_end (god,
    861                    MHD_NO);
    862         return;
    863       }
    864       GNUNET_break (MHD_YES ==
    865                     MHD_add_response_header (
    866                       reply,
    867                       MHD_HTTP_HEADER_LOCATION,
    868                       god->contract_terms->public_reorder_url));
    869       ret = MHD_queue_response (god->sc.con,
    870                                 MHD_HTTP_FOUND,
    871                                 reply);
    872       MHD_destroy_response (reply);
    873       phase_end (god,
    874                  ret);
    875       return;
    876     }
    877     /* Need to generate JSON reply */
    878     phase_end (god,
    879                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     return;
    886   }
    887   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    888               "Claim token or contract matched\n");
    889   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 get_order_summary (const struct GetOrderData *god)
    902 {
    903   const char *language_pattern;
    904   const char *ret;
    905 
    906   language_pattern = MHD_lookup_connection_value (god->sc.con,
    907                                                   MHD_HEADER_KIND,
    908                                                   MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
    909   if (NULL == language_pattern)
    910     language_pattern = "en";
    911   ret = json_string_value (TALER_JSON_extract_i18n (god->contract_terms_json,
    912                                                     language_pattern,
    913                                                     "summary"));
    914   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     GNUNET_break (0);
    920     ret = "<bug: no summary>";
    921   }
    922   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 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   remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
    945   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     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    950                 "Suspending request: long polling for payment\n");
    951     suspend_god (god);
    952     return true;
    953   }
    954 
    955   /* Check if resource_id has been paid for in the same session
    956    * with another order_id.
    957    */
    958   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    959               "Sending payment request\n");
    960   taler_pay_uri = TMH_make_taler_pay_uri (
    961     god->sc.con,
    962     god->order_id,
    963     god->session_id,
    964     god->hc->instance->settings.id,
    965     &god->claim_token);
    966   order_status_url = TMH_make_order_status_url (
    967     god->sc.con,
    968     god->order_id,
    969     god->session_id,
    970     god->hc->instance->settings.id,
    971     &god->claim_token,
    972     NULL);
    973   if ( (NULL == taler_pay_uri) ||
    974        (NULL == order_status_url) )
    975   {
    976     GNUNET_break_op (0);
    977     GNUNET_free (taler_pay_uri);
    978     GNUNET_free (order_status_url);
    979     phase_fail (god,
    980                 MHD_HTTP_BAD_REQUEST,
    981                 TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
    982                 "host");
    983     return false;
    984   }
    985   if (god->generate_html)
    986   {
    987     if (NULL != already_paid_order_id)
    988     {
    989       struct MHD_Response *reply;
    990 
    991       GNUNET_assert (NULL != god->contract_terms->fulfillment_url);
    992       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       reply = MHD_create_response_from_buffer (0,
    997                                                NULL,
    998                                                MHD_RESPMEM_PERSISTENT);
    999       if (NULL == reply)
   1000       {
   1001         GNUNET_break (0);
   1002         phase_end (god,
   1003                    MHD_NO);
   1004         return false;
   1005       }
   1006       GNUNET_break (MHD_YES ==
   1007                     MHD_add_response_header (
   1008                       reply,
   1009                       MHD_HTTP_HEADER_LOCATION,
   1010                       god->contract_terms->fulfillment_url));
   1011       {
   1012         ret = MHD_queue_response (god->sc.con,
   1013                                   MHD_HTTP_FOUND,
   1014                                   reply);
   1015         MHD_destroy_response (reply);
   1016         phase_end (god,
   1017                    ret);
   1018         return false;
   1019       }
   1020     }
   1021 
   1022     {
   1023       char *qr;
   1024 
   1025       qr = TMH_create_qrcode (taler_pay_uri);
   1026       if (NULL == qr)
   1027       {
   1028         GNUNET_break (0);
   1029         phase_end (god,
   1030                    MHD_NO);
   1031         return false;
   1032       }
   1033       {
   1034         enum GNUNET_GenericReturnValue res;
   1035         json_t *context;
   1036 
   1037         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         res = TALER_TEMPLATING_reply (
   1047           god->sc.con,
   1048           MHD_HTTP_PAYMENT_REQUIRED,
   1049           "request_payment",
   1050           god->hc->instance->settings.id,
   1051           taler_pay_uri,
   1052           context);
   1053         if (GNUNET_SYSERR == res)
   1054         {
   1055           GNUNET_break (0);
   1056           ret = MHD_NO;
   1057         }
   1058         else
   1059         {
   1060           ret = MHD_YES;
   1061         }
   1062         json_decref (context);
   1063       }
   1064       GNUNET_free (qr);
   1065     }
   1066   }
   1067   else /* end of 'generate HTML' */
   1068   {
   1069     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   GNUNET_free (taler_pay_uri);
   1082   GNUNET_free (order_status_url);
   1083   phase_end (god,
   1084              ret);
   1085   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 phase_check_paid (struct GetOrderData *god)
   1096 {
   1097   enum GNUNET_DB_QueryStatus qs;
   1098   struct TALER_PrivateContractHashP h_contract;
   1099 
   1100   god->paid = false;
   1101   qs = TMH_db->lookup_order_status (
   1102     TMH_db->cls,
   1103     god->hc->instance->settings.id,
   1104     god->order_id,
   1105     &h_contract,
   1106     &god->paid);
   1107   if (0 > qs)
   1108   {
   1109     /* Always report on hard error as well to enable diagnostics */
   1110     GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
   1111     phase_fail (god,
   1112                 MHD_HTTP_INTERNAL_SERVER_ERROR,
   1113                 TALER_EC_GENERIC_DB_FETCH_FAILED,
   1114                 "lookup_order_status");
   1115     return;
   1116   }
   1117   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 phase_redirect_to_paid_order (struct GetOrderData *god)
   1131 {
   1132   if ( (NULL != god->session_id) &&
   1133        (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     char *already_paid_order_id = NULL;
   1144     enum GNUNET_DB_QueryStatus qs;
   1145 
   1146     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     qs = TMH_db->lookup_order_by_fulfillment (
   1151       TMH_db->cls,
   1152       god->hc->instance->settings.id,
   1153       god->contract_terms->fulfillment_url,
   1154       god->session_id,
   1155       TALER_EXCHANGE_YNA_NO != god->allow_refunded_for_repurchase,
   1156       &already_paid_order_id);
   1157     if (qs < 0)
   1158     {
   1159       /* single, read-only SQL statements should never cause
   1160          serialization problems */
   1161       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
   1162       /* Always report on hard error as well to enable diagnostics */
   1163       GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
   1164       phase_fail (god,
   1165                   MHD_HTTP_INTERNAL_SERVER_ERROR,
   1166                   TALER_EC_GENERIC_DB_FETCH_FAILED,
   1167                   "order by fulfillment");
   1168       return false;
   1169     }
   1170     if ( (! god->paid) &&
   1171          ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
   1172            (0 != strcmp (god->order_id,
   1173                          already_paid_order_id)) ) )
   1174     {
   1175       bool ret;
   1176 
   1177       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       ret = send_pay_request (god,
   1182                               already_paid_order_id);
   1183       GNUNET_free (already_paid_order_id);
   1184       return ret;
   1185     }
   1186     GNUNET_free (already_paid_order_id);
   1187   }
   1188   god->phase++;
   1189   return false;
   1190 }
   1191 
   1192 
   1193 /**
   1194  * Check if the order has been paid, and if not
   1195  * request payment.
   1196  *
   1197  * @param[in,out] god request context
   1198  * @return true to exit due to suspension
   1199  */
   1200 static bool
   1201 phase_handle_unpaid (struct GetOrderData *god)
   1202 {
   1203   if (god->paid)
   1204   {
   1205     god->phase++;
   1206     return false;
   1207   }
   1208   if (god->claimed)
   1209   {
   1210     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1211                 "Order claimed but unpaid, sending pay request for order %s\n",
   1212                 god->order_id);
   1213   }
   1214   else
   1215   {
   1216     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1217                 "Order unclaimed, sending pay request for order %s\n",
   1218                 god->order_id);
   1219   }
   1220   return send_pay_request (god,
   1221                            NULL);
   1222 }
   1223 
   1224 
   1225 /**
   1226  * Function called with detailed information about a refund.
   1227  * It is responsible for packing up the data to return.
   1228  *
   1229  * @param cls closure
   1230  * @param refund_serial unique serial number of the refund
   1231  * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
   1232  * @param coin_pub public coin from which the refund comes from
   1233  * @param exchange_url URL of the exchange that issued @a coin_pub
   1234  * @param rtransaction_id identificator of the refund
   1235  * @param reason human-readable explanation of the refund
   1236  * @param refund_amount refund amount which is being taken from @a coin_pub
   1237  * @param pending true if the this refund was not yet processed by the wallet/exchange
   1238  */
   1239 static void
   1240 process_refunds_cb (void *cls,
   1241                     uint64_t refund_serial,
   1242                     struct GNUNET_TIME_Timestamp timestamp,
   1243                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
   1244                     const char *exchange_url,
   1245                     uint64_t rtransaction_id,
   1246                     const char *reason,
   1247                     const struct TALER_Amount *refund_amount,
   1248                     bool pending)
   1249 {
   1250   struct GetOrderData *god = cls;
   1251 
   1252   (void) refund_serial;
   1253   (void) timestamp;
   1254   (void) exchange_url;
   1255   (void) rtransaction_id;
   1256   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1257               "Found refund of %s for coin %s with reason `%s' in database\n",
   1258               TALER_amount2s (refund_amount),
   1259               TALER_B2S (coin_pub),
   1260               reason);
   1261   god->refund_pending |= pending;
   1262   if ( (GNUNET_OK !=
   1263         TALER_amount_cmp_currency (&god->refund_taken,
   1264                                    refund_amount)) ||
   1265        (GNUNET_OK !=
   1266         TALER_amount_cmp_currency (&god->refund_amount,
   1267                                    refund_amount)) )
   1268   {
   1269     god->bad_refund_currency_in_db = true;
   1270     return;
   1271   }
   1272   if (! pending)
   1273   {
   1274     GNUNET_assert (0 <=
   1275                    TALER_amount_add (&god->refund_taken,
   1276                                      &god->refund_taken,
   1277                                      refund_amount));
   1278   }
   1279   GNUNET_assert (0 <=
   1280                  TALER_amount_add (&god->refund_amount,
   1281                                    &god->refund_amount,
   1282                                    refund_amount));
   1283   god->refunded = true;
   1284 }
   1285 
   1286 
   1287 /**
   1288  * Check if the order has been refunded.
   1289  *
   1290  * @param[in,out] god request context
   1291  * @return true to exit due to suspension
   1292  */
   1293 static bool
   1294 phase_check_refunded (struct GetOrderData *god)
   1295 {
   1296   enum GNUNET_DB_QueryStatus qs;
   1297   struct TALER_Amount refund_amount;
   1298   const char *refund_currency;
   1299 
   1300   switch (god->contract_terms->version)
   1301   {
   1302   case TALER_MERCHANT_CONTRACT_VERSION_0:
   1303     refund_amount = god->contract_terms->details.v0.brutto;
   1304     refund_currency = god->contract_terms->details.v0.brutto.currency;
   1305     break;
   1306   case TALER_MERCHANT_CONTRACT_VERSION_1:
   1307     if (god->choice_index < 0)
   1308     {
   1309       // order was not paid, no refund to be checked
   1310       god->phase++;
   1311       return false;
   1312     }
   1313     GNUNET_assert (god->choice_index <
   1314                    god->contract_terms->details.v1.choices_len);
   1315     refund_currency = god->contract_terms->details.v1.choices[god->choice_index]
   1316                       .amount.currency;
   1317     GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (refund_currency,
   1318                                                        &refund_amount));
   1319     break;
   1320   default:
   1321     {
   1322       GNUNET_break (0);
   1323       phase_fail (god,
   1324                   MHD_HTTP_INTERNAL_SERVER_ERROR,
   1325                   TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION,
   1326                   NULL);
   1327       return false;
   1328     }
   1329   }
   1330 
   1331   if ( (god->sc.awaiting_refund) &&
   1332        (GNUNET_OK !=
   1333         TALER_amount_cmp_currency (&refund_amount,
   1334                                    &god->sc.refund_expected)) )
   1335   {
   1336     GNUNET_break (0);
   1337     phase_fail (god,
   1338                 MHD_HTTP_CONFLICT,
   1339                 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
   1340                 refund_currency);
   1341     return false;
   1342   }
   1343 
   1344   /* At this point, we know the contract was paid. Let's check for
   1345      refunds. First, clear away refunds found from previous invocations. */
   1346   GNUNET_assert (GNUNET_OK ==
   1347                  TALER_amount_set_zero (refund_currency,
   1348                                         &god->refund_amount));
   1349   GNUNET_assert (GNUNET_OK ==
   1350                  TALER_amount_set_zero (refund_currency,
   1351                                         &god->refund_taken));
   1352   qs = TMH_db->lookup_refunds_detailed (
   1353     TMH_db->cls,
   1354     god->hc->instance->settings.id,
   1355     &god->h_contract_terms,
   1356     &process_refunds_cb,
   1357     god);
   1358   if (0 > qs)
   1359   {
   1360     GNUNET_break (0);
   1361     phase_fail (god,
   1362                 MHD_HTTP_INTERNAL_SERVER_ERROR,
   1363                 TALER_EC_GENERIC_DB_FETCH_FAILED,
   1364                 "lookup_refunds_detailed");
   1365     return false;
   1366   }
   1367   if (god->bad_refund_currency_in_db)
   1368   {
   1369     GNUNET_break (0);
   1370     phase_fail (god,
   1371                 MHD_HTTP_INTERNAL_SERVER_ERROR,
   1372                 TALER_EC_GENERIC_DB_FETCH_FAILED,
   1373                 "currency mix-up between contract price and refunds in database");
   1374     return false;
   1375   }
   1376   if ( ((god->sc.awaiting_refund) &&
   1377         ( (! god->refunded) ||
   1378           (1 != TALER_amount_cmp (&god->refund_amount,
   1379                                   &god->sc.refund_expected)) )) ||
   1380        ( (god->sc.awaiting_refund_obtained) &&
   1381          (god->refund_pending) ) )
   1382   {
   1383     /* Client is waiting for a refund larger than what we have, suspend
   1384        until timeout */
   1385     struct GNUNET_TIME_Relative remaining;
   1386 
   1387     remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
   1388     if ( (! GNUNET_TIME_relative_is_zero (remaining)) &&
   1389          (! god->generate_html) )
   1390     {
   1391       /* yes, indeed suspend */
   1392       if (god->sc.awaiting_refund)
   1393         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1394                     "Awaiting refund exceeding %s\n",
   1395                     TALER_amount2s (&god->sc.refund_expected));
   1396       if (god->sc.awaiting_refund_obtained)
   1397         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1398                     "Awaiting pending refunds\n");
   1399       suspend_god (god);
   1400       return true;
   1401     }
   1402   }
   1403   god->phase++;
   1404   return false;
   1405 }
   1406 
   1407 
   1408 /**
   1409  * Create a taler://refund/ URI for the given @a con and @a order_id
   1410  * and @a instance_id.
   1411  *
   1412  * @param merchant_base_url URL to take host and path from;
   1413  *        we cannot take it from the MHD connection as a browser
   1414  *        may have changed 'http' to 'https' and we MUST be consistent
   1415  *        with what the merchant's frontend used initially
   1416  * @param order_id the order id
   1417  * @return corresponding taler://refund/ URI, or NULL on missing "host"
   1418  */
   1419 static char *
   1420 make_taler_refund_uri (const char *merchant_base_url,
   1421                        const char *order_id)
   1422 {
   1423   struct GNUNET_Buffer buf = { 0 };
   1424   char *url;
   1425   struct GNUNET_Uri uri;
   1426 
   1427   url = GNUNET_strdup (merchant_base_url);
   1428   if (-1 == GNUNET_uri_parse (&uri,
   1429                               url))
   1430   {
   1431     GNUNET_break (0);
   1432     GNUNET_free (url);
   1433     return NULL;
   1434   }
   1435   GNUNET_assert (NULL != order_id);
   1436   GNUNET_buffer_write_str (&buf,
   1437                            "taler");
   1438   if (0 == strcasecmp ("http",
   1439                        uri.scheme))
   1440     GNUNET_buffer_write_str (&buf,
   1441                              "+http");
   1442   GNUNET_buffer_write_str (&buf,
   1443                            "://refund/");
   1444   GNUNET_buffer_write_str (&buf,
   1445                            uri.host);
   1446   if (0 != uri.port)
   1447     GNUNET_buffer_write_fstr (&buf,
   1448                               ":%u",
   1449                               (unsigned int) uri.port);
   1450   if (NULL != uri.path)
   1451     GNUNET_buffer_write_path (&buf,
   1452                               uri.path);
   1453   GNUNET_buffer_write_path (&buf,
   1454                             order_id);
   1455   GNUNET_buffer_write_path (&buf,
   1456                             ""); // Trailing slash
   1457   GNUNET_free (url);
   1458   return GNUNET_buffer_reap_str (&buf);
   1459 }
   1460 
   1461 
   1462 /**
   1463  * Generate the order status response.
   1464  *
   1465  * @param[in,out] god request context
   1466  */
   1467 static void
   1468 phase_return_status (struct GetOrderData *god)
   1469 {
   1470   /* All operations done, build final response */
   1471   if (! god->generate_html)
   1472   {
   1473     phase_end (god,
   1474                TALER_MHD_REPLY_JSON_PACK (
   1475                  god->sc.con,
   1476                  MHD_HTTP_OK,
   1477                  GNUNET_JSON_pack_allow_null (
   1478                    GNUNET_JSON_pack_string ("fulfillment_url",
   1479                                             god->contract_terms->fulfillment_url
   1480                                             )),
   1481                  GNUNET_JSON_pack_bool ("refunded",
   1482                                         god->refunded),
   1483                  GNUNET_JSON_pack_bool ("refund_pending",
   1484                                         god->refund_pending),
   1485                  TALER_JSON_pack_amount ("refund_taken",
   1486                                          &god->refund_taken),
   1487                  TALER_JSON_pack_amount ("refund_amount",
   1488                                          &god->refund_amount)));
   1489     return;
   1490   }
   1491 
   1492   if (god->refund_pending)
   1493   {
   1494     char *qr;
   1495     char *uri;
   1496 
   1497     GNUNET_assert (NULL != god->contract_terms_json);
   1498     uri = make_taler_refund_uri (god->contract_terms->merchant_base_url,
   1499                                  god->order_id);
   1500     if (NULL == uri)
   1501     {
   1502       GNUNET_break (0);
   1503       phase_fail (god,
   1504                   MHD_HTTP_INTERNAL_SERVER_ERROR,
   1505                   TALER_EC_GENERIC_ALLOCATION_FAILURE,
   1506                   "refund URI");
   1507       return;
   1508     }
   1509     qr = TMH_create_qrcode (uri);
   1510     if (NULL == qr)
   1511     {
   1512       GNUNET_break (0);
   1513       GNUNET_free (uri);
   1514       phase_fail (god,
   1515                   MHD_HTTP_INTERNAL_SERVER_ERROR,
   1516                   TALER_EC_GENERIC_ALLOCATION_FAILURE,
   1517                   "qr code");
   1518       return;
   1519     }
   1520 
   1521     {
   1522       enum GNUNET_GenericReturnValue res;
   1523       json_t *context;
   1524 
   1525       context = GNUNET_JSON_PACK (
   1526         GNUNET_JSON_pack_string ("order_summary",
   1527                                  get_order_summary (god)),
   1528         TALER_JSON_pack_amount ("refund_amount",
   1529                                 &god->refund_amount),
   1530         TALER_JSON_pack_amount ("refund_taken",
   1531                                 &god->refund_taken),
   1532         GNUNET_JSON_pack_string ("taler_refund_uri",
   1533                                  uri),
   1534         GNUNET_JSON_pack_string ("taler_refund_qrcode_svg",
   1535                                  qr));
   1536       res = TALER_TEMPLATING_reply (
   1537         god->sc.con,
   1538         MHD_HTTP_OK,
   1539         "offer_refund",
   1540         god->hc->instance->settings.id,
   1541         uri,
   1542         context);
   1543       GNUNET_break (GNUNET_OK == res);
   1544       json_decref (context);
   1545       phase_end (god,
   1546                  (GNUNET_SYSERR == res)
   1547                  ? MHD_NO
   1548                  : MHD_YES);
   1549     }
   1550     GNUNET_free (uri);
   1551     GNUNET_free (qr);
   1552     return;
   1553   }
   1554 
   1555   {
   1556     enum GNUNET_GenericReturnValue res;
   1557     json_t *context;
   1558 
   1559     context = GNUNET_JSON_PACK (
   1560       GNUNET_JSON_pack_object_incref ("contract_terms",
   1561                                       god->contract_terms_json),
   1562       GNUNET_JSON_pack_string ("order_summary",
   1563                                get_order_summary (god)),
   1564       TALER_JSON_pack_amount ("refund_amount",
   1565                               &god->refund_amount),
   1566       TALER_JSON_pack_amount ("refund_taken",
   1567                               &god->refund_taken));
   1568     res = TALER_TEMPLATING_reply (
   1569       god->sc.con,
   1570       MHD_HTTP_OK,
   1571       "show_order_details",
   1572       god->hc->instance->settings.id,
   1573       NULL,
   1574       context);
   1575     GNUNET_break (GNUNET_OK == res);
   1576     json_decref (context);
   1577     phase_end (god,
   1578                (GNUNET_SYSERR == res)
   1579                ? MHD_NO
   1580                : MHD_YES);
   1581   }
   1582 }
   1583 
   1584 
   1585 MHD_RESULT
   1586 TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
   1587                    struct MHD_Connection *connection,
   1588                    struct TMH_HandlerContext *hc)
   1589 {
   1590   struct GetOrderData *god = hc->ctx;
   1591 
   1592   (void) rh;
   1593   if (NULL == god)
   1594   {
   1595     god = GNUNET_new (struct GetOrderData);
   1596     hc->ctx = god;
   1597     hc->cc = &god_cleanup;
   1598     god->sc.con = connection;
   1599     god->hc = hc;
   1600     god->order_id = hc->infix;
   1601     god->generate_html
   1602       = TMH_MHD_test_html_desired (connection);
   1603 
   1604     /* first-time initialization / sanity checks */
   1605     TALER_MHD_parse_request_arg_auto (connection,
   1606                                       "h_contract",
   1607                                       &god->h_contract_terms,
   1608                                       god->h_contract_provided);
   1609     TALER_MHD_parse_request_arg_auto (connection,
   1610                                       "token",
   1611                                       &god->claim_token,
   1612                                       god->claim_token_provided);
   1613     if (! (TALER_MHD_arg_to_yna (connection,
   1614                                  "allow_refunded_for_repurchase",
   1615                                  TALER_EXCHANGE_YNA_NO,
   1616                                  &god->allow_refunded_for_repurchase)) )
   1617       return TALER_MHD_reply_with_error (connection,
   1618                                          MHD_HTTP_BAD_REQUEST,
   1619                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1620                                          "allow_refunded_for_repurchase");
   1621     god->session_id = MHD_lookup_connection_value (connection,
   1622                                                    MHD_GET_ARGUMENT_KIND,
   1623                                                    "session_id");
   1624 
   1625     /* process await_refund_obtained argument */
   1626     {
   1627       const char *await_refund_obtained_s;
   1628 
   1629       await_refund_obtained_s =
   1630         MHD_lookup_connection_value (connection,
   1631                                      MHD_GET_ARGUMENT_KIND,
   1632                                      "await_refund_obtained");
   1633       god->sc.awaiting_refund_obtained =
   1634         (NULL != await_refund_obtained_s)
   1635         ? 0 == strcasecmp (await_refund_obtained_s,
   1636                            "yes")
   1637         : false;
   1638       if (god->sc.awaiting_refund_obtained)
   1639         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1640                     "Awaiting refund obtained\n");
   1641     }
   1642 
   1643     TALER_MHD_parse_request_amount (connection,
   1644                                     "refund",
   1645                                     &god->sc.refund_expected);
   1646     if (TALER_amount_is_valid (&god->sc.refund_expected))
   1647     {
   1648       god->sc.awaiting_refund = true;
   1649       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1650                   "Awaiting minimum refund of %s\n",
   1651                   TALER_amount2s (&god->sc.refund_expected));
   1652     }
   1653     TALER_MHD_parse_request_timeout (connection,
   1654                                      &god->sc.long_poll_timeout);
   1655   }
   1656 
   1657   if (GNUNET_SYSERR == god->suspended)
   1658     return MHD_NO; /* we are in shutdown */
   1659   if (GNUNET_YES == god->suspended)
   1660   {
   1661     god->suspended = GNUNET_NO;
   1662     GNUNET_CONTAINER_DLL_remove (god_head,
   1663                                  god_tail,
   1664                                  god);
   1665   }
   1666 
   1667   while (1)
   1668   {
   1669     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1670                 "Handling request in phase %d\n",
   1671                 (int) god->phase);
   1672     switch (god->phase)
   1673     {
   1674     case GOP_INIT:
   1675       phase_init (god);
   1676       break;
   1677     case GOP_LOOKUP_TERMS:
   1678       phase_lookup_terms (god);
   1679       break;
   1680     case GOP_PARSE_CONTRACT:
   1681       phase_parse_contract (god);
   1682       break;
   1683     case GOP_CHECK_CLIENT_ACCESS:
   1684       phase_check_client_access (god);
   1685       break;
   1686     case GOP_CHECK_PAID:
   1687       phase_check_paid (god);
   1688       break;
   1689     case GOP_REDIRECT_TO_PAID_ORDER:
   1690       if (phase_redirect_to_paid_order (god))
   1691         return MHD_YES;
   1692       break;
   1693     case GOP_HANDLE_UNPAID:
   1694       if (phase_handle_unpaid (god))
   1695         return MHD_YES;
   1696       break;
   1697     case GOP_CHECK_REFUNDED:
   1698       if (phase_check_refunded (god))
   1699         return MHD_YES;
   1700       break;
   1701     case GOP_RETURN_STATUS:
   1702       phase_return_status (god);
   1703       break;
   1704     case GOP_RETURN_MHD_YES:
   1705       return MHD_YES;
   1706     case GOP_RETURN_MHD_NO:
   1707       return MHD_NO;
   1708     }
   1709   }
   1710 }
   1711 
   1712 
   1713 /* end of taler-merchant-httpd_get-orders-ID.c */