summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2024-02-24 17:28:29 +0100
committerChristian Grothoff <christian@grothoff.org>2024-02-24 17:28:29 +0100
commit1a695f06eaf4fa635f0235a28dac5dcdea5fd448 (patch)
tree92b9d51b07510f5183ebd23cc441e536ae39b44f
parent70a44acf8458cfed17191950cb41e69b6f06e64d (diff)
downloadmerchant-1a695f06eaf4fa635f0235a28dac5dcdea5fd448.tar.gz
merchant-1a695f06eaf4fa635f0235a28dac5dcdea5fd448.tar.bz2
merchant-1a695f06eaf4fa635f0235a28dac5dcdea5fd448.zip
major refactoring of taler-merchant-httpd_get-orders-ID.c --- no semantic change
-rw-r--r--src/backend/taler-merchant-httpd_get-orders-ID.c1746
-rw-r--r--src/backend/taler-merchant-httpd_get-orders-ID.h39
-rw-r--r--src/backend/taler-merchant-httpd_helper.c110
-rw-r--r--src/backend/taler-merchant-httpd_helper.h39
-rw-r--r--src/include/taler_merchant_testing_lib.h49
-rw-r--r--src/testing/test_merchant_api.c54
-rw-r--r--src/testing/testing_api_cmd_wallet_get_order.c90
7 files changed, 1289 insertions, 838 deletions
diff --git a/src/backend/taler-merchant-httpd_get-orders-ID.c b/src/backend/taler-merchant-httpd_get-orders-ID.c
index 13ff3fc9..aeaba030 100644
--- a/src/backend/taler-merchant-httpd_get-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_get-orders-ID.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2022 Taler Systems SA
+ (C) 2014-2024 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
@@ -40,6 +40,25 @@
/**
+ * The different phases in which we handle the request.
+ */
+enum Phase
+{
+ GOP_INIT = 0,
+ GOP_LOOKUP_TERMS,
+ GOP_PARSE_CONTRACT,
+ GOP_CHECK_CLIENT_ACCESS,
+ GOP_REDIRECT_TO_PAID_ORDER,
+ GOP_CHECK_CLAIMED,
+ GOP_CHECK_PAID,
+ GOP_CHECK_REFUNDED,
+ GOP_RETURN_STATUS,
+ GOP_RETURN_MHD_YES,
+ GOP_RETURN_MHD_NO
+};
+
+
+/**
* Context for the operation.
*/
struct GetOrderData
@@ -148,6 +167,12 @@ struct GetOrderData
struct TALER_Amount refund_taken;
/**
+ * Phase in which we currently are handling this
+ * request.
+ */
+ enum Phase phase;
+
+ /**
* Return code: #TALER_EC_NONE if successful.
*/
enum TALER_ErrorCode ec;
@@ -166,7 +191,7 @@ struct GetOrderData
* doing repurchase detection.
*/
enum TALER_EXCHANGE_YesNoAll allow_refunded_for_repurchase;
-
+
/**
* Set to true if the client passed 'h_contract'.
*/
@@ -212,6 +237,25 @@ struct GetOrderData
* a different currency then the main contract.
*/
bool bad_refund_currency_in_db;
+
+ /**
+ * Did the hash of the contract match the contract
+ * hash supplied by the client?
+ */
+ bool contract_match;
+
+ /**
+ * True if we had a claim token and the claim token
+ * provided by the client matched our claim token.
+ */
+ bool token_match;
+
+ /**
+ * True if we found a (claimed) contract for the order,
+ * false if we had an unclaimed order.
+ */
+ bool contract_available;
+
};
@@ -245,6 +289,112 @@ TMH_force_wallet_get_order_resume (void)
/**
+ * 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);
+ /* We reset the contract terms and start by looking them up
+ again, as while we are suspended fundamental things could
+ change (such as the contract being claimed) */
+ if (NULL != god->contract_terms)
+ {
+ json_decref (god->contract_terms);
+ god->fulfillment_url = NULL;
+ god->contract_terms = NULL;
+ god->contract_parsed = false;
+ god->merchant_base_url = NULL;
+ god->public_reorder_url = NULL;
+ }
+ GNUNET_assert (! god->suspended);
+ god->contract_parsed = false;
+ god->contract_match = false;
+ god->token_match = false;
+ god->contract_available = false;
+ god->phase = GOP_LOOKUP_TERMS;
+ god->suspended = GNUNET_YES;
+ GNUNET_CONTAINER_DLL_insert (god_head,
+ god_tail,
+ god);
+ MHD_suspend_connection (god->sc.con);
+}
+
+
+/**
+ * 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);
+ god->contract_terms = NULL;
+ }
+ if (NULL != god->refund_eh)
+ {
+ TMH_db->event_listen_cancel (god->refund_eh);
+ god->refund_eh = NULL;
+ }
+ if (NULL != god->pay_eh)
+ {
+ TMH_db->event_listen_cancel (god->pay_eh);
+ god->pay_eh = NULL;
+ }
+ GNUNET_free (god);
+}
+
+
+/**
+ * Finish the request by returning @a mret as the
+ * final result.
+ *
+ * @param[in,out] god request we are processing
+ * @param mret MHD result to return
+ */
+static void
+phase_end (struct GetOrderData *god,
+ MHD_RESULT mret)
+{
+ god->phase = (MHD_YES == mret)
+ ? GOP_RETURN_MHD_YES
+ : GOP_RETURN_MHD_NO;
+}
+
+
+/**
+ * Finish the request by returning an error @a ec
+ * with HTTP status @a http_status and @a message.
+ *
+ * @param[in,out] god request we are processing
+ * @param http_status HTTP status code to return
+ * @param ec error code to return
+ * @param message human readable hint to return, can be NULL
+ */
+static void
+phase_fail (struct GetOrderData *god,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const char *message)
+{
+ phase_end (god,
+ TALER_MHD_reply_with_error (god->sc.con,
+ http_status,
+ ec,
+ message));
+}
+
+
+/**
* We have received a trigger from the database
* that we should (possibly) resume the request.
*
@@ -333,195 +483,385 @@ resume_by_event (void *cls,
/**
- * Suspend this @a god until the trigger is satisfied.
+ * First phase (after request parsing).
+ * Set up long-polling.
*
- * @param god request to suspend
+ * @param[in,out] god request context
*/
static void
-suspend_god (struct GetOrderData *god)
+phase_init (struct GetOrderData *god)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Suspending GET /orders/%s\n",
- god->order_id);
- if (NULL != god->contract_terms)
+ god->phase++;
+ if (god->generate_html)
+ return; /* If HTML is requested, we never actually long poll. */
+ if (! GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout))
+ return; /* long polling not requested */
+
+ if (god->sc.awaiting_refund ||
+ god->sc.awaiting_refund_obtained)
{
- json_decref (god->contract_terms);
- god->fulfillment_url = NULL;
- god->contract_terms = NULL;
- god->contract_parsed = false;
- god->merchant_base_url = NULL;
- god->public_reorder_url = NULL;
+ struct TMH_OrderPayEventP refund_eh = {
+ .header.size = htons (sizeof (refund_eh)),
+ .header.type = htons (god->sc.awaiting_refund_obtained
+ ? TALER_DBEVENT_MERCHANT_REFUND_OBTAINED
+ : TALER_DBEVENT_MERCHANT_ORDER_REFUND),
+ .merchant_pub = god->hc->instance->merchant_pub
+ };
+
+ GNUNET_CRYPTO_hash (god->order_id,
+ strlen (god->order_id),
+ &refund_eh.h_order_id);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Subscribing %p to refunds on %s\n",
+ god,
+ god->order_id);
+ god->refund_eh
+ = TMH_db->event_listen (
+ TMH_db->cls,
+ &refund_eh.header,
+ GNUNET_TIME_absolute_get_remaining (
+ god->sc.long_poll_timeout),
+ &resume_by_event,
+ god);
+ }
+ {
+ struct TMH_OrderPayEventP pay_eh = {
+ .header.size = htons (sizeof (pay_eh)),
+ .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
+ .merchant_pub = god->hc->instance->merchant_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Subscribing to payments on %s\n",
+ god->order_id);
+ GNUNET_CRYPTO_hash (god->order_id,
+ strlen (god->order_id),
+ &pay_eh.h_order_id);
+ god->pay_eh
+ = TMH_db->event_listen (
+ TMH_db->cls,
+ &pay_eh.header,
+ GNUNET_TIME_absolute_get_remaining (
+ god->sc.long_poll_timeout),
+ &resume_by_event,
+ god);
}
- GNUNET_assert (! god->suspended);
- god->suspended = GNUNET_YES;
- GNUNET_CONTAINER_DLL_insert (god_head,
- god_tail,
- god);
- MHD_suspend_connection (god->sc.con);
}
/**
- * Create a taler://refund/ URI for the given @a con and @a order_id
- * and @a instance_id.
+ * Lookup contract terms and check client has the
+ * right to access this order (by claim token or
+ * contract hash).
*
- * @param merchant_base_url URL to take host and path from;
- * we cannot take it from the MHD connection as a browser
- * may have changed 'http' to 'https' and we MUST be consistent
- * with what the merchant's frontend used initially
- * @param order_id the order id
- * @return corresponding taler://refund/ URI, or NULL on missing "host"
+ * @param[in,out] god request context
*/
-static char *
-make_taler_refund_uri (const char *merchant_base_url,
- const char *order_id)
+static void
+phase_lookup_terms (struct GetOrderData *god)
{
- struct GNUNET_Buffer buf = { 0 };
- char *url;
- struct GNUNET_Uri uri;
+ uint64_t order_serial;
+ struct TALER_ClaimTokenP db_claim_token;
+ enum GNUNET_DB_QueryStatus qs;
- url = GNUNET_strdup (merchant_base_url);
- if (-1 == GNUNET_uri_parse (&uri,
- url))
+ /* Convert order_id to h_contract_terms */
+ TMH_db->preflight (TMH_db->cls);
+ GNUNET_assert (NULL == god->contract_terms);
+ qs = TMH_db->lookup_contract_terms (
+ TMH_db->cls,
+ god->hc->instance->settings.id,
+ god->order_id,
+ &god->contract_terms,
+ &order_serial,
+ &db_claim_token);
+ 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 (0);
- GNUNET_free (url);
- return NULL;
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_contract_terms");
+ return;
}
- GNUNET_assert (NULL != order_id);
- GNUNET_buffer_write_str (&buf,
- "taler");
- if (0 == strcasecmp ("http",
- uri.scheme))
- GNUNET_buffer_write_str (&buf,
- "+http");
- GNUNET_buffer_write_str (&buf,
- "://refund/");
- GNUNET_buffer_write_str (&buf,
- uri.host);
- if (0 != uri.port)
- GNUNET_buffer_write_fstr (&buf,
- ":%u",
- (unsigned int) uri.port);
- if (NULL != uri.path)
- GNUNET_buffer_write_path (&buf,
- uri.path);
- GNUNET_buffer_write_path (&buf,
- order_id);
- GNUNET_buffer_write_path (&buf,
- ""); // Trailing slash
- GNUNET_free (url);
- return GNUNET_buffer_reap_str (&buf);
-}
+ /* Note: when "!ord.requireClaimToken" and the client does not provide
+ a claim token (all zeros!), then token_match==TRUE below: */
+ god->token_match
+ = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ && (0 == GNUNET_memcmp (&db_claim_token,
+ &god->claim_token));
+ /* Check if client provided the right hash code of the contract terms */
+ if (NULL != god->contract_terms)
+ {
+ god->contract_available = true;
+ if (GNUNET_YES ==
+ GNUNET_is_zero (&god->h_contract_terms))
+ {
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (god->contract_terms,
+ &god->h_contract_terms))
+ {
+ GNUNET_break (0);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+ "contract terms");
+ return;
+ }
+ }
+ else
+ {
+ struct TALER_PrivateContractHashP h;
-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 TALER_PrivateContractHashP *h_contract)
-{
- struct GNUNET_Buffer buf;
- /* Number of query parameters written so far */
- unsigned int num_qp = 0;
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (god->contract_terms,
+ &h))
+ {
+ GNUNET_break (0);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+ "contract terms");
+ return;
+ }
+ god->contract_match = (0 ==
+ GNUNET_memcmp (&h,
+ &god->h_contract_terms));
+ if (! god->contract_match)
+ {
+ GNUNET_break_op (0);
+ phase_fail (god,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
+ NULL);
+ return;
+ }
+ }
+ }
- GNUNET_assert (NULL != instance_id);
- GNUNET_assert (NULL != order_id);
- if (GNUNET_OK !=
- TMH_base_url_by_connection (con,
- instance_id,
- &buf))
+ if (god->contract_available)
{
- GNUNET_break (0);
- return NULL;
+ god->claimed = true;
}
- GNUNET_buffer_write_path (&buf,
- "/orders");
- GNUNET_buffer_write_path (&buf,
- order_id);
- if ( (NULL != claim_token) &&
- (! GNUNET_is_zero (claim_token)) )
+ else
{
- /* 'token=' for human readability */
- GNUNET_buffer_write_str (&buf,
- "?token=");
- GNUNET_buffer_write_data_encoded (&buf,
- (char *) claim_token,
- sizeof (*claim_token));
- num_qp++;
- }
+ struct TALER_ClaimTokenP db_claim_token;
+ struct TALER_MerchantPostDataHashP unused;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->lookup_order (
+ TMH_db->cls,
+ god->hc->instance->settings.id,
+ god->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 (0);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_order");
+ return;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Unknown order id given: `%s'\n",
+ god->order_id);
+ phase_fail (god,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
+ god->order_id);
+ return;
+ }
+ /* Note: when "!ord.requireClaimToken" and the client does not provide
+ a claim token (all zeros!), then token_match==TRUE below: */
+ god->token_match
+ = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
+ (0 == GNUNET_memcmp (&db_claim_token,
+ &god->claim_token));
+ } /* end unclaimed order logic */
+ god->phase++;
+}
- 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)
+/**
+ * Parse contract terms.
+ *
+ * @param[in,out] god request context
+ */
+static void
+phase_parse_contract (struct GetOrderData *god)
+{
+ struct GNUNET_JSON_Specification espec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &god->contract_total),
+ TALER_JSON_spec_web_url ("merchant_base_url",
+ &god->merchant_base_url),
+ GNUNET_JSON_spec_mark_optional (
+ /* this one does NOT have to be a Web URL! */
+ GNUNET_JSON_spec_string ("fulfillment_url",
+ &god->fulfillment_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("public_reorder_url",
+ &god->public_reorder_url),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ const char *ename;
+ unsigned int eline;
+
+ GNUNET_assert (NULL != god->contract_terms);
+ if (god->contract_parsed)
+ return; /* not sure this is possible... */
+
+ res = GNUNET_JSON_parse (god->contract_terms,
+ espec,
+ &ename,
+ &eline);
+ if (GNUNET_OK != res)
{
- 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 (*h_contract));
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse contract %s in DB at field %s\n",
+ god->order_id,
+ ename);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ god->order_id);
+ return;
}
-
- return GNUNET_buffer_reap_str (&buf);
+ god->contract_parsed = true;
+ god->phase++;
}
-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)
+/**
+ * Check that this order is unclaimed or claimed by
+ * this client.
+ *
+ * @param[in,out] god request context
+ */
+static void
+phase_check_client_access (struct GetOrderData *god)
{
- struct GNUNET_Buffer buf;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Token match: %d, contract_available: %d, contract match: %d, claimed: %d\n",
+ god->token_match,
+ god->contract_available,
+ god->contract_match,
+ god->claimed);
- GNUNET_assert (NULL != instance_id);
- GNUNET_assert (NULL != order_id);
- if (GNUNET_OK !=
- TMH_taler_uri_by_connection (con,
- "pay",
- instance_id,
- &buf))
+ if (god->claim_token_provided && ! god->token_match)
{
- GNUNET_break (0);
- return NULL;
+ /* Authentication provided but wrong. */
+ GNUNET_break_op (0);
+ phase_fail (god,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
+ "authentication with claim token provided but wrong");
+ return;
}
- GNUNET_buffer_write_path (&buf,
- order_id);
- GNUNET_buffer_write_path (&buf,
- (NULL == session_id)
- ? ""
- : session_id);
- if ( (NULL != claim_token) &&
- (! GNUNET_is_zero (claim_token)))
+
+ if (god->h_contract_provided && ! god->contract_match)
{
- /* Just 'c=' because this goes into QR
- codes, so this is more compact. */
- GNUNET_buffer_write_str (&buf,
- "?c=");
- GNUNET_buffer_write_data_encoded (&buf,
- (char *) claim_token,
- sizeof (struct TALER_ClaimTokenP));
+ /* Authentication provided but wrong. */
+ GNUNET_break_op (0);
+ phase_fail (god,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_HASH,
+ NULL);
+ return;
}
- return GNUNET_buffer_reap_str (&buf);
+ if (! (god->token_match ||
+ god->contract_match) )
+ {
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Neither claim token nor contract matched\n");
+ /* Client has no rights to this order */
+ if (NULL == god->public_reorder_url)
+ {
+ /* We cannot give the client a new order, just fail */
+ if (! GNUNET_is_zero (&god->h_contract_terms))
+ {
+ GNUNET_break_op (0);
+ phase_fail (god,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
+ NULL);
+ return;
+ }
+ GNUNET_break_op (0);
+ phase_fail (god,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
+ "no 'public_reorder_url'");
+ return;
+ }
+ /* We have a fulfillment URL, redirect the client there, maybe
+ the frontend can generate a fresh order for this new customer */
+ 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;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Contract claimed, redirecting to fulfillment page for order %s\n",
+ god->order_id);
+ reply = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ if (NULL == reply)
+ {
+ GNUNET_break (0);
+ phase_end (god,
+ MHD_NO);
+ return;
+ }
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (reply,
+ MHD_HTTP_HEADER_LOCATION,
+ god->public_reorder_url));
+ ret = MHD_queue_response (god->sc.con,
+ MHD_HTTP_FOUND,
+ reply);
+ MHD_destroy_response (reply);
+ phase_end (god,
+ ret);
+ return;
+ }
+ /* Need to generate JSON reply */
+ phase_end (god,
+ TALER_MHD_REPLY_JSON_PACK (
+ god->sc.con,
+ MHD_HTTP_ACCEPTED,
+ GNUNET_JSON_pack_string ("public_reorder_url",
+ god->public_reorder_url)));
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Claim token or contract matched\n");
+ god->phase++;
}
@@ -565,9 +905,9 @@ get_order_summary (const struct GetOrderData *god)
* @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
+ * @return true to exit due to suspension
*/
-static MHD_RESULT
+static bool
send_pay_request (struct GetOrderData *god,
const char *already_paid_order_id)
{
@@ -584,7 +924,7 @@ send_pay_request (struct GetOrderData *god,
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Suspending request: long polling for payment\n");
suspend_god (god);
- return MHD_YES;
+ return true;
}
/* Check if resource_id has been paid for in the same session
@@ -592,27 +932,30 @@ send_pay_request (struct GetOrderData *god,
*/
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Sending payment request\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);
+ 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 ( (NULL == taler_pay_uri) ||
(NULL == order_status_url) )
{
GNUNET_break_op (0);
GNUNET_free (taler_pay_uri);
GNUNET_free (order_status_url);
- return TALER_MHD_reply_with_error (god->sc.con,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
- "host");
+ phase_fail (god,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
+ "host");
+ return false;
}
if (god->generate_html)
{
@@ -631,7 +974,9 @@ send_pay_request (struct GetOrderData *god,
if (NULL == reply)
{
GNUNET_break (0);
- return MHD_NO;
+ phase_end (god,
+ MHD_NO);
+ return false;
}
GNUNET_break (MHD_YES ==
MHD_add_response_header (reply,
@@ -644,7 +989,9 @@ send_pay_request (struct GetOrderData *god,
MHD_HTTP_FOUND,
reply);
MHD_destroy_response (reply);
- return ret;
+ phase_end (god,
+ ret);
+ return false;
}
}
@@ -655,7 +1002,9 @@ send_pay_request (struct GetOrderData *god,
if (NULL == qr)
{
GNUNET_break (0);
- return MHD_NO;
+ phase_end (god,
+ MHD_NO);
+ return false;
}
{
enum GNUNET_GenericReturnValue res;
@@ -670,12 +1019,13 @@ send_pay_request (struct GetOrderData *god,
qr),
GNUNET_JSON_pack_string ("order_summary",
get_order_summary (god)));
- res = TALER_TEMPLATING_reply (god->sc.con,
- MHD_HTTP_PAYMENT_REQUIRED,
- "request_payment",
- god->hc->instance->settings.id,
- taler_pay_uri,
- context);
+ res = TALER_TEMPLATING_reply (
+ god->sc.con,
+ MHD_HTTP_PAYMENT_REQUIRED,
+ "request_payment",
+ god->hc->instance->settings.id,
+ taler_pay_uri,
+ context);
if (GNUNET_SYSERR == res)
{
GNUNET_break (0);
@@ -706,7 +1056,151 @@ send_pay_request (struct GetOrderData *god,
}
GNUNET_free (taler_pay_uri);
GNUNET_free (order_status_url);
- return ret;
+ phase_end (god,
+ ret);
+ return false;
+}
+
+
+/**
+ * Check if the client already paid for an equivalent
+ * order under this session, and if so redirect to
+ * that order.
+ *
+ * @param[in,out] god request context
+ * @return true to exit due to suspension
+ */
+static bool
+phase_redirect_to_paid_order (struct GetOrderData *god)
+{
+ if ( (NULL != god->session_id) &&
+ (NULL != god->fulfillment_url) )
+ {
+ /* Check if client paid for this fulfillment article
+ already within this session, but using a different
+ order ID. If so, redirect the client to the order
+ it already paid. Allows, for example, the case
+ where a mobile phone pays for a browser's session,
+ where the mobile phone has a different order
+ ID (because it purchased the article earlier)
+ than the one that the browser is waiting for. */
+ char *already_paid_order_id = NULL;
+ enum GNUNET_DB_QueryStatus qs;
+
+ 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,
+ god->hc->instance->settings.id,
+ god->fulfillment_url,
+ god->session_id,
+ TALER_EXCHANGE_YNA_NO !=
+ god->allow_refunded_for_repurchase,
+ &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);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "order by fulfillment");
+ return false;
+ }
+ if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
+ (0 != strcmp (god->order_id,
+ already_paid_order_id)) )
+ {
+ bool ret;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sending pay request for order %s (already paid: %s)\n",
+ god->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 (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ GNUNET_free (already_paid_order_id);
+ }
+ god->phase++;
+ return false;
+}
+
+
+/**
+ * Check if order is unclaimed, and if so request
+ * payment.
+ *
+ * @param[in,out] god request context
+ * @return true to exit due to suspension
+ */
+static bool
+phase_check_claimed (struct GetOrderData *god)
+{
+ if (god->claimed)
+ {
+ god->phase++;
+ return false;
+ }
+ /* Order is unclaimed, no need to check for payments or even
+ refunds, simply always generate payment request */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order unclaimed, sending pay request for order %s\n",
+ god->order_id);
+ return send_pay_request (god,
+ NULL);
+}
+
+
+/**
+ * Check if the order has been paid, and if not
+ * request payment.
+ *
+ * @param[in,out] god request context
+ * @return true to exit due to suspension
+ */
+static bool
+phase_check_paid (struct GetOrderData *god)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_PrivateContractHashP h_contract;
+ bool paid;
+
+ qs = TMH_db->lookup_order_status (
+ TMH_db->cls,
+ god->hc->instance->settings.id,
+ god->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);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_order_status");
+ return false;
+ }
+ GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ if (! paid)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order claimed but unpaid, sending pay request for order %s\n",
+ god->order_id);
+ return send_pay_request (god,
+ NULL);
+ }
+ god->phase++;
+ return false;
}
@@ -773,46 +1267,275 @@ process_refunds_cb (void *cls,
/**
- * Clean up the session state for a GET /orders/$ID request.
+ * Check if the order has been refunded.
*
- * @param cls must be a `struct GetOrderData *`
+ * @param[in,out] god request context
+ * @return true to exit due to suspension
*/
-static void
-god_cleanup (void *cls)
+static bool
+phase_check_refunded (struct GetOrderData *god)
{
- struct GetOrderData *god = cls;
+ enum GNUNET_DB_QueryStatus qs;
- if (NULL != god->contract_terms)
+ if ( (god->sc.awaiting_refund) &&
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&god->contract_total,
+ &god->sc.refund_expected)) )
{
- json_decref (god->contract_terms);
- god->contract_terms = NULL;
+ GNUNET_break (0);
+ phase_fail (god,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ god->contract_total.currency);
+ return false;
}
- if (NULL != god->refund_eh)
+
+ /* 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_set_zero (god->contract_total.currency,
+ &god->refund_amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (god->contract_total.currency,
+ &god->refund_taken));
+ qs = TMH_db->lookup_refunds_detailed (
+ TMH_db->cls,
+ god->hc->instance->settings.id,
+ &god->h_contract_terms,
+ &process_refunds_cb,
+ god);
+ if (0 > qs)
{
- TMH_db->event_listen_cancel (god->refund_eh);
- god->refund_eh = NULL;
+ GNUNET_break (0);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_refunds_detailed");
+ return false;
}
- if (NULL != god->pay_eh)
+ if (god->bad_refund_currency_in_db)
{
- TMH_db->event_listen_cancel (god->pay_eh);
- god->pay_eh = NULL;
+ GNUNET_break (0);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "currency mix-up between contract price and refunds in database");
+ return false;
+ }
+ 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_pending) ) )
+ {
+ /* 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 ( (! GNUNET_TIME_relative_is_zero (remaining)) &&
+ (! god->generate_html) )
+ {
+ /* 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 true;
+ }
+ }
+ god->phase++;
+ return false;
+}
+
+
+/**
+ * Create a taler://refund/ URI for the given @a con and @a order_id
+ * and @a instance_id.
+ *
+ * @param merchant_base_url URL to take host and path from;
+ * we cannot take it from the MHD connection as a browser
+ * may have changed 'http' to 'https' and we MUST be consistent
+ * with what the merchant's frontend used initially
+ * @param order_id the order id
+ * @return corresponding taler://refund/ URI, or NULL on missing "host"
+ */
+static char *
+make_taler_refund_uri (const char *merchant_base_url,
+ const char *order_id)
+{
+ struct GNUNET_Buffer buf = { 0 };
+ char *url;
+ struct GNUNET_Uri uri;
+
+ url = GNUNET_strdup (merchant_base_url);
+ if (-1 == GNUNET_uri_parse (&uri,
+ url))
+ {
+ GNUNET_break (0);
+ GNUNET_free (url);
+ return NULL;
+ }
+ GNUNET_assert (NULL != order_id);
+ GNUNET_buffer_write_str (&buf,
+ "taler");
+ if (0 == strcasecmp ("http",
+ uri.scheme))
+ GNUNET_buffer_write_str (&buf,
+ "+http");
+ GNUNET_buffer_write_str (&buf,
+ "://refund/");
+ GNUNET_buffer_write_str (&buf,
+ uri.host);
+ if (0 != uri.port)
+ GNUNET_buffer_write_fstr (&buf,
+ ":%u",
+ (unsigned int) uri.port);
+ if (NULL != uri.path)
+ GNUNET_buffer_write_path (&buf,
+ uri.path);
+ GNUNET_buffer_write_path (&buf,
+ order_id);
+ GNUNET_buffer_write_path (&buf,
+ ""); // Trailing slash
+ GNUNET_free (url);
+ return GNUNET_buffer_reap_str (&buf);
+}
+
+
+/**
+ * Generate the order status response.
+ *
+ * @param[in,out] god request context
+ */
+static void
+phase_return_status (struct GetOrderData *god)
+{
+ /* All operations done, build final response */
+ if (! god->generate_html)
+ {
+ phase_end (god,
+ TALER_MHD_REPLY_JSON_PACK (
+ god->sc.con,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("fulfillment_url",
+ god->fulfillment_url)),
+ GNUNET_JSON_pack_bool ("refunded",
+ god->refunded),
+ GNUNET_JSON_pack_bool ("refund_pending",
+ god->refund_pending),
+ TALER_JSON_pack_amount ("refund_taken",
+ &god->refund_taken),
+ TALER_JSON_pack_amount ("refund_amount",
+ &god->refund_amount)));
+ return;
+ }
+
+ if (god->refund_pending)
+ {
+ char *qr;
+ char *uri;
+
+ GNUNET_assert (NULL != god->contract_terms);
+ uri = make_taler_refund_uri (god->merchant_base_url,
+ god->order_id);
+ if (NULL == uri)
+ {
+ GNUNET_break (0);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ "refund URI");
+ return;
+ }
+ qr = TMH_create_qrcode (uri);
+ if (NULL == qr)
+ {
+ GNUNET_break (0);
+ GNUNET_free (uri);
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ "qr code");
+ return;
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ json_t *context;
+
+ context = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("order_summary",
+ get_order_summary (god)),
+ TALER_JSON_pack_amount ("refund_amount",
+ &god->refund_amount),
+ TALER_JSON_pack_amount ("refund_taken",
+ &god->refund_taken),
+ GNUNET_JSON_pack_string ("taler_refund_uri",
+ uri),
+ GNUNET_JSON_pack_string ("taler_refund_qrcode_svg",
+ qr));
+ res = TALER_TEMPLATING_reply (
+ god->sc.con,
+ MHD_HTTP_OK,
+ "offer_refund",
+ god->hc->instance->settings.id,
+ uri,
+ context);
+ GNUNET_break (GNUNET_OK == res);
+ json_decref (context);
+ phase_end (god,
+ (GNUNET_SYSERR == res)
+ ? MHD_NO
+ : MHD_YES);
+ }
+ GNUNET_free (uri);
+ GNUNET_free (qr);
+ return;
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ json_t *context;
+
+ context = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_incref ("contract_terms",
+ god->contract_terms),
+ GNUNET_JSON_pack_string ("order_summary",
+ get_order_summary (god)),
+ TALER_JSON_pack_amount ("refund_amount",
+ &god->refund_amount),
+ TALER_JSON_pack_amount ("refund_taken",
+ &god->refund_taken));
+ res = TALER_TEMPLATING_reply (
+ god->sc.con,
+ MHD_HTTP_OK,
+ "show_order_details",
+ god->hc->instance->settings.id,
+ NULL,
+ context);
+ GNUNET_break (GNUNET_OK == res);
+ json_decref (context);
+ phase_end (god,
+ (GNUNET_SYSERR == res)
+ ? MHD_NO
+ : MHD_YES);
}
- GNUNET_free (god);
}
-// FIXME: this function should probably be refactored...
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;
(void) rh;
if (NULL == god)
@@ -822,8 +1545,9 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
hc->cc = &god_cleanup;
god->sc.con = connection;
god->hc = hc;
- god->order_id = order_id;
- god->generate_html = TMH_MHD_test_html_desired (connection);
+ god->order_id = hc->infix;
+ god->generate_html
+ = TMH_MHD_test_html_desired (connection);
/* first-time initialization / sanity checks */
TALER_MHD_parse_request_arg_auto (connection,
@@ -890,65 +1614,9 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
min_refund);
}
}
-
TALER_MHD_parse_request_timeout (connection,
&god->sc.long_poll_timeout);
- /* If HTML is requested, we never actually long poll. Makes no sense */
- if ( (! god->generate_html) &&
- (GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout)) )
- {
- if (god->sc.awaiting_refund ||
- god->sc.awaiting_refund_obtained)
- {
- struct TMH_OrderPayEventP refund_eh = {
- .header.size = htons (sizeof (refund_eh)),
- .header.type = htons (god->sc.awaiting_refund_obtained
- ? TALER_DBEVENT_MERCHANT_REFUND_OBTAINED
- : TALER_DBEVENT_MERCHANT_ORDER_REFUND),
- .merchant_pub = hc->instance->merchant_pub
- };
-
- GNUNET_CRYPTO_hash (god->order_id,
- strlen (god->order_id),
- &refund_eh.h_order_id);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Subscribing %p to refunds on %s\n",
- god,
- god->order_id);
- god->refund_eh
- = TMH_db->event_listen (
- TMH_db->cls,
- &refund_eh.header,
- GNUNET_TIME_absolute_get_remaining (
- god->sc.long_poll_timeout),
- &resume_by_event,
- god);
- }
- {
- struct TMH_OrderPayEventP pay_eh = {
- .header.size = htons (sizeof (pay_eh)),
- .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
- .merchant_pub = hc->instance->merchant_pub
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Subscribing to payments on %s\n",
- god->order_id);
- GNUNET_CRYPTO_hash (god->order_id,
- strlen (god->order_id),
- &pay_eh.h_order_id);
- god->pay_eh
- = TMH_db->event_listen (
- TMH_db->cls,
- &pay_eh.header,
- GNUNET_TIME_absolute_get_remaining (
- god->sc.long_poll_timeout),
- &resume_by_event,
- god);
- }
- } /* end of do long-polling / no HTML */
-
- } /* end of first-time initialization / sanity checks */
+ }
if (GNUNET_SYSERR == god->suspended)
return MHD_NO; /* we are in shutdown */
@@ -960,530 +1628,50 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
god);
}
- /* Convert order_id to h_contract_terms */
- TMH_db->preflight (TMH_db->cls);
- if (NULL == god->contract_terms)
- {
- uint64_t order_serial;
- struct TALER_ClaimTokenP db_claim_token;
-
- qs = TMH_db->lookup_contract_terms (TMH_db->cls,
- hc->instance->settings.id,
- order_id,
- &god->contract_terms,
- &order_serial,
- &db_claim_token);
- 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 (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_contract_terms");
- }
- /* Note: when "!ord.requireClaimToken" and the client does not provide
- a claim token (all zeros!), then token_match==TRUE below: */
- token_match = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- && (0 == GNUNET_memcmp (&db_claim_token,
- &god->claim_token));
- }
-
- /* Check if client provided the right hash code of the contract terms */
- if (NULL != god->contract_terms)
- {
- contract_available = true;
- if (GNUNET_YES ==
- GNUNET_is_zero (&god->h_contract_terms))
- {
- if (GNUNET_OK !=
- TALER_JSON_contract_hash (god->contract_terms,
- &god->h_contract_terms))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
- "contract terms");
- }
- }
- else
- {
- struct TALER_PrivateContractHashP h;
-
- if (GNUNET_OK !=
- TALER_JSON_contract_hash (god->contract_terms,
- &h))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
- "contract terms");
- }
- contract_match = (0 ==
- GNUNET_memcmp (&h,
- &god->h_contract_terms));
- if (! contract_match)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
- NULL);
- }
- }
- }
-
- if (contract_available)
- {
- god->claimed = true;
- }
- else
- {
- struct TALER_ClaimTokenP db_claim_token;
- struct TALER_MerchantPostDataHashP 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 (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_order");
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- 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_MERCHANT_GENERIC_ORDER_UNKNOWN,
- order_id);
- }
- /* Note: when "!ord.requireClaimToken" and the client does not provide
- a claim token (all zeros!), then token_match==TRUE below: */
- token_match = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
- (0 == GNUNET_memcmp (&db_claim_token,
- &god->claim_token));
- } /* end unclaimed order logic */
-
- GNUNET_assert (NULL != god->contract_terms);
- if (! god->contract_parsed)
- {
- struct GNUNET_JSON_Specification espec[] = {
- TALER_JSON_spec_amount_any ("amount",
- &god->contract_total),
- TALER_JSON_spec_web_url ("merchant_base_url",
- &god->merchant_base_url),
- GNUNET_JSON_spec_mark_optional (
- /* this one does NOT have to be a Web URL! */
- GNUNET_JSON_spec_string ("fulfillment_url",
- &god->fulfillment_url),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_web_url ("public_reorder_url",
- &god->public_reorder_url),
- NULL),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
- const char *ename;
- unsigned int eline;
-
- res = GNUNET_JSON_parse (god->contract_terms,
- espec,
- &ename,
- &eline);
- if (GNUNET_OK != res)
- {
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse contract %s in DB at field %s\n",
- order_id,
- ename);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
- order_id);
- }
- god->contract_parsed = true;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Token match: %d, contract_available: %d, contract match: %d, claimed: %d\n",
- token_match,
- contract_available,
- contract_match,
- god->claimed);
-
- if (god->claim_token_provided && ! token_match)
- {
- /* Authentication provided but wrong. */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
- "authentication with claim token provided but wrong");
- }
-
- if (god->h_contract_provided && ! contract_match)
- {
- /* Authentication provided but wrong. */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_HASH,
- NULL);
- }
-
- if (! (token_match ||
- contract_match) )
- {
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Neither claim token nor contract matched\n");
- /* Client has no rights to this order */
- if (NULL == god->public_reorder_url)
- {
- /* We cannot give the client a new order, just fail */
- if (! GNUNET_is_zero (&god->h_contract_terms))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
- NULL);
- }
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
- "no 'public_reorder_url'");
- }
- /* We have a fulfillment URL, redirect the client there, maybe
- the frontend can generate a fresh order for this new customer */
- 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;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Contract claimed, redirecting to fulfillment page for order %s\n",
- order_id);
- 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->public_reorder_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,
- GNUNET_JSON_pack_string ("public_reorder_url",
- god->public_reorder_url));
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Claim token or contract matched\n");
-
- if ( (NULL != god->session_id) &&
- (NULL != god->fulfillment_url) )
- {
- /* Check if client paid for this fulfillment article
- already within this session, but using a different
- order ID. If so, redirect the client to the order
- it already paid. Allows, for example, the case
- where a mobile phone pays for a browser's session,
- where the mobile phone has a different order
- ID (because it purchased the article earlier)
- than the one that the browser is waiting for. */
- 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,
- TALER_EXCHANGE_YNA_NO !=
- god->allow_refunded_for_repurchase,
- &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_GENERIC_DB_FETCH_FAILED,
- "order by fulfillment");
- }
- if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
- (0 != strcmp (order_id,
- already_paid_order_id)) )
- {
- MHD_RESULT ret;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "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 (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
- GNUNET_free (already_paid_order_id);
- }
-
- if (! god->claimed)
+ while (1)
{
- /* Order is unclaimed, no need to check for payments or even
- refunds, simply always generate payment request */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Order unclaimed, sending pay request for order %s\n",
- order_id);
- return send_pay_request (god,
- NULL);
- }
-
- {
- /* Check if paid. */
- struct TALER_PrivateContractHashP 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)
+ "Handling request in phase %d\n",
+ (int) god->phase);
+ switch (god->phase)
{
- /* 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_GENERIC_DB_FETCH_FAILED,
- "lookup_order_status");
- }
- GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
- if (! paid)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Order claimed but unpaid, sending pay request for order %s\n",
- order_id);
- return send_pay_request (god,
- NULL);
- }
- }
-
- if ( (god->sc.awaiting_refund) &&
- (GNUNET_OK !=
- TALER_amount_cmp_currency (&god->contract_total,
- &god->sc.refund_expected)) )
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
- god->contract_total.currency);
- }
-
- /* 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_set_zero (god->contract_total.currency,
- &god->refund_amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (god->contract_total.currency,
- &god->refund_taken));
- 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_GENERIC_DB_FETCH_FAILED,
- "lookup_refunds_detailed");
- }
- if (god->bad_refund_currency_in_db)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "currency mix-up between contract price and refunds in database");
- }
- 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_pending) ) )
- {
- /* 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 ( (! GNUNET_TIME_relative_is_zero (remaining)) &&
- (! god->generate_html) )
- {
- /* 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);
+ case GOP_INIT:
+ phase_init (god);
+ break;
+ case GOP_LOOKUP_TERMS:
+ phase_lookup_terms (god);
+ break;
+ case GOP_PARSE_CONTRACT:
+ phase_parse_contract (god);
+ break;
+ case GOP_CHECK_CLIENT_ACCESS:
+ phase_check_client_access (god);
+ break;
+ case GOP_REDIRECT_TO_PAID_ORDER:
+ if (phase_redirect_to_paid_order (god))
+ return MHD_YES;
+ break;
+ case GOP_CHECK_CLAIMED:
+ if (phase_check_claimed (god))
+ return MHD_YES;
+ break;
+ case GOP_CHECK_PAID:
+ if (phase_check_paid (god))
+ return MHD_YES;
+ break;
+ case GOP_CHECK_REFUNDED:
+ if (phase_check_refunded (god))
+ return MHD_YES;
+ break;
+ case GOP_RETURN_STATUS:
+ phase_return_status (god);
+ break;
+ case GOP_RETURN_MHD_YES:
return MHD_YES;
- }
- }
-
- /* All operations done, build final response */
- if (god->generate_html)
- {
- enum GNUNET_GenericReturnValue res;
-
- if (god->refund_pending)
- {
- char *qr;
- char *uri;
-
- GNUNET_assert (NULL != god->contract_terms);
- uri = make_taler_refund_uri (god->merchant_base_url,
- order_id);
- if (NULL == uri)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (god->sc.con,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_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_GENERIC_ALLOCATION_FAILURE,
- "qr code");
- }
- {
- json_t *context;
-
- context = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("order_summary",
- get_order_summary (god)),
- TALER_JSON_pack_amount ("refund_amount",
- &god->refund_amount),
- TALER_JSON_pack_amount ("refund_taken",
- &god->refund_taken),
- GNUNET_JSON_pack_string ("taler_refund_uri",
- uri),
- GNUNET_JSON_pack_string ("taler_refund_qrcode_svg",
- qr));
- res = TALER_TEMPLATING_reply (god->sc.con,
- MHD_HTTP_OK,
- "offer_refund",
- hc->instance->settings.id,
- uri,
- context);
- json_decref (context);
- }
- GNUNET_free (uri);
- GNUNET_free (qr);
- }
- else
- {
- json_t *context;
-
- context = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_object_incref ("contract_terms",
- god->contract_terms),
- GNUNET_JSON_pack_string ("order_summary",
- get_order_summary (god)),
- TALER_JSON_pack_amount ("refund_amount",
- &god->refund_amount),
- TALER_JSON_pack_amount ("refund_taken",
- &god->refund_taken));
- res = TALER_TEMPLATING_reply (god->sc.con,
- MHD_HTTP_OK,
- "show_order_details",
- hc->instance->settings.id,
- NULL,
- context);
- json_decref (context);
- }
- if (GNUNET_SYSERR == res)
- {
- GNUNET_break (0);
+ case GOP_RETURN_MHD_NO:
return MHD_NO;
}
- return MHD_YES;
}
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("fulfillment_url",
- god->fulfillment_url)),
- GNUNET_JSON_pack_bool ("refunded",
- god->refunded),
- GNUNET_JSON_pack_bool ("refund_pending",
- god->refund_pending),
- TALER_JSON_pack_amount ("refund_taken",
- &god->refund_taken),
- TALER_JSON_pack_amount ("refund_amount",
- &god->refund_amount));
}
diff --git a/src/backend/taler-merchant-httpd_get-orders-ID.h b/src/backend/taler-merchant-httpd_get-orders-ID.h
index 97b8525b..49ef5a73 100644
--- a/src/backend/taler-merchant-httpd_get-orders-ID.h
+++ b/src/backend/taler-merchant-httpd_get-orders-ID.h
@@ -32,45 +32,6 @@ TMH_force_wallet_get_order_resume (void);
/**
- * 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);
-
-/**
- * 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 session, may be NULL
- * @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 TALER_PrivateContractHashP *h_contract);
-
-
-/**
* Handle a GET "/orders/$ID" request.
*
* @param rh context of the handler
diff --git a/src/backend/taler-merchant-httpd_helper.c b/src/backend/taler-merchant-httpd_helper.c
index 9261df91..f21b2e48 100644
--- a/src/backend/taler-merchant-httpd_helper.c
+++ b/src/backend/taler-merchant-httpd_helper.c
@@ -980,3 +980,113 @@ TMH_exchange_accounts_by_method (
}
return emc.accounts;
}
+
+
+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 TALER_PrivateContractHashP *h_contract)
+{
+ struct GNUNET_Buffer buf;
+ /* Number of query parameters written so far */
+ unsigned int num_qp = 0;
+
+ GNUNET_assert (NULL != instance_id);
+ GNUNET_assert (NULL != order_id);
+ if (GNUNET_OK !=
+ TMH_base_url_by_connection (con,
+ instance_id,
+ &buf))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_buffer_write_path (&buf,
+ "/orders");
+ GNUNET_buffer_write_path (&buf,
+ order_id);
+ if ( (NULL != claim_token) &&
+ (! GNUNET_is_zero (claim_token)) )
+ {
+ /* 'token=' for human readability */
+ GNUNET_buffer_write_str (&buf,
+ "?token=");
+ GNUNET_buffer_write_data_encoded (&buf,
+ (char *) claim_token,
+ sizeof (*claim_token));
+ 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 (*h_contract));
+ }
+
+ return GNUNET_buffer_reap_str (&buf);
+}
+
+
+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)
+{
+ struct GNUNET_Buffer buf;
+
+ GNUNET_assert (NULL != instance_id);
+ GNUNET_assert (NULL != order_id);
+ if (GNUNET_OK !=
+ TMH_taler_uri_by_connection (con,
+ "pay",
+ instance_id,
+ &buf))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_buffer_write_path (&buf,
+ order_id);
+ GNUNET_buffer_write_path (&buf,
+ (NULL == session_id)
+ ? ""
+ : session_id);
+ if ( (NULL != claim_token) &&
+ (! GNUNET_is_zero (claim_token)))
+ {
+ /* Just 'c=' because this goes into QR
+ codes, so this is more compact. */
+ 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);
+}
diff --git a/src/backend/taler-merchant-httpd_helper.h b/src/backend/taler-merchant-httpd_helper.h
index 827cc03b..6783b9d4 100644
--- a/src/backend/taler-merchant-httpd_helper.h
+++ b/src/backend/taler-merchant-httpd_helper.h
@@ -193,6 +193,45 @@ TMH_taler_uri_by_connection (struct MHD_Connection *connection,
/**
+ * 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);
+
+/**
+ * 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 session, may be NULL
+ * @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 TALER_PrivateContractHashP *h_contract);
+
+
+/**
* Put data from an exchange's HTTP response into
* a JSON reply
*
diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h
index 011f96a8..56063a0f 100644
--- a/src/include/taler_merchant_testing_lib.h
+++ b/src/include/taler_merchant_testing_lib.h
@@ -644,9 +644,10 @@ TALER_TESTING_cmd_poll_orders_start (const char *label,
* @param poll_start_reference reference to the #TALER_TESTING_cmd_poll_orders_start command
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_poll_orders_conclude (const char *label,
- unsigned int http_status,
- const char *poll_start_reference);
+TALER_TESTING_cmd_poll_orders_conclude (
+ const char *label,
+ unsigned int http_status,
+ const char *poll_start_reference);
/**
@@ -662,13 +663,41 @@ TALER_TESTING_cmd_poll_orders_conclude (const char *label,
* @param http_status expected HTTP response code for the request.
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_wallet_get_order (const char *label,
- const char *merchant_url,
- const char *order_reference,
- bool paid,
- bool refunded,
- bool refund_pending,
- unsigned int http_status);
+TALER_TESTING_cmd_wallet_get_order (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ bool paid,
+ bool refunded,
+ bool refund_pending,
+ unsigned int http_status);
+
+
+/**
+ * Define a GET /orders/$ORDER_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ * serve the request.
+ * @param order_reference reference to a command that created an order.
+ * @param session_id session ID to check for
+ * @param paid whether the order has been paid for or not.
+ * @param refunded whether the order has been refunded.
+ * @param refund_pending whether the order has refunds that haven't been obtained.
+ * @param repurchase_order_ref command of a paid equivalent order the merchant should be refering us to, or NULL
+ * @param http_status expected HTTP response code for the request.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wallet_get_order2 (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ const char *session_id,
+ bool paid,
+ bool refunded,
+ bool refund_pending,
+ const char *repurchase_order_ref,
+ unsigned int http_status);
/**
diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c
index 48e8cec4..bfa534d5 100644
--- a/src/testing/test_merchant_api.c
+++ b/src/testing/test_merchant_api.c
@@ -1514,6 +1514,14 @@ run (void *cls,
"EUR:1.00",
"EUR:0.99",
"repurchase-session"),
+ TALER_TESTING_cmd_wallet_get_order (
+ "repurchase-wallet-check-primary-order",
+ merchant_url,
+ "post-order-repurchase-original",
+ true,
+ false,
+ false,
+ MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_get_order3 (
"repurchase-check-primary-payment",
merchant_url,
@@ -1556,6 +1564,26 @@ run (void *cls,
"repurchase-session",
"post-order-repurchase-original",
MHD_HTTP_OK),
+ TALER_TESTING_cmd_wallet_get_order2 (
+ "repurchase-wallet-check-order-secondary",
+ merchant_url,
+ "post-order-repurchase-secondary",
+ "repurchase-session",
+ false,
+ false,
+ false,
+ "post-order-repurchase-original",
+ MHD_HTTP_PAYMENT_REQUIRED),
+ TALER_TESTING_cmd_wallet_get_order2 (
+ "repurchase-wallet-check-order-secondary-bad-session",
+ merchant_url,
+ "post-order-repurchase-secondary",
+ "wrong-session",
+ false,
+ false,
+ false,
+ NULL,
+ MHD_HTTP_PAYMENT_REQUIRED),
TALER_TESTING_cmd_merchant_order_refund (
"refund-repurchased",
merchant_url,
@@ -1563,8 +1591,30 @@ run (void *cls,
"repurchase-original",
"EUR:1.0",
MHD_HTTP_OK),
+ TALER_TESTING_cmd_wallet_get_order2 (
+ "repurchase-wallet-check-primary-order-refunded-no-session",
+ merchant_url,
+ "post-order-repurchase-original",
+ NULL,
+ true,
+ true,
+ true,
+ "post-order-repurchase-original",
+ MHD_HTTP_OK),
+#ifdef XFAIL
+ TALER_TESTING_cmd_wallet_get_order2 (
+ "repurchase-wallet-check-primary-order-refunded",
+ merchant_url,
+ "post-order-repurchase-original",
+ "repurchase-session",
+ true,
+ true,
+ true,
+ "post-order-repurchase-original",
+ MHD_HTTP_OK),
+#endif
TALER_TESTING_cmd_merchant_get_order3 (
- "repurchase-check-secondary-payment",
+ "repurchase-check-refunded",
merchant_url,
"post-order-repurchase-secondary",
TALER_MERCHANT_OSC_CLAIMED,
@@ -1879,7 +1929,7 @@ run (void *cls,
merchant_url,
"product-2",
MHD_HTTP_CONFLICT),
-#if 0
+#if 1
TALER_TESTING_cmd_batch ("pay",
pay),
TALER_TESTING_cmd_batch ("double-spending",
diff --git a/src/testing/testing_api_cmd_wallet_get_order.c b/src/testing/testing_api_cmd_wallet_get_order.c
index fd4a914d..d55ff0a7 100644
--- a/src/testing/testing_api_cmd_wallet_get_order.c
+++ b/src/testing/testing_api_cmd_wallet_get_order.c
@@ -59,6 +59,18 @@ struct WalletGetOrderState
const char *order_reference;
/**
+ * Reference to a command that created a paid
+ * equivalent order that we expect to be referred
+ * to during repurchase detection, or NULL.
+ */
+ const char *repurchase_order_ref;
+
+ /**
+ * Session Id the order needs to be bound to.
+ */
+ const char *session_id;
+
+ /**
* Whether the order was paid or not.
*/
bool paid;
@@ -125,6 +137,31 @@ wallet_get_order_cb (
const char *order_id;
const struct TALER_ClaimTokenP *claim_token;
+ if (NULL != gos->repurchase_order_ref)
+ {
+ const struct TALER_TESTING_Command *rep_cmd;
+ const char *rep_id;
+ const char *ri;
+
+ rep_cmd = TALER_TESTING_interpreter_lookup_command (
+ gos->is,
+ gos->repurchase_order_ref);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_order_id (rep_cmd,
+ &rep_id))
+ {
+ TALER_TESTING_FAIL (gos->is);
+ }
+ ri = owgr->details.payment_required.already_paid_order_id;
+ if ( (NULL == ri) ||
+ (0 !=
+ strcmp (ri,
+ rep_id)) )
+ {
+ TALER_TESTING_FAIL (gos->is);
+ }
+ }
+
if (GNUNET_OK !=
TALER_MERCHANT_parse_pay_uri (
owgr->details.payment_required.taler_pay_uri,
@@ -243,7 +280,7 @@ wallet_get_order_run (void *cls,
order_id,
h_contract,
GNUNET_TIME_UNIT_ZERO,
- NULL,
+ gos->session_id,
NULL,
false,
&wallet_get_order_cb,
@@ -274,13 +311,48 @@ wallet_get_order_cleanup (void *cls,
struct TALER_TESTING_Command
-TALER_TESTING_cmd_wallet_get_order (const char *label,
- const char *merchant_url,
- const char *order_reference,
- bool paid,
- bool refunded,
- bool refund_pending,
- unsigned int http_status)
+TALER_TESTING_cmd_wallet_get_order (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ bool paid,
+ bool refunded,
+ bool refund_pending,
+ unsigned int http_status)
+{
+ struct WalletGetOrderState *gos;
+
+ gos = GNUNET_new (struct WalletGetOrderState);
+ gos->merchant_url = merchant_url;
+ gos->order_reference = order_reference;
+ gos->http_status = http_status;
+ gos->paid = paid;
+ gos->refunded = refunded;
+ gos->refund_pending = refund_pending;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = gos,
+ .label = label,
+ .run = &wallet_get_order_run,
+ .cleanup = &wallet_get_order_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wallet_get_order2 (
+ const char *label,
+ const char *merchant_url,
+ const char *order_reference,
+ const char *session_id,
+ bool paid,
+ bool refunded,
+ bool refund_pending,
+ const char *repurchase_order_ref,
+ unsigned int http_status)
{
struct WalletGetOrderState *gos;
@@ -289,8 +361,10 @@ TALER_TESTING_cmd_wallet_get_order (const char *label,
gos->order_reference = order_reference;
gos->http_status = http_status;
gos->paid = paid;
+ gos->session_id = session_id;
gos->refunded = refunded;
gos->refund_pending = refund_pending;
+ gos->repurchase_order_ref = repurchase_order_ref;
{
struct TALER_TESTING_Command cmd = {
.cls = gos,