/*
This file is part of TALER
(C) 2014-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
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
*/
/**
* @file taler-merchant-httpd_get-orders-ID.c
* @brief implementation of GET /orders/$ID
* @author Marcello Stanisci
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include
#include
#include
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_get-orders-ID.h"
#include "taler-merchant-httpd_mhd.h"
#include "taler-merchant-httpd_qr.h"
#include "taler-merchant-httpd_templating.h"
/**
* How often do we retry DB transactions on serialization failures?
*/
#define MAX_RETRIES 5
/**
* Context for the operation.
*/
struct GetOrderData
{
/**
* Hashed version of contract terms. All zeros if not provided.
*/
struct GNUNET_HashCode h_contract_terms;
/**
* Claim token used for access control. All zeros if not provided.
*/
struct TALER_ClaimTokenP claim_token;
/**
* DLL of (suspended) requests.
*/
struct GetOrderData *next;
/**
* DLL of (suspended) requests.
*/
struct GetOrderData *prev;
/**
* Context of the request.
*/
struct TMH_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;
/**
* 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; but can also be NULL if the contract_terms does not come with
* a fulfillment URL).
*/
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;
/**
* Total refunds granted for this payment. Only initialized
* if @e refunded is set to true.
*/
struct TALER_Amount refund_amount;
/**
* Did we suspend @a connection?
*/
bool suspended;
/**
* Return code: #TALER_EC_NONE if successful.
*/
enum TALER_ErrorCode ec;
/**
* Set to true if we are dealing with an unclaimed order
* (and thus @e h_contract_terms is not set, and certain
* DB queries will not work).
*/
bool unclaimed;
/**
* Set to true if this payment has been refunded and
* @e refund_amount is initialized.
*/
bool refunded;
/**
* Set to true if a refund is still available for the
* wallet for this payment.
*/
bool refund_available;
/**
* Set to true if the client requested HTML, otherwise we generate JSON.
*/
bool generate_html;
};
/**
* Head of DLL of (suspended) requests.
*/
static struct GetOrderData *god_head;
/**
* Tail of DLL of (suspended) requests.
*/
static struct GetOrderData *god_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 = false;
MHD_resume_connection (god->sc.con);
}
}
/**
* Suspend this @a god until the trigger is satisfied.
*
* @param god request to suspend
*/
static void
suspend_god (struct GetOrderData *god)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Suspending GET /orders/%s\n",
god->order_id);
TMH_long_poll_suspend (god->order_id,
god->session_id,
god->fulfillment_url,
god->hc->instance,
&god->sc,
god->sc.awaiting_refund
? &god->sc.refund_expected
: NULL);
}
/**
* Create a taler://refund/ URI for the given @a con and @a order_id
* and @a instance_id.
*
* @param con HTTP connection
* @param order_id the order id
* @param instance_id instance, may be "default"
* @return corresponding taler://refund/ URI, or NULL on missing "host"
*/
static char *
make_taler_refund_uri (struct MHD_Connection *con,
const char *order_id,
const char *instance_id)
{
const char *host;
const char *forwarded_host;
const char *uri_path;
struct GNUNET_Buffer buf = { 0 };
host = MHD_lookup_connection_value (con,
MHD_HEADER_KIND,
"Host");
forwarded_host = MHD_lookup_connection_value (con,
MHD_HEADER_KIND,
"X-Forwarded-Host");
uri_path = MHD_lookup_connection_value (con,
MHD_HEADER_KIND,
"X-Forwarded-Prefix");
if (NULL != forwarded_host)
host = forwarded_host;
if (NULL == host)
{
GNUNET_break (0);
return NULL;
}
GNUNET_assert (NULL != instance_id);
GNUNET_assert (NULL != order_id);
GNUNET_buffer_write_str (&buf,
"taler");
if (GNUNET_NO == TALER_mhd_is_https (con))
GNUNET_buffer_write_str (&buf,
"+http");
GNUNET_buffer_write_str (&buf,
"://refund/");
GNUNET_buffer_write_str (&buf,
host);
if (NULL != uri_path)
GNUNET_buffer_write_path (&buf,
uri_path);
if (0 != strcmp ("default",
instance_id))
{
GNUNET_buffer_write_path (&buf,
"instances");
GNUNET_buffer_write_path (&buf,
instance_id);
}
GNUNET_buffer_write_path (&buf,
order_id);
GNUNET_buffer_write_path (&buf,
""); // Trailing slash
return GNUNET_buffer_reap_str (&buf);
}
/**
* Create a http(s) URL for the given @a con and @a order_id
* and @a instance_id to display the /orders/{order_id} page.
*
* @param con HTTP connection
* @param order_id the order id
* @param session_id the session id
* @param instance_id instance, may be "default"
* @param claim_token claim token for the order, may be NULL
* @param h_contract contract hash for authentication, may be NULL
* @return corresponding http(s):// URL, or NULL on missing "host"
*/
char *
TMH_make_order_status_url (struct MHD_Connection *con,
const char *order_id,
const char *session_id,
const char *instance_id,
struct TALER_ClaimTokenP *claim_token,
struct GNUNET_HashCode *h_contract)
{
const char *host;
const char *forwarded_host;
const char *uri_path;
struct GNUNET_Buffer buf = { 0 };
/* Number of query parameters written so far */
unsigned int num_qp = 0;
host = MHD_lookup_connection_value (con,
MHD_HEADER_KIND,
"Host");
forwarded_host = MHD_lookup_connection_value (con,
MHD_HEADER_KIND,
"X-Forwarded-Host");
uri_path = MHD_lookup_connection_value (con,
MHD_HEADER_KIND,
"X-Forwarded-Prefix");
if (NULL != forwarded_host)
host = forwarded_host;
if (NULL == host)
{
GNUNET_break (0);
return NULL;
}
GNUNET_assert (NULL != instance_id);
GNUNET_assert (NULL != order_id);
if (GNUNET_NO == TALER_mhd_is_https (con))
GNUNET_buffer_write_str (&buf,
"http://");
else
GNUNET_buffer_write_str (&buf,
"https://");
GNUNET_buffer_write_str (&buf,
host);
if (NULL != uri_path)
GNUNET_buffer_write_path (&buf,
uri_path);
if (0 != strcmp ("default",
instance_id))
{
GNUNET_buffer_write_path (&buf,
"instances");
GNUNET_buffer_write_path (&buf,
instance_id);
}
GNUNET_buffer_write_path (&buf,
"/orders");
GNUNET_buffer_write_path (&buf,
order_id);
if ((NULL != claim_token) &&
(GNUNET_NO == GNUNET_is_zero (claim_token)))
{
GNUNET_buffer_write_str (&buf,
"?token=");
GNUNET_buffer_write_data_encoded (&buf,
(char *) claim_token,
sizeof (struct TALER_ClaimTokenP));
num_qp++;
}
if (NULL != session_id)
{
if (num_qp > 0)
GNUNET_buffer_write_str (&buf,
"&session_id=");
else
GNUNET_buffer_write_str (&buf,
"?session_id=");
GNUNET_buffer_write_str (&buf,
session_id);
num_qp++;
}
if (NULL != h_contract)
{
if (num_qp > 0)
GNUNET_buffer_write_str (&buf,
"&h_contract=");
else
GNUNET_buffer_write_str (&buf,
"?h_contract=");
GNUNET_buffer_write_data_encoded (&buf,
(char *) h_contract,
sizeof (struct GNUNET_HashCode));
}
return GNUNET_buffer_reap_str (&buf);
}
/**
* Create a taler://pay/ URI for the given @a con and @a order_id
* and @a session_id and @a instance_id.
*
* @param con HTTP connection
* @param order_id the order id
* @param session_id session, may be NULL
* @param instance_id instance, may be "default"
* @param claim_token claim token for the order, may be NULL
* @return corresponding taler://pay/ URI, or NULL on missing "host"
*/
char *
TMH_make_taler_pay_uri (struct MHD_Connection *con,
const char *order_id,
const char *session_id,
const char *instance_id,
struct TALER_ClaimTokenP *claim_token)
{
const char *host;
const char *forwarded_host;
const char *uri_path;
struct GNUNET_Buffer buf = { 0 };
host = MHD_lookup_connection_value (con,
MHD_HEADER_KIND,
"Host");
forwarded_host = MHD_lookup_connection_value (con,
MHD_HEADER_KIND,
"X-Forwarded-Host");
uri_path = MHD_lookup_connection_value (con,
MHD_HEADER_KIND,
"X-Forwarded-Prefix");
if (NULL != forwarded_host)
host = forwarded_host;
if (NULL == host)
{
GNUNET_break (0);
return NULL;
}
GNUNET_assert (NULL != instance_id);
GNUNET_assert (NULL != order_id);
GNUNET_buffer_write_str (&buf,
"taler");
if (GNUNET_NO == TALER_mhd_is_https (con))
GNUNET_buffer_write_str (&buf,
"+http");
GNUNET_buffer_write_str (&buf,
"://pay/");
GNUNET_buffer_write_str (&buf,
host);
if (NULL != uri_path)
GNUNET_buffer_write_path (&buf,
uri_path);
if (0 != strcmp ("default",
instance_id))
{
GNUNET_buffer_write_path (&buf,
"instances");
GNUNET_buffer_write_path (&buf,
instance_id);
}
GNUNET_buffer_write_path (&buf,
order_id);
GNUNET_buffer_write_path (&buf,
(session_id == NULL) ? "" : session_id);
if ((NULL != claim_token) &&
(GNUNET_NO == GNUNET_is_zero (claim_token)))
{
GNUNET_buffer_write_str (&buf,
"?c=");
GNUNET_buffer_write_data_encoded (&buf,
(char *) claim_token,
sizeof (struct TALER_ClaimTokenP));
}
return GNUNET_buffer_reap_str (&buf);
}
/**
* Return the order summary of the contract of @a god in the
* preferred language of the HTTP client.
*
* @param god order to extract summary from
* @return NULL if no summary was provided in the contract
*/
static const char *
get_order_summary (struct GetOrderData *god)
{
const char *language_pattern;
const char *ret;
language_pattern = MHD_lookup_connection_value (god->sc.con,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
if (NULL == language_pattern)
language_pattern = "en";
ret = json_string_value (TALER_JSON_extract_i18n (god->contract_terms,
language_pattern,
"summary"));
if (NULL == ret)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"No order summary found!\n");
ret = "";
}
return ret;
}
/**
* The client did not yet pay, send it the payment request.
*
* @param god check pay request context
* @param already_paid_order_id if for the fulfillment URI there is
* already a paid order, this is the order ID to redirect
* the wallet to; NULL if not applicable
* @return #MHD_YES on success
*/
static MHD_RESULT
send_pay_request (struct GetOrderData *god,
const char *already_paid_order_id)
{
MHD_RESULT ret;
char *taler_pay_uri;
char *order_status_url;
struct GNUNET_TIME_Relative remaining;
remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
if ( (0 != remaining.rel_value_us) &&
( (NULL == already_paid_order_id) ||
(NULL == god->fulfillment_url) ) )
{
/* long polling: do not queue a response, suspend connection instead */
suspend_god (god);
return MHD_YES;
}
/* 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->hc->instance->settings.id,
&god->claim_token);
order_status_url = TMH_make_order_status_url (god->sc.con,
god->order_id,
god->session_id,
god->hc->instance->settings.id,
&god->claim_token,
NULL);
if (god->generate_html)
{
char *qr;
if ( (NULL != already_paid_order_id) &&
(NULL != god->fulfillment_url) )
{
struct MHD_Response *reply;
MHD_RESULT ret;
reply = MHD_create_response_from_buffer (0,
NULL,
MHD_RESPMEM_PERSISTENT);
if (NULL == reply)
{
GNUNET_break (0);
return MHD_NO;
}
GNUNET_break (MHD_YES ==
MHD_add_response_header (reply,
MHD_HTTP_HEADER_LOCATION,
god->fulfillment_url));
ret = MHD_queue_response (god->sc.con,
MHD_HTTP_FOUND,
reply);
MHD_destroy_response (reply);
return ret;
}
qr = TMH_create_qrcode (taler_pay_uri);
if (NULL == qr)
{
GNUNET_break (0);
return MHD_NO;
}
{
enum GNUNET_GenericReturnValue res;
json_t *context;
context = json_pack ("{s:s, s:s, s:s, s:s}",
"taler_pay_uri",
taler_pay_uri,
"order_status_url",
order_status_url,
"taler_pay_qrcode_svg",
qr,
"order_summary",
get_order_summary (god));
GNUNET_assert (NULL != context);
res = TMH_return_from_template (god->sc.con,
MHD_HTTP_PAYMENT_REQUIRED,
"request_payment",
taler_pay_uri,
context);
if (GNUNET_SYSERR == res)
{
GNUNET_break (0);
ret = MHD_NO;
}
ret = MHD_YES;
json_decref (context);
}
GNUNET_free (qr);
}
else
{
ret = TALER_MHD_reply_json_pack (god->sc.con,
MHD_HTTP_PAYMENT_REQUIRED,
"{s:s, s:s?, s:s?}",
"taler_pay_uri",
taler_pay_uri,
"fulfillment_url",
god->fulfillment_url,
"already_paid_order_id",
already_paid_order_id);
}
GNUNET_free (taler_pay_uri);
GNUNET_free (order_status_url);
return ret;
}
/**
* Function called with detailed information about a refund.
* It is responsible for packing up the data to return.
*
* @param cls closure
* @param refund_serial unique serial number of the refund
* @param timestamp time of the refund (for grouping of refunds in the wallet UI)
* @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 pending true if the this refund was not yet processed by the wallet/exchange
*/
static void
process_refunds_cb (void *cls,
uint64_t refund_serial,
struct GNUNET_TIME_Absolute timestamp,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const char *exchange_url,
uint64_t rtransaction_id,
const char *reason,
const struct TALER_Amount *refund_amount,
bool pending)
{
struct GetOrderData *god = cls;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Found refund of %s for coin %s with reason `%s' in database\n",
TALER_amount2s (refund_amount),
TALER_B2S (coin_pub),
reason);
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;
god->refund_available |= pending;
}
/**
* 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;
if (NULL != god->contract_terms)
json_decref (god->contract_terms);
GNUNET_free (god);
}
/**
* Handle a GET "/orders/$ID" request.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] hc context with further information about the request
* @return MHD result code
*/
MHD_RESULT
TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
struct GetOrderData *god = hc->ctx;
const char *order_id = hc->infix;
enum GNUNET_DB_QueryStatus qs;
bool contract_match = false;
bool token_match = false;
bool contract_available = false;
if (NULL == god)
{
god = GNUNET_new (struct GetOrderData);
hc->ctx = god;
hc->cc = &god_cleanup;
god->sc.con = connection;
god->hc = hc;
god->order_id = order_id;
{
const char *ct;
ct = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"token");
if ( (NULL != ct) &&
(GNUNET_OK !=
GNUNET_STRINGS_string_to_data (ct,
strlen (ct),
&god->claim_token,
sizeof (god->claim_token))) )
{
/* ct has wrong encoding */
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_PARAMETER_MALFORMED,
"token");
}
}
{
const char *cts;
cts = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"h_contract");
if ( (NULL != cts) &&
(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");
}
}
god->generate_html = TMH_MHD_test_html_desired (connection);
{
const char *long_poll_timeout_ms;
long_poll_timeout_ms = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"timeout_ms");
if ((NULL != long_poll_timeout_ms) &&
! god->generate_html)
{
unsigned int timeout;
if (1 != sscanf (long_poll_timeout_ms,
"%u",
&timeout))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_PARAMETER_MALFORMED,
"timeout_ms (must be non-negative number)");
}
god->sc.long_poll_timeout
= GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
GNUNET_TIME_UNIT_MILLISECONDS,
timeout));
}
else
{
god->sc.long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
}
}
{
const char *await_refund_obtained_s;
await_refund_obtained_s =
MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"await_refund_obtained");
god->sc.awaiting_refund_obtained =
(NULL != await_refund_obtained_s)
? 0 == strcasecmp (await_refund_obtained_s, "yes")
: false;
}
{
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,
"refund");
}
god->sc.awaiting_refund = true;
}
}
god->session_id = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"session_id");
} /* end of first-time initialization / sanity checks */
/* Convert order_id to h_contract_terms */
TMH_db->preflight (TMH_db->cls);
if (NULL == god->contract_terms)
{
uint64_t order_serial;
qs = TMH_db->lookup_contract_terms (TMH_db->cls,
hc->instance->settings.id,
order_id,
&god->contract_terms,
&order_serial);
if (0 > qs)
{
/* 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_GET_ORDERS_DB_LOOKUP_ERROR,
NULL);
}
}
/* Check client provided the right hash code of the contract terms */
if (NULL != god->contract_terms)
{
struct GNUNET_HashCode h;
contract_available = true;
if (GNUNET_OK !=
TALER_JSON_contract_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");
}
contract_match = (0 ==
GNUNET_memcmp (&h,
&god->h_contract_terms));
if ( (GNUNET_NO ==
GNUNET_is_zero (&god->h_contract_terms)) &&
(! contract_match) )
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_GET_ORDER_WRONG_CONTRACT,
NULL);
}
}
if (! contract_match)
{
struct TALER_ClaimTokenP db_claim_token;
struct GNUNET_HashCode unused;
qs = TMH_db->lookup_order (TMH_db->cls,
hc->instance->settings.id,
order_id,
&db_claim_token,
&unused,
(NULL == god->contract_terms)
? &god->contract_terms
: NULL);
if (0 > qs)
{
/* 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_GET_ORDERS_DB_LOOKUP_ERROR,
NULL);
}
god->unclaimed = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
! contract_available;
if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) &&
(NULL == god->contract_terms) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Unknown order id given: `%s'\n",
order_id);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_GET_ORDERS_ID_UNKNOWN,
order_id);
}
token_match = (0 == GNUNET_memcmp (&db_claim_token,
&god->claim_token));
} /* end unclaimed order logic */
if (NULL == god->fulfillment_url)
god->fulfillment_url = json_string_value (json_object_get (
god->contract_terms,
"fulfillment_url"));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Token match: %d, contract match: %d, unclaimed: %d\n",
token_match,
contract_match,
god->unclaimed);
if ( (god->unclaimed) &&
(! token_match) )
{
/* Token wrong, and required because contract is unclaimed */
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_MERCHANT_GET_ORDER_INVALID_TOKEN,
NULL);
}
if ( ( (! token_match) ||
(GNUNET_YES == GNUNET_is_zero (&god->claim_token)) ) &&
(! contract_match) )
{
if (NULL == god->fulfillment_url)
{
if (GNUNET_NO ==
GNUNET_is_zero (&god->h_contract_terms))
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_GET_ORDER_WRONG_CONTRACT,
NULL);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_MERCHANT_GET_ORDER_INVALID_TOKEN,
NULL);
}
if (god->generate_html)
{
/* Contract was claimed (maybe by another device), so this client
cannot get the status information. Redirect to fulfillment page,
where the client may be able to pickup a fresh order -- or might
be able authenticate via session ID */
struct MHD_Response *reply;
MHD_RESULT ret;
reply = MHD_create_response_from_buffer (0,
NULL,
MHD_RESPMEM_PERSISTENT);
if (NULL == reply)
{
GNUNET_break (0);
return MHD_NO;
}
GNUNET_break (MHD_YES ==
MHD_add_response_header (reply,
MHD_HTTP_HEADER_LOCATION,
god->fulfillment_url));
ret = MHD_queue_response (connection,
MHD_HTTP_FOUND,
reply);
MHD_destroy_response (reply);
return ret;
}
/* Need to generate JSON reply */
return TALER_MHD_reply_json_pack (connection,
MHD_HTTP_ACCEPTED,
"{s:s}",
"fulfillment_url",
god->fulfillment_url);
}
if (god->unclaimed)
{
/* Order is unclaimed, no need to check for payments or even
refunds, simply always generate payment request */
return send_pay_request (god,
NULL);
}
if ( (NULL != god->session_id) &&
(NULL != god->fulfillment_url) )
{
/* Check if paid within a session. */
char *already_paid_order_id = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Running re-purchase detection for %s/%s\n",
god->session_id,
god->fulfillment_url);
qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
hc->instance->settings.id,
god->fulfillment_url,
god->session_id,
&already_paid_order_id);
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_GET_ORDERS_DB_LOOKUP_ERROR,
NULL);
}
if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
(0 != strcmp (order_id,
already_paid_order_id)) )
{
MHD_RESULT ret;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Sending pay request for order %s (already paid: %s)\n",
order_id,
already_paid_order_id);
ret = send_pay_request (god,
already_paid_order_id);
GNUNET_free (already_paid_order_id);
return ret;
}
GNUNET_break (1 == qs);
GNUNET_free (already_paid_order_id);
}
{
/* Check if paid. */
struct GNUNET_HashCode h_contract;
bool paid;
qs = TMH_db->lookup_order_status (TMH_db->cls,
hc->instance->settings.id,
order_id,
&h_contract,
&paid);
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_GET_ORDERS_DB_LOOKUP_ERROR,
NULL);
}
GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
GNUNET_break (0 ==
GNUNET_memcmp (&h_contract,
&god->h_contract_terms));
if (! paid)
{
return send_pay_request (god,
NULL);
}
}
/* At this point, we know the contract was paid. Let's check for
refunds. First, clear away refunds found from previous invocations. */
GNUNET_assert (GNUNET_OK ==
TALER_amount_get_zero (TMH_currency,
&god->refund_amount));
qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
hc->instance->settings.id,
&god->h_contract_terms,
&process_refunds_cb,
god);
if (0 > qs)
{
GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GET_ORDERS_DB_LOOKUP_ERROR,
NULL);
}
if ( ((god->sc.awaiting_refund) &&
( (! god->refunded) ||
(1 != TALER_amount_cmp (&god->refund_amount,
&god->sc.refund_expected)) )) ||
((god->sc.awaiting_refund_obtained) &&
(god->refund_available)) )
{
/* 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 */
if (god->sc.awaiting_refund)
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Awaiting refund exceeding %s\n",
TALER_amount2s (&god->sc.refund_expected));
if (god->sc.awaiting_refund_obtained)
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Awaiting pending refunds\n");
suspend_god (god);
return MHD_YES;
}
}
/* All operations done, build final response */
{
if (god->generate_html)
{
enum GNUNET_GenericReturnValue res;
if (god->refund_available)
{
char *qr;
char *uri;
GNUNET_assert (NULL != god->contract_terms);
uri = make_taler_refund_uri (god->sc.con,
order_id,
hc->instance->settings.id);
if (NULL == uri)
{
GNUNET_break (0);
return TALER_MHD_reply_with_error (god->sc.con,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_ALLOCATION_FAILURE,
"refund URI");
}
qr = TMH_create_qrcode (uri);
if (NULL == qr)
{
GNUNET_break (0);
GNUNET_free (uri);
return TALER_MHD_reply_with_error (god->sc.con,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_ALLOCATION_FAILURE,
"qr code");
}
{
json_t *context;
context = json_pack ("{s:s, s:s, s:s, s:s}",
"order_summary",
get_order_summary (god),
"refund_amount",
TALER_amount2s (&god->refund_amount),
"taler_refund_uri",
uri,
"taler_refund_qrcode_svg",
qr);
GNUNET_assert (NULL != context);
res = TMH_return_from_template (god->sc.con,
MHD_HTTP_OK,
"offer_refund",
uri,
context);
json_decref (context);
}
GNUNET_free (uri);
GNUNET_free (qr);
}
else
{
json_t *context;
context = json_pack ("{s:O, s:s, s:s}",
"contract_terms",
god->contract_terms,
"order_summary",
get_order_summary (god),
"refund_amount",
TALER_amount2s (&god->refund_amount));
GNUNET_assert (NULL != context);
res = TMH_return_from_template (god->sc.con,
MHD_HTTP_OK,
"show_order_details",
NULL,
context);
json_decref (context);
}
if (GNUNET_SYSERR == res)
{
GNUNET_break (0);
return MHD_NO;
}
return MHD_YES;
}
else
{
return TALER_MHD_reply_json_pack (
connection,
MHD_HTTP_OK,
"{s:b, s:b, s:o}",
"refunded", god->refunded,
"refund_pending", god->refund_available,
"refund_amount",
TALER_JSON_from_amount (&god->refund_amount));
}
}
}
/* end of taler-merchant-httpd_get-orders-ID.c */