gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-merchant] 90/277: starting with get-orders-ID logic


From: gnunet
Subject: [taler-merchant] 90/277: starting with get-orders-ID logic
Date: Sun, 05 Jul 2020 20:50:03 +0200

This is an automated email from the git hooks/post-receive script.

grothoff pushed a commit to branch master
in repository merchant.

commit e138cd0e5adff973cf1f065e3fd5588f4bd33ddc
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Mon May 4 00:25:27 2020 +0200

    starting with get-orders-ID logic
---
 src/backend/taler-merchant-httpd_get-orders-ID-2.c | 582 -----------------
 src/backend/taler-merchant-httpd_get-orders-ID-2.h |  47 --
 src/backend/taler-merchant-httpd_get-orders-ID.c   | 723 +++++++++++++++------
 src/backend/taler-merchant-httpd_get-orders-ID.h   |  28 +-
 4 files changed, 542 insertions(+), 838 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_get-orders-ID-2.c 
b/src/backend/taler-merchant-httpd_get-orders-ID-2.c
deleted file mode 100644
index 6ca4fcc..0000000
--- a/src/backend/taler-merchant-httpd_get-orders-ID-2.c
+++ /dev/null
@@ -1,582 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2017, 2019 Taler Systems SA
-
-  TALER is free software; you can redistribute it and/or modify it under the
-  terms of the GNU General Public License as published by the Free Software
-  Foundation; either version 3, or (at your option) any later version.
-
-  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License along with
-  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file backend/taler-merchant-httpd_poll-payment.c
- * @brief implementation of /public/poll-payment handler
- * @author Florian Dold
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <string.h>
-#include <microhttpd.h>
-#include <jansson.h>
-#include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_poll-payment.h"
-
-/**
- * Maximum number of retries for database operations.
- */
-#define MAX_RETRIES 5
-
-
-/**
- * Data structure we keep for a check payment request.
- */
-struct PollPaymentRequestContext
-{
-  /**
-   * Must be first for #handle_mhd_completion_callback.
-   */
-  struct TM_HandlerContext hc;
-
-  /**
-   * Entry in the #resume_timeout_heap for this check payment, if we are
-   * suspended.
-   */
-  struct TMH_SuspendedConnection sc;
-
-  /**
-   * Which merchant instance is this for?
-   */
-  struct MerchantInstance *mi;
-
-  /**
-   * URL where the final contract can be found for this payment.
-   */
-  char *final_contract_url;
-
-  /**
-   * order ID for the payment
-   */
-  const char *order_id;
-
-  /**
-   * Where to get the contract
-   */
-  const char *contract_url;
-
-  /**
-   * fulfillment URL of the contract (valid as long as
-   * @e contract_terms is valid).
-   */
-  const char *fulfillment_url;
-
-  /**
-   * session of the client
-   */
-  const char *session_id;
-
-  /**
-   * Contract terms of the payment we are checking. NULL when they
-   * are not (yet) known.
-   */
-  json_t *contract_terms;
-
-  /**
-   * Hash of @e contract_terms, set only once @e contract_terms
-   * is available.
-   */
-  struct GNUNET_HashCode h_contract_terms;
-
-  /**
-   * Total refunds granted for this payment. Only initialized
-   * if @e refunded is set to #GNUNET_YES.
-   */
-  struct TALER_Amount refund_amount;
-
-  /**
-   * Minimum refund amount the client would like to poll for.
-   * Only initialized if
-   * @e awaiting_refund is set to #GNUNET_YES.
-   */
-  struct TALER_Amount min_refund;
-
-  /**
-   * Set to #GNUNET_YES if this payment has been refunded and
-   * @e refund_amount is initialized.
-   */
-  int refunded;
-
-  /**
-   * Set to #GNUNET_YES if this client is waiting for a refund.
-   */
-  int awaiting_refund;
-
-  /**
-   * Initially #GNUNET_SYSERR. If we queued a response, set to the
-   * result code (i.e. #MHD_YES or #MHD_NO). FIXME: fix type!
-   */
-  int ret;
-
-};
-
-
-/**
- * Clean up the session state for a check payment request.
- *
- * @param hc must be a `struct PollPaymentRequestContext *`
- */
-static void
-pprc_cleanup (struct TM_HandlerContext *hc)
-{
-  struct PollPaymentRequestContext *pprc
-    = (struct PollPaymentRequestContext *) hc;
-
-  if (NULL != pprc->contract_terms)
-    json_decref (pprc->contract_terms);
-  GNUNET_free_non_null (pprc->final_contract_url);
-  GNUNET_free (pprc);
-}
-
-
-/**
- * Function called with information about a refund.
- * It is responsible for summing up the refund amount.
- *
- * @param cls closure
- * @param coin_pub public coin from which the refund comes from
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param rtransaction_id identificator of the refund
- * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from @a coin_pub
- * @param refund_fee cost of this refund operation
- */
-static void
-process_refunds_cb (void *cls,
-                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                    const char *exchange_url,
-                    uint64_t rtransaction_id,
-                    const char *reason,
-                    const struct TALER_Amount *refund_amount,
-                    const struct TALER_Amount *refund_fee)
-{
-  struct PollPaymentRequestContext *pprc = cls;
-
-  if (pprc->refunded)
-  {
-    GNUNET_assert (0 <=
-                   TALER_amount_add (&pprc->refund_amount,
-                                     &pprc->refund_amount,
-                                     refund_amount));
-    return;
-  }
-  pprc->refund_amount = *refund_amount;
-  pprc->refunded = GNUNET_YES;
-}
-
-
-/**
- * Suspend this @a pprc until the trigger is satisfied.
- *
- * @param ppr
- */
-static void
-suspend_pprc (struct PollPaymentRequestContext *pprc)
-{
-  TMH_compute_pay_key (pprc->order_id,
-                       &pprc->mi->pubkey,
-                       &pprc->sc.key);
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Suspending /poll-payment on key %s\n",
-              GNUNET_h2s (&pprc->sc.key));
-  TMH_long_poll_suspend (&pprc->sc,
-                         (pprc->awaiting_refund)
-                         ? &pprc->min_refund
-                         : NULL);
-}
-
-
-/**
- * The client did not yet pay, send it the payment request.
- *
- * @param pprc check pay request context
- * @return #MHD_YES on success
- */
-static MHD_RESULT
-send_pay_request (struct PollPaymentRequestContext *pprc)
-{
-  MHD_RESULT ret;
-  char *already_paid_order_id = NULL;
-  char *taler_pay_uri;
-  struct GNUNET_TIME_Relative remaining;
-
-  remaining = GNUNET_TIME_absolute_get_remaining (pprc->sc.long_poll_timeout);
-  if (0 != remaining.rel_value_us)
-  {
-    /* long polling: do not queue a response, suspend connection instead */
-    suspend_pprc (pprc);
-    return MHD_YES;
-  }
-
-  /* Check if resource_id has been paid for in the same session
-   * with another order_id.
-   */
-  if ( (NULL != pprc->session_id) &&
-       (NULL != pprc->fulfillment_url) )
-  {
-    enum GNUNET_DB_QueryStatus qs;
-
-    qs = db->find_session_info (db->cls,
-                                &already_paid_order_id,
-                                pprc->session_id,
-                                pprc->fulfillment_url,
-                                &pprc->mi->pubkey);
-    if (qs < 0)
-    {
-      /* single, read-only SQL statements should never cause
-         serialization problems */
-      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (pprc->sc.con,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR,
-                                         "db error fetching pay session info");
-    }
-  }
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Sending payment request in /poll-payment\n");
-  taler_pay_uri = TMH_make_taler_pay_uri (pprc->sc.con,
-                                          pprc->order_id,
-                                          pprc->session_id,
-                                          pprc->mi->id);
-  ret = TALER_MHD_reply_json_pack (pprc->sc.con,
-                                   MHD_HTTP_OK,
-                                   "{s:s, s:s, s:b, s:s?}",
-                                   "taler_pay_uri", taler_pay_uri,
-                                   "contract_url", pprc->final_contract_url,
-                                   "paid", 0,
-                                   "already_paid_order_id",
-                                   already_paid_order_id);
-  GNUNET_free (taler_pay_uri);
-  GNUNET_free_non_null (already_paid_order_id);
-  return ret;
-}
-
-
-/**
- * Manages a /public/poll-payment call, checking the status
- * of a payment and, if necessary, constructing the URL
- * for a payment redirect URL.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_poll_payment (struct TMH_RequestHandler *rh,
-                         struct MHD_Connection *connection,
-                         void **connection_cls,
-                         const char *upload_data,
-                         size_t *upload_data_size,
-                         struct MerchantInstance *mi)
-{
-  struct PollPaymentRequestContext *pprc = *connection_cls;
-  enum GNUNET_DB_QueryStatus qs;
-  MHD_RESULT ret;
-
-  if (NULL == pprc)
-  {
-    /* First time here, parse request and check order is known */
-    const char *long_poll_timeout_s;
-    const char *cts;
-    const char *min_refund;
-
-    pprc = GNUNET_new (struct PollPaymentRequestContext);
-    pprc->hc.cc = &pprc_cleanup;
-    pprc->ret = GNUNET_SYSERR;
-    pprc->sc.con = connection;
-    pprc->mi = mi;
-    *connection_cls = pprc;
-
-    pprc->order_id = MHD_lookup_connection_value (connection,
-                                                  MHD_GET_ARGUMENT_KIND,
-                                                  "order_id");
-    if (NULL == pprc->order_id)
-    {
-      /* order_id is required but missing */
-      GNUNET_break_op (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         TALER_EC_PARAMETER_MISSING,
-                                         "order_id required");
-    }
-    cts = MHD_lookup_connection_value (connection,
-                                       MHD_GET_ARGUMENT_KIND,
-                                       "h_contract");
-    if (NULL == cts)
-    {
-      /* h_contract required but missing */
-      GNUNET_break_op (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         TALER_EC_PARAMETER_MISSING,
-                                         "h_contract required");
-    }
-    if (GNUNET_OK !=
-        GNUNET_CRYPTO_hash_from_string (cts,
-                                        &pprc->h_contract_terms))
-    {
-      /* cts has wrong encoding */
-      GNUNET_break_op (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         TALER_EC_PARAMETER_MALFORMED,
-                                         "h_contract malformed");
-    }
-    long_poll_timeout_s = MHD_lookup_connection_value (connection,
-                                                       MHD_GET_ARGUMENT_KIND,
-                                                       "timeout");
-    if (NULL != long_poll_timeout_s)
-    {
-      unsigned int timeout;
-
-      if (1 != sscanf (long_poll_timeout_s,
-                       "%u",
-                       &timeout))
-      {
-        GNUNET_break_op (0);
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_BAD_REQUEST,
-                                           TALER_EC_PARAMETER_MALFORMED,
-                                           "timeout must be non-negative 
number");
-      }
-      pprc->sc.long_poll_timeout
-        = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
-                                              GNUNET_TIME_UNIT_SECONDS,
-                                              timeout));
-    }
-    else
-    {
-      pprc->sc.long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
-    }
-
-    min_refund = MHD_lookup_connection_value (connection,
-                                              MHD_GET_ARGUMENT_KIND,
-                                              "refund");
-    if (NULL != min_refund)
-    {
-      if ( (GNUNET_OK !=
-            TALER_string_to_amount (min_refund,
-                                    &pprc->min_refund)) ||
-           (0 != strcasecmp (pprc->min_refund.currency,
-                             TMH_currency) ) )
-      {
-        GNUNET_break_op (0);
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_BAD_REQUEST,
-                                           TALER_EC_PARAMETER_MALFORMED,
-                                           "invalid amount given for refund 
argument");
-      }
-      pprc->awaiting_refund = GNUNET_YES;
-    }
-
-    pprc->contract_url = MHD_lookup_connection_value (connection,
-                                                      MHD_GET_ARGUMENT_KIND,
-                                                      "contract_url");
-    if (NULL == pprc->contract_url)
-    {
-      pprc->final_contract_url = TALER_url_absolute_mhd (connection,
-                                                         "/public/proposal",
-                                                         "instance", mi->id,
-                                                         "order_id",
-                                                         pprc->order_id,
-                                                         NULL);
-      GNUNET_assert (NULL != pprc->final_contract_url);
-    }
-    else
-    {
-      pprc->final_contract_url = GNUNET_strdup (pprc->contract_url);
-    }
-    pprc->session_id = MHD_lookup_connection_value (connection,
-                                                    MHD_GET_ARGUMENT_KIND,
-                                                    "session_id");
-
-    /* obtain contract terms, indirectly checking that the client's contract
-       terms hash is actually valid and known. */
-    db->preflight (db->cls);
-    qs = db->find_contract_terms_from_hash (db->cls,
-                                            &pprc->contract_terms,
-                                            &pprc->h_contract_terms,
-                                            &mi->pubkey);
-    if (0 > qs)
-    {
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
-                                         "Merchant database error");
-    }
-    if (0 == qs)
-    {
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_NOT_FOUND,
-                                         
TALER_EC_POLL_PAYMENT_CONTRACT_NOT_FOUND,
-                                         "Given order_id doesn't map to any 
proposal");
-    }
-    GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
-
-    {
-      struct GNUNET_JSON_Specification spec[] = {
-        GNUNET_JSON_spec_string ("fulfillment_url",
-                                 &pprc->fulfillment_url),
-        GNUNET_JSON_spec_end ()
-      };
-
-      if (GNUNET_OK !=
-          GNUNET_JSON_parse (pprc->contract_terms,
-                             spec,
-                             NULL, NULL))
-      {
-        GNUNET_break (0);
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                           
TALER_EC_CHECK_PAYMENT_DB_FETCH_CONTRACT_TERMS_ERROR,
-                                           "Merchant database error (contract 
terms corrupted)");
-      }
-    }
-  } /* end of first-time initialization / sanity checks */
-
-  db->preflight (db->cls);
-
-  /* Check if the order has been paid for. */
-  if (NULL != pprc->session_id)
-  {
-    /* Check if paid within a session. */
-    char *already_paid_order_id = NULL;
-
-    qs = db->find_session_info (db->cls,
-                                &already_paid_order_id,
-                                pprc->session_id,
-                                pprc->fulfillment_url,
-                                &mi->pubkey);
-    if (qs < 0)
-    {
-      /* single, read-only SQL statements should never cause
-         serialization problems */
-      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR,
-                                         "db error fetching pay session info");
-    }
-    else if (0 == qs)
-    {
-      ret = send_pay_request (pprc);
-      GNUNET_free_non_null (already_paid_order_id);
-      return ret;
-    }
-    GNUNET_break (1 == qs);
-    GNUNET_break (0 == strcmp (pprc->order_id,
-                               already_paid_order_id));
-    GNUNET_free_non_null (already_paid_order_id);
-  }
-  else
-  {
-    /* Check if paid regardless of session. */
-    json_t *xcontract_terms = NULL;
-
-    qs = db->find_paid_contract_terms_from_hash (db->cls,
-                                                 &xcontract_terms,
-                                                 &pprc->h_contract_terms,
-                                                 &mi->pubkey);
-    if (0 > qs)
-    {
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
-                                         "Merchant database error");
-    }
-    if (0 == qs)
-    {
-      return send_pay_request (pprc);
-    }
-    GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
-    GNUNET_assert (NULL != xcontract_terms);
-    json_decref (xcontract_terms);
-  }
-
-  /* Accumulate refunds, if any. */
-  for (unsigned int i = 0; i<MAX_RETRIES; i++)
-  {
-    pprc->refunded = GNUNET_NO;
-    qs = db->get_refunds_from_contract_terms_hash (db->cls,
-                                                   &mi->pubkey,
-                                                   &pprc->h_contract_terms,
-                                                   &process_refunds_cb,
-                                                   pprc);
-    if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
-      break;
-  }
-  if (0 > qs)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Database hard error on refunds_from_contract_terms_hash 
lookup: %s\n",
-                GNUNET_h2s (&pprc->h_contract_terms));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
-                                       "Merchant database error");
-  }
-  if ( (pprc->awaiting_refund) &&
-       ( (! pprc->refunded) ||
-         (1 != TALER_amount_cmp (&pprc->refund_amount,
-                                 &pprc->min_refund)) ) )
-  {
-    /* Client is waiting for a refund larger than what we have, suspend
-       until timeout */
-    struct GNUNET_TIME_Relative remaining;
-
-    remaining = GNUNET_TIME_absolute_get_remaining 
(pprc->sc.long_poll_timeout);
-    if (0 != remaining.rel_value_us)
-    {
-      /* yes, indeed suspend */
-      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                  "Awaiting refund exceeding %s\n",
-                  TALER_amount2s (&pprc->min_refund));
-      suspend_pprc (pprc);
-      return MHD_YES;
-    }
-  }
-
-  if (pprc->refunded)
-    return TALER_MHD_reply_json_pack (connection,
-                                      MHD_HTTP_OK,
-                                      "{s:b, s:b, s:o}",
-                                      "paid", 1,
-                                      "refunded", pprc->refunded,
-                                      "refund_amount",
-                                      TALER_JSON_from_amount (
-                                        &pprc->refund_amount));
-  return TALER_MHD_reply_json_pack (connection,
-                                    MHD_HTTP_OK,
-                                    "{s:b, s:b }",
-                                    "paid", 1,
-                                    "refunded", 0);
-}
diff --git a/src/backend/taler-merchant-httpd_get-orders-ID-2.h 
b/src/backend/taler-merchant-httpd_get-orders-ID-2.h
deleted file mode 100644
index ac13c4a..0000000
--- a/src/backend/taler-merchant-httpd_get-orders-ID-2.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2017 Taler Systems SA
-
-  TALER is free software; you can redistribute it and/or modify it under the
-  terms of the GNU General Public License as published by the Free Software
-  Foundation; either version 3, or (at your option) any later version.
-
-  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License along with
-  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file backend/taler-merchant-httpd_poll-payment.h
- * @brief headers for /public/poll-payment handler
- * @author Christian Grothoff
- * @author Florian Dold
- */
-#ifndef TALER_MERCHANT_HTTPD_POLL_PAYMENT_H
-#define TALER_MERCHANT_HTTPD_POLL_PAYMENT_H
-#include <microhttpd.h>
-#include "taler-merchant-httpd.h"
-
-/**
- * Manages a /public/poll-payment call, checking the status
- * of a payment.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_poll_payment (struct TMH_RequestHandler *rh,
-                         struct MHD_Connection *connection,
-                         void **connection_cls,
-                         const char *upload_data,
-                         size_t *upload_data_size,
-                         struct MerchantInstance *mi);
-
-#endif
diff --git a/src/backend/taler-merchant-httpd_get-orders-ID.c 
b/src/backend/taler-merchant-httpd_get-orders-ID.c
index e86e4e4..ba0d354 100644
--- a/src/backend/taler-merchant-httpd_get-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_get-orders-ID.c
@@ -14,9 +14,10 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_refund_lookup.c
- * @brief refund handling logic
+ * @file backend/taler-merchant-httpd_get-orders-ID.c
+ * @brief implementation of GET /orders/$ID
  * @author Marcello Stanisci
+ * @author Christian Grothoff
  */
 #include "platform.h"
 #include <jansson.h>
@@ -59,9 +60,9 @@ struct CoinRefund
   struct TALER_EXCHANGE_RefundHandle *rh;
 
   /**
-   * PRD this operation is part of.
+   * Request this operation is part of.
    */
-  struct ProcessRefundData *prd;
+  struct GetOrderData *god;
 
   /**
    * URL of the exchange for this @e coin_pub.
@@ -119,15 +120,10 @@ struct CoinRefund
 
 
 /**
- * Closure for #process_refunds_cb.
+ * Context for the operation.
  */
-struct ProcessRefundData
+struct GetOrderData
 {
-  /**
-   * Must be first for #handle_mhd_completion_callback() cleanup
-   * logic to work.
-   */
-  struct TM_HandlerContext hc;
 
   /**
    * Hashed version of contract terms.
@@ -137,106 +133,186 @@ struct ProcessRefundData
   /**
    * DLL of (suspended) requests.
    */
-  struct ProcessRefundData *next;
+  struct GetOrderData *next;
 
   /**
    * DLL of (suspended) requests.
    */
-  struct ProcessRefundData *prev;
+  struct GetOrderData *prev;
 
   /**
-   * Head of DLL of coin refunds for this request.
+   * Context of the request.
    */
-  struct CoinRefund *cr_head;
+  struct TMH_HandlerContext *hc;
 
   /**
-   * Tail of DLL of coin refunds for this request.
+   * Did we suspend @a connection?
    */
-  struct CoinRefund *cr_tail;
+  int suspended;
 
   /**
-   * Both public and private key are needed by the callback
+   * Return code: #TALER_EC_NONE if successful.
    */
-  const struct MerchantInstance *merchant;
+  enum TALER_ErrorCode ec;
 
   /**
-   * Connection we are handling.
+   * Entry in the #resume_timeout_heap for this check payment, if we are
+   * suspended.
    */
-  struct MHD_Connection *connection;
+  struct TMH_SuspendedConnection sc;
 
   /**
-   * Did we suspend @a connection?
+   * Which merchant instance is this for?
    */
-  int suspended;
+  struct MerchantInstance *mi;
 
   /**
-   * Return code: #TALER_EC_NONE if successful.
+   * URL where the final contract can be found for this payment.
    */
-  enum TALER_ErrorCode ec;
+  char *final_contract_url;
+
+  /**
+   * order ID for the payment
+   */
+  const char *order_id;
+
+  /**
+   * Where to get the contract
+   */
+  const char *contract_url;
+
+  /**
+   * fulfillment URL of the contract (valid as long as
+   * @e contract_terms is valid).
+   */
+  const char *fulfillment_url;
+
+  /**
+   * session of the client
+   */
+  const char *session_id;
+
+  /**
+   * Contract terms of the payment we are checking. NULL when they
+   * are not (yet) known.
+   */
+  json_t *contract_terms;
+
+  /**
+   * Hash of @e contract_terms, set only once @e contract_terms
+   * is available.
+   */
+  struct GNUNET_HashCode h_contract_terms;
+
+  /**
+   * Total refunds granted for this payment. Only initialized
+   * if @e refunded is set to true.
+   */
+  struct TALER_Amount refund_amount;
+
+  /**
+   * Set to true if this payment has been refunded and
+   * @e refund_amount is initialized.
+   */
+  bool refunded;
+
+  /**
+   * Initially #GNUNET_SYSERR. If we queued a response, set to the
+   * result code (i.e. #MHD_YES or #MHD_NO). FIXME: fix type!
+   */
+  int ret;
+
+
 };
 
 
 /**
  * HEad of DLL of (suspended) requests.
  */
-static struct ProcessRefundData *prd_head;
+static struct GetOrderData *prd_head;
 
 /**
  * Tail of DLL of (suspended) requests.
  */
-static struct ProcessRefundData *prd_tail;
+static struct GetOrderData *prd_tail;
+
+
+/**
+ * Force resuming all suspended order lookups, needed during shutdown.
+ */
+void
+TMH_force_wallet_get_order_resume (void)
+{
+  struct GetOrderData *god;
+
+  while (NULL != (god = god_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (god_head,
+                                 god_tail,
+                                 god);
+    GNUNET_assert (god->suspended);
+    god->suspended = GNUNET_NO;
+    MHD_resume_connection (god->connection);
+  }
+}
 
 
 /**
- * Clean up memory in @a cls, the connection was closed.
+ * The client did not yet pay, send it the payment request.
  *
- * @param cls a `struct ProcessRefundData` to clean up.
+ * @param god check pay request context
+ * @return #MHD_YES on success
  */
-static void
-cleanup_prd (struct TM_HandlerContext *cls)
+static MHD_RESULT
+send_pay_request (struct GetOrderData *god,
+                  const char *already_paid_order_id)
 {
-  struct ProcessRefundData *prd = (struct ProcessRefundData *) cls;
-  struct CoinRefund *cr;
+  MHD_RESULT ret;
+  char *taler_pay_uri;
+  struct GNUNET_TIME_Relative remaining;
 
-  while (NULL != (cr = prd->cr_head))
+  remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
+  if (0 != remaining.rel_value_us)
   {
-    GNUNET_CONTAINER_DLL_remove (prd->cr_head,
-                                 prd->cr_tail,
-                                 cr);
-    if (NULL != cr->fo)
-    {
-      TMH_EXCHANGES_find_exchange_cancel (cr->fo);
-      cr->fo = NULL;
-    }
-    if (NULL != cr->rh)
-    {
-      TALER_EXCHANGE_refund_cancel (cr->rh);
-      cr->rh = NULL;
-    }
-    if (NULL != cr->exchange_reply)
-    {
-      json_decref (cr->exchange_reply);
-      cr->exchange_reply = NULL;
-    }
-    GNUNET_free (cr->exchange_url);
-    GNUNET_free (cr);
+    /* long polling: do not queue a response, suspend connection instead */
+    suspend_god (god);
+    return MHD_YES;
   }
-  GNUNET_free (prd);
+
+  /* Check if resource_id has been paid for in the same session
+   * with another order_id.
+   */
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Sending payment request in /poll-payment\n");
+  taler_pay_uri = TMH_make_taler_pay_uri (god->sc.con,
+                                          god->order_id,
+                                          god->session_id,
+                                          god->mi->id);
+  ret = TALER_MHD_reply_json_pack (god->sc.con,
+                                   MHD_HTTP_OK,
+                                   "{s:s, s:s, s:b, s:s?}",
+                                   "taler_pay_uri", taler_pay_uri,
+                                   "contract_url", god->final_contract_url,
+                                   "paid", 0,
+                                   "already_paid_order_id",
+                                   already_paid_order_id);
+  GNUNET_free (taler_pay_uri);
+  return ret;
 }
 
 
 /**
- * Check if @a prd has sub-activities still pending.
+ * Check if @a god has sub-activities still pending.
  *
- * @param prd request to check
+ * @param god request to check
  * @return #GNUNET_YES if activities are still pending
  */
 static int
-prd_pending (struct ProcessRefundData *prd)
+god_pending (struct GetOrderData *god)
 {
   int pending = GNUNET_NO;
 
-  for (struct CoinRefund *cr = prd->cr_head;
+  for (struct CoinRefund *cr = god->cr_head;
        NULL != cr;
        cr = cr->next)
   {
@@ -252,21 +328,21 @@ prd_pending (struct ProcessRefundData *prd)
 
 
 /**
- * Check if @a prd is ready to be resumed, and if so, do it.
+ * Check if @a god is ready to be resumed, and if so, do it.
  *
- * @param prd refund request to be possibly ready
+ * @param god refund request to be possibly ready
  */
 static void
-check_resume_prd (struct ProcessRefundData *prd)
+check_resume_god (struct GetOrderData *god)
 {
-  if (prd_pending (prd))
+  if (god_pending (god))
     return;
-  GNUNET_CONTAINER_DLL_remove (prd_head,
-                               prd_tail,
-                               prd);
-  GNUNET_assert (prd->suspended);
-  prd->suspended = GNUNET_NO;
-  MHD_resume_connection (prd->connection);
+  GNUNET_CONTAINER_DLL_remove (god_head,
+                               god_tail,
+                               god);
+  GNUNET_assert (god->suspended);
+  god->suspended = GNUNET_NO;
+  MHD_resume_connection (god->connection);
   TMH_trigger_daemon ();
 }
 
@@ -306,8 +382,8 @@ refund_cb (void *cls,
     cr->exchange_pub = *exchange_pub;
     cr->exchange_sig = *exchange_sig;
     qs = db->put_refund_proof (db->cls,
-                               &cr->prd->merchant->pubkey,
-                               &cr->prd->h_contract_terms,
+                               &cr->god->merchant->pubkey,
+                               &cr->god->h_contract_terms,
                                &cr->coin_pub,
                                cr->rtransaction_id,
                                exchange_pub,
@@ -321,7 +397,7 @@ refund_cb (void *cls,
                   qs);
     }
   }
-  check_resume_prd (cr->prd);
+  check_resume_god (cr->god);
 }
 
 
@@ -350,10 +426,10 @@ exchange_found_cb (void *cls,
     cr->rh = TALER_EXCHANGE_refund (eh,
                                     &cr->refund_amount,
                                     &cr->refund_fee,
-                                    &cr->prd->h_contract_terms,
+                                    &cr->god->h_contract_terms,
                                     &cr->coin_pub,
                                     cr->rtransaction_id,
-                                    &cr->prd->merchant->privkey,
+                                    &cr->god->merchant->privkey,
                                     &refund_cb,
                                     cr);
     return;
@@ -361,7 +437,7 @@ exchange_found_cb (void *cls,
   cr->exchange_status = hr->http_status;
   cr->exchange_code = hr->ec;
   cr->exchange_reply = json_incref ((json_t*) hr->reply);
-  check_resume_prd (cr->prd);
+  check_resume_god (cr->god);
 }
 
 
@@ -386,7 +462,7 @@ process_refunds_cb (void *cls,
                     const struct TALER_Amount *refund_amount,
                     const struct TALER_Amount *refund_fee)
 {
-  struct ProcessRefundData *prd = cls;
+  struct GetOrderData *god = cls;
   struct CoinRefund *cr;
 
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -396,83 +472,230 @@ process_refunds_cb (void *cls,
               reason);
   cr = GNUNET_new (struct CoinRefund);
   cr->exchange_url = GNUNET_strdup (exchange_url);
-  cr->prd = prd;
+  cr->god = god;
   cr->coin_pub = *coin_pub;
   cr->rtransaction_id = rtransaction_id;
   cr->refund_amount = *refund_amount;
   cr->refund_fee = *refund_fee;
-  GNUNET_CONTAINER_DLL_insert (prd->cr_head,
-                               prd->cr_tail,
+  GNUNET_CONTAINER_DLL_insert (god->cr_head,
+                               god->cr_tail,
                                cr);
+  if (god->refunded)
+  {
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&god->refund_amount,
+                                     &god->refund_amount,
+                                     refund_amount));
+    return;
+  }
+  god->refund_amount = *refund_amount;
+  god->refunded = true;
 }
 
 
 /**
- * Force resuming all suspended refund lookups, needed during shutdown.
+ * Suspend this @a god until the trigger is satisfied.
+ *
+ * @param ppr
  */
-void
-MH_force_refund_resume (void)
+static void
+suspend_god (struct GetOrderData *god)
 {
-  struct ProcessRefundData *prd;
+  TMH_compute_pay_key (god->order_id,
+                       &god->mi->pubkey,
+                       &god->sc.key);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Suspending /poll-payment on key %s\n",
+              GNUNET_h2s (&god->sc.key));
+  TMH_long_poll_suspend (&god->sc,
+                         (god->sc.awaiting_refund)
+                         ? &god->sc.refund_expected
+                         : NULL);
+}
 
-  while (NULL != (prd = prd_head))
+
+/**
+ * Clean up the session state for a GET /orders/$ID request.
+ *
+ * @param cls must be a `struct GetOrderData *`
+ */
+static void
+god_cleanup (void *cls)
+{
+  struct GetOrderData *god = cls;
+
+  while (NULL != (cr = god->cr_head))
   {
-    GNUNET_CONTAINER_DLL_remove (prd_head,
-                                 prd_tail,
-                                 prd);
-    GNUNET_assert (prd->suspended);
-    prd->suspended = GNUNET_NO;
-    MHD_resume_connection (prd->connection);
+    GNUNET_CONTAINER_DLL_remove (god->cr_head,
+                                 god->cr_tail,
+                                 cr);
+    if (NULL != cr->fo)
+    {
+      TMH_EXCHANGES_find_exchange_cancel (cr->fo);
+      cr->fo = NULL;
+    }
+    if (NULL != cr->rh)
+    {
+      TALER_EXCHANGE_refund_cancel (cr->rh);
+      cr->rh = NULL;
+    }
+    if (NULL != cr->exchange_reply)
+    {
+      json_decref (cr->exchange_reply);
+      cr->exchange_reply = NULL;
+    }
+    GNUNET_free (cr->exchange_url);
+    GNUNET_free (cr);
   }
+
+  if (NULL != god->contract_terms)
+    json_decref (god->contract_terms);
+  GNUNET_free_non_null (god->final_contract_url);
+  GNUNET_free (god);
 }
 
 
 /**
- * Return refund situation about a contract.
+ * Handle a GET "/orders/$ID" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_refund_lookup (struct TMH_RequestHandler *rh,
-                          struct MHD_Connection *connection,
-                          void **connection_cls,
-                          const char *upload_data,
-                          size_t *upload_data_size,
-                          struct MerchantInstance *mi)
+TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
+                   struct MHD_Connection *connection,
+                   struct TMH_HandlerContext *hc)
 {
-  struct ProcessRefundData *prd;
-  const char *order_id;
-  json_t *contract_terms;
+  struct GetOrderData *god = hc->ctx;
+  const char *order_id = hc->infix;
   enum GNUNET_DB_QueryStatus qs;
 
-  prd = *connection_cls;
-  if (NULL == prd)
+  if (NULL == god)
   {
-    order_id = MHD_lookup_connection_value (connection,
-                                            MHD_GET_ARGUMENT_KIND,
-                                            "order_id");
-    if (NULL == order_id)
+    god = GNUNET_new (struct GetOrderData);
+    hc->ctx = god;
+    god->hc.cc = &god_cleanup;
+    god->sc.con = connection;
+    god->ec = TALER_EC_NONE;
+    god->connection = connection;
+    god->hc = hc;
+    god->ret = GNUNET_SYSERR;
+
     {
-      GNUNET_break_op (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         TALER_EC_PARAMETER_MISSING,
-                                         "order_id");
+      const char *cts;
+
+      cts = MHD_lookup_connection_value (connection,
+                                         MHD_GET_ARGUMENT_KIND,
+                                         "h_contract");
+      if (NULL == cts)
+      {
+        /* h_contract required but missing */
+        GNUNET_break_op (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MISSING,
+                                           "h_contract required");
+      }
+      if (GNUNET_OK !=
+          GNUNET_CRYPTO_hash_from_string (cts,
+                                          &god->h_contract_terms))
+      {
+        /* cts has wrong encoding */
+        GNUNET_break_op (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MALFORMED,
+                                           "h_contract malformed");
+      }
+    }
+
+    {
+      const char *long_poll_timeout_s;
+
+      long_poll_timeout_s = MHD_lookup_connection_value (connection,
+                                                         MHD_GET_ARGUMENT_KIND,
+                                                         "timeout");
+      if (NULL != long_poll_timeout_s)
+      {
+        unsigned int timeout;
+
+        if (1 != sscanf (long_poll_timeout_s,
+                         "%u",
+                         &timeout))
+        {
+          GNUNET_break_op (0);
+          return TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_BAD_REQUEST,
+                                             TALER_EC_PARAMETER_MALFORMED,
+                                             "timeout must be non-negative 
number");
+        }
+        god->sc.long_poll_timeout
+          = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
+                                                GNUNET_TIME_UNIT_SECONDS,
+                                                timeout));
+      }
+      else
+      {
+        god->sc.long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
+      }
     }
 
+    {
+      const char *min_refund;
+
+      min_refund = MHD_lookup_connection_value (connection,
+                                                MHD_GET_ARGUMENT_KIND,
+                                                "refund");
+      if (NULL != min_refund)
+      {
+        if ( (GNUNET_OK !=
+              TALER_string_to_amount (min_refund,
+                                      &god->sc.refund_expected)) ||
+             (0 != strcasecmp (god->sc.refund_expected.currency,
+                               TMH_currency) ) )
+        {
+          GNUNET_break_op (0);
+          return TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_BAD_REQUEST,
+                                             TALER_EC_PARAMETER_MALFORMED,
+                                             "invalid amount given for refund 
argument");
+        }
+        god->sc.awaiting_refund = true;
+      }
+    }
+
+    // FIXME: what is this about again???
+    god->contract_url = MHD_lookup_connection_value (connection,
+                                                     MHD_GET_ARGUMENT_KIND,
+                                                     "contract_url");
+    if (NULL == god->contract_url)
+    {
+      // FIXME: this can't be right anymore...
+      god->final_contract_url = TALER_url_absolute_mhd (connection,
+                                                        "/public/proposal",
+                                                        "instance", mi->id,
+                                                        "order_id",
+                                                        god->order_id,
+                                                        NULL);
+      GNUNET_assert (NULL != god->final_contract_url);
+    }
+    else
+    {
+      god->final_contract_url = GNUNET_strdup (god->contract_url);
+    }
+
+    god->session_id = MHD_lookup_connection_value (connection,
+                                                   MHD_GET_ARGUMENT_KIND,
+                                                   "session_id");
+
     /* Convert order id to h_contract_terms */
-    contract_terms = NULL;
     db->preflight (db->cls);
     qs = db->find_contract_terms (db->cls,
-                                  &contract_terms,
+                                  hc->instance->settings.id,
                                   order_id,
-                                  &mi->pubkey);
+                                  &god->contract_terms);
     if (0 > qs)
     {
       /* single, read-only SQL statements should never cause
@@ -483,9 +706,8 @@ MH_handler_refund_lookup (struct TMH_RequestHandler *rh,
       return TALER_MHD_reply_with_error (connection,
                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
                                          TALER_EC_REFUND_LOOKUP_DB_ERROR,
-                                         "database error looking up order_id 
from merchant_contract_terms table");
+                                         "database error looking up contract");
     }
-
     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     {
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -493,108 +715,210 @@ MH_handler_refund_lookup (struct TMH_RequestHandler *rh,
                   order_id);
       return TALER_MHD_reply_with_error (connection,
                                          MHD_HTTP_NOT_FOUND,
-                                         TALER_EC_REFUND_ORDER_ID_UNKNOWN,
+                                         TALER_EC_ORDER_ID_UNKNOWN,
                                          "order_id not found in database");
     }
 
-    prd = GNUNET_new (struct ProcessRefundData);
-    if (GNUNET_OK !=
-        TALER_JSON_hash (contract_terms,
-                         &prd->h_contract_terms))
+    /* Check client provided the right hash code of the contract terms */
+    {
+      struct GNUNET_HashCode h;
+
+      if (GNUNET_OK !=
+          TALER_JSON_hash (god->contract_terms,
+                           &h))
+      {
+        GNUNET_break (0);
+        GNUNET_free (god);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_INTERNAL_LOGIC_ERROR,
+                                           "Could not hash contract terms");
+      }
+      if (0 !=
+          GNUNET_memcmp (&h,
+                         &god->h_contract_terms))
+      {
+        GNUNET_break_op (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_FORBIDDEN,
+                                           TALER_EC_WRONG_CONTRACT,
+                                           "Contract hash does not match 
order");
+      }
+    }
+
+    {
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_string ("fulfillment_url",
+                                 &god->fulfillment_url),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (god->contract_terms,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           
TALER_EC_CHECK_PAYMENT_DB_FETCH_CONTRACT_TERMS_ERROR,
+                                           "Merchant database error (contract 
terms corrupted)");
+      }
+    }
+  } /* end of first-time initialization / sanity checks */
+
+  if ( (NULL != god->session_id) &&
+       (NULL != god->fulfillment_url) )
+  {
+    /* Check if paid within a session. */
+    char *already_paid_order_id = NULL;
+
+    qs = db->find_session_info (db->cls,
+                                &already_paid_order_id,
+                                god->session_id,
+                                god->fulfillment_url,
+                                &mi->pubkey);
+    if (qs < 0)
     {
-      GNUNET_break (0);
-      json_decref (contract_terms);
-      GNUNET_free (prd);
+      /* single, read-only SQL statements should never cause
+         serialization problems */
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+      /* Always report on hard error as well to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
       return TALER_MHD_reply_with_error (connection,
                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_INTERNAL_LOGIC_ERROR,
-                                         "Could not hash contract terms");
+                                         
TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR,
+                                         "db error fetching pay session info");
     }
-    json_decref (contract_terms);
-    prd->hc.cc = &cleanup_prd;
-    prd->merchant = mi;
-    prd->ec = TALER_EC_NONE;
-    prd->connection = connection;
-    *connection_cls = prd;
-
-    for (unsigned int i = 0; i<MAX_RETRIES; i++)
+    else if (0 == qs)
     {
-      qs = db->get_refunds_from_contract_terms_hash (db->cls,
-                                                     &mi->pubkey,
-                                                     &prd->h_contract_terms,
-                                                     &process_refunds_cb,
-                                                     prd);
-      if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
-        break;
+      ret = send_pay_request (god,
+                              already_paid_order_id);
+      GNUNET_free_non_null (already_paid_order_id);
+      return ret;
     }
+    GNUNET_break (1 == qs);
+    GNUNET_break (0 == strcmp (order_id,
+                               already_paid_order_id));
+    GNUNET_free_non_null (already_paid_order_id);
+  }
+  else
+  {
+    /* Check if paid regardless of session. */
+    json_t *xcontract_terms = NULL;
+
+    qs = db->find_paid_contract_terms_from_hash (db->cls,
+                                                 &xcontract_terms,
+                                                 &god->h_contract_terms,
+                                                 &mi->pubkey);
     if (0 > qs)
     {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Database hard error on refunds_from_contract_terms_hash 
lookup: %s\n",
-                  GNUNET_h2s (&prd->h_contract_terms));
+      /* Always report on hard error as well to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
       return TALER_MHD_reply_with_error (connection,
                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_REFUND_LOOKUP_DB_ERROR,
-                                         "Failed to lookup refunds for 
contract");
+                                         
TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
+                                         "Merchant database error");
+    }
+    if (0 == qs)
+    {
+      return send_pay_request (god,
+                               NULL);
     }
+    GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+    GNUNET_assert (NULL != xcontract_terms);
+    json_decref (xcontract_terms);
+  }
 
-    /* Now launch exchange interactions, unless we already have the
-       response in the database! */
-    for (struct CoinRefund *cr = prd->cr_head;
-         NULL != cr;
-         cr = cr->next)
+
+  for (unsigned int i = 0; i<MAX_RETRIES; i++)
+  {
+    qs = db->get_refunds_from_contract_terms_hash (db->cls,
+                                                   hc->instance->settings.id,
+                                                   &god->h_contract_terms,
+                                                   &process_refunds_cb,
+                                                   god);
+    if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+      break;
+  }
+  if (0 > qs)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_REFUND_LOOKUP_DB_ERROR,
+                                       "Failed to lookup refunds for 
contract");
+  }
+
+  /* Now launch exchange interactions, unless we already have the
+     response in the database! */
+  for (struct CoinRefund *cr = god->cr_head;
+       NULL != cr;
+       cr = cr->next)
+  {
+    enum GNUNET_DB_QueryStatus qs;
+
+    qs = db->get_refund_proof (db->cls,
+                               &cr->god->merchant->pubkey,
+                               &cr->god->h_contract_terms,
+                               &cr->coin_pub,
+                               cr->rtransaction_id,
+                               &cr->exchange_pub,
+                               &cr->exchange_sig);
+    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     {
-      enum GNUNET_DB_QueryStatus qs;
-
-      qs = db->get_refund_proof (db->cls,
-                                 &cr->prd->merchant->pubkey,
-                                 &cr->prd->h_contract_terms,
-                                 &cr->coin_pub,
-                                 cr->rtransaction_id,
-                                 &cr->exchange_pub,
-                                 &cr->exchange_sig);
-      if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
-      {
-        /* We need to talk to the exchange */
-        cr->fo = TMH_EXCHANGES_find_exchange (cr->exchange_url,
-                                              NULL,
-                                              GNUNET_NO,
-                                              &exchange_found_cb,
-                                              cr);
-      }
+      /* We need to talk to the exchange */
+      cr->fo = TMH_EXCHANGES_find_exchange (cr->exchange_url,
+                                            NULL,
+                                            GNUNET_NO,
+                                            &exchange_found_cb,
+                                            cr);
     }
   }
 
+  if ( (god->awaiting_refund) &&
+       ( (! god->refunded) ||
+         (1 != TALER_amount_cmp (&god->refund_amount,
+                                 &god->min_refund)) ) )
+  {
+    /* Client is waiting for a refund larger than what we have, suspend
+       until timeout */
+    struct GNUNET_TIME_Relative remaining;
+
+    remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
+    if (0 != remaining.rel_value_us)
+    {
+      /* yes, indeed suspend */
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                  "Awaiting refund exceeding %s\n",
+                  TALER_amount2s (&god->min_refund));
+      suspend_god (god);
+      return MHD_YES;
+    }
+  }
+
+
   /* Check if there are still exchange operations pending */
-  if (GNUNET_YES == prd_pending (prd))
+  if (GNUNET_YES == god_pending (god))
   {
-    if (! prd->suspended)
+    if (! god->suspended)
     {
-      prd->suspended = GNUNET_YES;
+      god->suspended = GNUNET_YES;
       MHD_suspend_connection (connection);
-      GNUNET_CONTAINER_DLL_insert (prd_head,
-                                   prd_tail,
-                                   prd);
+      GNUNET_CONTAINER_DLL_insert (god_head,
+                                   god_tail,
+                                   god);
     }
     return MHD_YES;   /* we're still talking to the exchange */
   }
 
   /* All operations done, build final response */
-  if (NULL == prd->cr_head)
-  {
-    /* There ARE no refunds scheduled, bitch */
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_REFUND_LOOKUP_NO_REFUND,
-                                       "This contract is not currently 
eligible for refunds");
-  }
-
   {
     json_t *ra;
 
     ra = json_array ();
     GNUNET_assert (NULL != ra);
-    for (struct CoinRefund *cr = prd->cr_head;
+    for (struct CoinRefund *cr = god->cr_head;
          NULL != cr;
          cr = cr->next)
     {
@@ -637,6 +961,23 @@ MH_handler_refund_lookup (struct TMH_RequestHandler *rh,
                        GNUNET_JSON_from_data_auto (&cr->exchange_sig)
                        )));
     }
+
+    if (god->refunded)
+      return TALER_MHD_reply_json_pack (connection,
+                                        MHD_HTTP_OK,
+                                        "{s:b, s:b, s:o}",
+                                        "paid", 1,
+                                        "refunded", god->refunded,
+                                        "refund_amount",
+                                        TALER_JSON_from_amount (
+                                          &god->refund_amount));
+    return TALER_MHD_reply_json_pack (connection,
+                                      MHD_HTTP_OK,
+                                      "{s:b, s:b }",
+                                      "paid", 1,
+                                      "refunded", 0);
+
+
     return TALER_MHD_reply_json_pack (
       connection,
       MHD_HTTP_OK,
@@ -646,9 +987,9 @@ MH_handler_refund_lookup (struct TMH_RequestHandler *rh,
       "merchant_pub",
       GNUNET_JSON_from_data_auto (&mi->pubkey),
       "h_contract_terms",
-      GNUNET_JSON_from_data_auto (&prd->h_contract_terms));
+      GNUNET_JSON_from_data_auto (&god->h_contract_terms));
   }
 }
 
 
-/* end of taler-merchant-httpd_refund_lookup.c */
+/* end of taler-merchant-httpd_get-orders-ID.c */
diff --git a/src/backend/taler-merchant-httpd_get-orders-ID.h 
b/src/backend/taler-merchant-httpd_get-orders-ID.h
index 24495da..3aa157d 100644
--- a/src/backend/taler-merchant-httpd_get-orders-ID.h
+++ b/src/backend/taler-merchant-httpd_get-orders-ID.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014, 2015, 2016, 2017 Taler Systems SA
+  (C) 2014, 2015, 2016, 2017, 2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free 
Software
@@ -13,36 +13,28 @@
   You should have received a copy of the GNU General Public License along with
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
-
 /**
- * @file backend/taler-merchant-httpd_refund_lookup.h
- * @brief
+ * @file backend/taler-merchant-httpd_get-orders-ID.h
+ * @brief implementation of GET /orders/$ID
  * @author Marcello Stanisci
  */
-
-#ifndef TALER_MERCHANT_HTTPD_REFUND_LOOKUP_H
-#define TALER_MERCHANT_HTTPD_REFUND_LOOKUP_H
+#ifndef TALER_MERCHANT_HTTPD_GET_ORDERS_ID_H
+#define TALER_MERCHANT_HTTPD_GET_ORDERS_ID_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
 
 /**
- * Return refund situation about a contract.
+ * Handle a GET "/orders/$ID" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_refund_lookup (struct TMH_RequestHandler *rh,
-                          struct MHD_Connection *connection,
-                          void **connection_cls,
-                          const char *upload_data,
-                          size_t *upload_data_size,
-                          struct MerchantInstance *mi);
+TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
+                   struct MHD_Connection *connection,
+                   struct TMH_HandlerContext *hc);
 
 #endif

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]