summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_private-get-orders-ID.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/taler-merchant-httpd_private-get-orders-ID.c')
-rw-r--r--src/backend/taler-merchant-httpd_private-get-orders-ID.c2058
1 files changed, 1056 insertions, 1002 deletions
diff --git a/src/backend/taler-merchant-httpd_private-get-orders-ID.c b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
index 828d1d65..98653997 100644
--- a/src/backend/taler-merchant-httpd_private-get-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2017-2022 Taler Systems SA
+ (C) 2017-2024 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
@@ -31,13 +31,6 @@
/**
- * How long do we wait on the exchange?
- */
-#define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_SECONDS, 30)
-
-
-/**
* Data structure we keep for a check payment request.
*/
struct GetOrderRequestContext;
@@ -66,16 +59,6 @@ struct TransferQuery
char *exchange_url;
/**
- * Handle to query exchange about deposit status.
- */
- struct TALER_EXCHANGE_DepositGetHandle *dgh;
-
- /**
- * Handle for ongoing exchange operation.
- */
- struct TMH_EXCHANGES_FindOperation *fo;
-
- /**
* Overall request this TQ belongs with.
*/
struct GetOrderRequestContext *gorc;
@@ -109,12 +92,101 @@ struct TransferQuery
/**
+ * Phases of order processing.
+ */
+enum GetOrderPhase
+{
+ /**
+ * Initialization.
+ */
+ GOP_INIT = 0,
+
+ /**
+ * Obtain contract terms from database.
+ */
+ GOP_FETCH_CONTRACT = 1,
+
+ /**
+ * Parse the contract terms.
+ */
+ GOP_PARSE_CONTRACT = 2,
+
+ /**
+ * Check if the contract was fully paid.
+ */
+ GOP_CHECK_PAID = 3,
+
+ /**
+ * Check if the wallet may have purchased an equivalent
+ * order before and we need to redirect the wallet to
+ * an existing paid order.
+ */
+ GOP_CHECK_REPURCHASE = 4,
+
+ /**
+ * Terminate processing of unpaid orders, either by
+ * suspending until payment or by returning the
+ * unpaid order status.
+ */
+ GOP_UNPAID_FINISH = 5,
+
+ /**
+ * Check if the (paid) order was refunded.
+ */
+ GOP_CHECK_REFUNDS = 6,
+
+ /**
+ * Load all deposits associated with the order.
+ */
+ GOP_CHECK_DEPOSITS = 7,
+
+ /**
+ * Check local records for transfers of funds to
+ * the merchant.
+ */
+ GOP_CHECK_LOCAL_TRANSFERS = 8,
+
+ /**
+ * Generate final comprehensive result.
+ */
+ GOP_REPLY_RESULT = 9,
+
+ /**
+ * End with the HTTP status and error code in
+ * wire_hc and wire_ec.
+ */
+ GOP_ERROR = 10,
+
+ /**
+ * We are suspended awaiting payment.
+ */
+ GOP_SUSPENDED_ON_UNPAID = 11,
+
+ /**
+ * Processing is done, return #MHD_YES.
+ */
+ GOP_END_YES = 12,
+
+ /**
+ * Processing is done, return #MHD_NO.
+ */
+ GOP_END_NO = 13
+
+};
+
+
+/**
* Data structure we keep for a check payment request.
*/
struct GetOrderRequestContext
{
/**
+ * Processing phase we are in.
+ */
+ enum GetOrderPhase phase;
+
+ /**
* Entry in the #resume_timeout_heap for this check payment, if we are
* suspended.
*/
@@ -181,16 +253,30 @@ struct GetOrderRequestContext
json_t *contract_terms;
/**
- * Wire details for the payment, to be returned in the reply. NULL
- * if not available.
+ * Claim token of the order.
*/
- json_t *wire_details;
+ struct TALER_ClaimTokenP claim_token;
/**
- * Problems we encountered when looking up Wire details
- * for the payment, to be returned. NULL if not available.
+ * Timestamp from the @e contract_terms.
*/
- json_t *wire_reports;
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Timestamp of the last payment.
+ */
+ struct GNUNET_TIME_Timestamp last_payment;
+
+ /**
+ * Order summary. Pointer into @e contract_terms.
+ */
+ const char *summary;
+
+ /**
+ * Wire details for the payment, to be returned in the reply. NULL
+ * if not available.
+ */
+ json_t *wire_details;
/**
* Details about refunds, NULL if there are no refunds.
@@ -254,6 +340,12 @@ struct GetOrderRequestContext
enum TALER_ErrorCode wire_ec;
/**
+ * Set to YES if refunded orders should be included when
+ * doing repurchase detection.
+ */
+ enum TALER_EXCHANGE_YesNoAll allow_refunded_for_repurchase;
+
+ /**
* HTTP status to return with @e wire_ec, 0 if @e wire_ec is #TALER_EC_NONE.
*/
unsigned int wire_hc;
@@ -274,17 +366,45 @@ struct GetOrderRequestContext
bool refunded;
/**
+ * True if the order was paid.
+ */
+ bool paid;
+
+ /**
+ * True if the paid session in the database matches
+ * our @e session_id.
+ */
+ bool paid_session_matches;
+
+ /**
+ * True if the exchange wired the money to the merchant.
+ */
+ bool wired;
+
+ /**
+ * True if the order remains unclaimed.
+ */
+ bool order_only;
+
+ /**
* Set to true if this payment has been refunded and
* some refunds remain to be picked up by the wallet.
*/
bool refund_pending;
/**
- * Did the client request us to fetch the wire transfer status?
- * If false, we may still return it if it is available.
+ * Set to true if our database (incorrectly) has refunds
+ * in a different currency than the currency of the
+ * original payment for the order.
*/
- bool transfer_status_requested;
+ bool refund_currency_mismatch;
+ /**
+ * Set to true if our database (incorrectly) has deposits
+ * in a different currency than the currency of the
+ * original payment for the order.
+ */
+ bool deposit_currency_mismatch;
};
@@ -317,51 +437,6 @@ TMH_force_gorc_resume (void)
/**
- * Resume processing the request, cancelling all pending asynchronous
- * operations.
- *
- * @param gorc request to resume
- * @param http_status HTTP status to return, 0 to continue with success
- * @param ec error code for the request, #TALER_EC_NONE on success
- */
-static void
-gorc_resume (struct GetOrderRequestContext *gorc,
- unsigned int http_status,
- enum TALER_ErrorCode ec)
-{
- struct TransferQuery *tq;
-
- if (NULL != gorc->tt)
- {
- GNUNET_SCHEDULER_cancel (gorc->tt);
- gorc->tt = NULL;
- }
- while (NULL != (tq = gorc->tq_head))
- {
- if (NULL != tq->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (tq->fo);
- tq->fo = NULL;
- }
- if (NULL != tq->dgh)
- {
- TALER_EXCHANGE_deposits_get_cancel (tq->dgh);
- tq->dgh = NULL;
- }
- }
- gorc->wire_hc = http_status;
- gorc->wire_ec = ec;
- GNUNET_assert (GNUNET_YES == gorc->suspended);
- GNUNET_CONTAINER_DLL_remove (gorc_head,
- gorc_tail,
- gorc);
- gorc->suspended = GNUNET_NO;
- MHD_resume_connection (gorc->sc.con);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
-}
-
-
-/**
* We have received a trigger from the database
* that we should (possibly) resume the request.
*
@@ -379,11 +454,12 @@ resume_by_event (void *cls,
(void) extra;
(void) extra_size;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming request %p by trigger\n",
- gorc);
+ "Resuming request for order %s by trigger\n",
+ gorc->hc->infix);
if (GNUNET_NO == gorc->suspended)
return; /* duplicate event is possible */
gorc->suspended = GNUNET_NO;
+ gorc->phase = GOP_FETCH_CONTRACT;
GNUNET_CONTAINER_DLL_remove (gorc_head,
gorc_tail,
gorc);
@@ -393,353 +469,520 @@ resume_by_event (void *cls,
/**
- * Add a report about trouble obtaining wire transfer data to the reply.
+ * Clean up the session state for a GET /private/order/ID request.
*
- * @param gorc request to add wire report to
- * @param ec error code to add
- * @param coin_pub public key of the affected coin
- * @param exchange_hr details from exchange, NULL if exchange is blameless
+ * @param cls closure, must be a `struct GetOrderRequestContext *`
*/
static void
-gorc_report (struct GetOrderRequestContext *gorc,
- enum TALER_ErrorCode ec,
- struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_EXCHANGE_HttpResponse *exchange_hr)
+gorc_cleanup (void *cls)
{
- if (NULL != exchange_hr)
- GNUNET_assert (0 ==
- json_array_append_new (
- gorc->wire_reports,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_ec (ec),
- TMH_pack_exchange_reply (exchange_hr),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- coin_pub))));
- else
- GNUNET_assert (0 ==
- json_array_append_new (
- gorc->wire_reports,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_ec (ec),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- coin_pub))));
+ struct GetOrderRequestContext *gorc = cls;
+
+ if (NULL != gorc->contract_terms)
+ json_decref (gorc->contract_terms);
+ if (NULL != gorc->wire_details)
+ json_decref (gorc->wire_details);
+ if (NULL != gorc->refund_details)
+ json_decref (gorc->refund_details);
+ if (NULL != gorc->tt)
+ {
+ GNUNET_SCHEDULER_cancel (gorc->tt);
+ gorc->tt = NULL;
+ }
+ if (NULL != gorc->eh)
+ {
+ TMH_db->event_listen_cancel (gorc->eh);
+ gorc->eh = NULL;
+ }
+ if (NULL != gorc->session_eh)
+ {
+ TMH_db->event_listen_cancel (gorc->session_eh);
+ gorc->session_eh = NULL;
+ }
+ GNUNET_free (gorc);
}
/**
- * Timeout trying to get current wire transfer data from the exchange.
- * Clean up and continue.
+ * Processing the request @a gorc is finished, set the
+ * final return value in phase based on @a mret.
*
- * @param cls closure, must be a `struct GetOrderRequestContext *`
+ * @param[in,out] gorc order context to initialize
+ * @param mret MHD HTTP response status to return
*/
static void
-exchange_timeout_cb (void *cls)
+phase_end (struct GetOrderRequestContext *gorc,
+ MHD_RESULT mret)
{
- struct GetOrderRequestContext *gorc = cls;
+ gorc->phase = (MHD_YES == mret)
+ ? GOP_END_YES
+ : GOP_END_NO;
+}
+
+
+/**
+ * Initialize event callbacks for the order processing.
+ *
+ * @param[in,out] gorc order context to initialize
+ */
+static void
+phase_init (struct GetOrderRequestContext *gorc)
+{
+ struct TMH_HandlerContext *hc = gorc->hc;
+ 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
+ };
+
+ if (! GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
+ {
+ gorc->phase++;
+ return;
+ }
+
+ GNUNET_CRYPTO_hash (hc->infix,
+ strlen (hc->infix),
+ &pay_eh.h_order_id);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Subscribing to payment triggers for %p\n",
+ gorc);
+ gorc->eh = TMH_db->event_listen (
+ TMH_db->cls,
+ &pay_eh.header,
+ GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout),
+ &resume_by_event,
+ gorc);
+ if ( (NULL != gorc->session_id) &&
+ (NULL != gorc->fulfillment_url) )
+ {
+ struct TMH_SessionEventP session_eh = {
+ .header.size = htons (sizeof (session_eh)),
+ .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
+ .merchant_pub = hc->instance->merchant_pub
+ };
- gorc->tt = NULL;
- gorc_resume (gorc,
- MHD_HTTP_REQUEST_TIMEOUT,
- TALER_EC_GENERIC_TIMEOUT);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Subscribing to session triggers for %p\n",
+ gorc);
+ GNUNET_CRYPTO_hash (gorc->session_id,
+ strlen (gorc->session_id),
+ &session_eh.h_session_id);
+ GNUNET_CRYPTO_hash (gorc->fulfillment_url,
+ strlen (gorc->fulfillment_url),
+ &session_eh.h_fulfillment_url);
+ gorc->session_eh
+ = TMH_db->event_listen (
+ TMH_db->cls,
+ &session_eh.header,
+ GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout),
+ &resume_by_event,
+ gorc);
+ }
+ gorc->phase++;
}
/**
- * Function called with detailed wire transfer data.
+ * Obtain latest contract terms from the database.
*
- * @param cls closure with a `struct TransferQuery *`
- * @param hr HTTP response data
- * @param dd details about the deposit (NULL on errors)
+ * @param[in,out] gorc order context to update
*/
static void
-deposit_get_cb (void *cls,
- const struct TALER_EXCHANGE_GetDepositResponse *dr)
+phase_fetch_contract (struct GetOrderRequestContext *gorc)
{
- struct TransferQuery *tq = cls;
- struct GetOrderRequestContext *gorc = tq->gorc;
+ struct TMH_HandlerContext *hc = gorc->hc;
+ enum GNUNET_DB_QueryStatus qs;
- GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
- gorc->tq_tail,
- tq);
- switch (dr->hr.http_status)
+ if (NULL != gorc->contract_terms)
{
- case MHD_HTTP_OK:
- {
- enum GNUNET_DB_QueryStatus qs;
+ /* Free memory filled with old contract terms before fetching the latest
+ ones from the DB. Note that we cannot simply skip the database
+ interaction as the contract terms loaded previously might be from an
+ earlier *unclaimed* order state (which we loaded in a previous
+ invocation of this function and we are back here due to long polling)
+ and thus the contract terms could have changed during claiming. Thus,
+ we need to fetch the latest contract terms from the DB again. */
+ json_decref (gorc->contract_terms);
+ gorc->contract_terms = NULL;
+ gorc->fulfillment_url = NULL;
+ gorc->summary = NULL;
+ gorc->order_only = false;
+ }
+ TMH_db->preflight (TMH_db->cls);
+ qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
+ hc->instance->settings.id,
+ hc->infix,
+ gorc->session_id,
+ &gorc->contract_terms,
+ &gorc->order_serial,
+ &gorc->paid,
+ &gorc->wired,
+ &gorc->paid_session_matches,
+ &gorc->claim_token);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "lookup_contract_terms (%s) returned %d\n",
+ hc->infix,
+ (int) qs);
+ 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);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "contract terms"));
+ return;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order %s is %s (%s) according to database\n",
+ hc->infix,
+ gorc->paid ? "paid" : "unpaid",
+ gorc->wired ? "wired" : "unwired");
+ gorc->phase++;
+ return;
+ }
+ GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
+ GNUNET_assert (! gorc->paid);
+ /* No contract, only order, fetch from orders table */
+ gorc->order_only = true;
+ {
+ struct TALER_MerchantPostDataHashP unused;
- qs = TMH_db->insert_deposit_to_transfer (TMH_db->cls,
- tq->deposit_serial,
- &dr->details.success);
- if (qs < 0)
- {
- gorc_report (gorc,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- &tq->coin_pub,
- NULL);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- if (NULL == gorc->tq_head)
- gorc_resume (gorc,
- 0,
- TALER_EC_NONE);
- return;
- }
- /* Compute total amount *wired* */
- if (0 >
- TALER_amount_add (&gorc->deposits_total,
- &gorc->deposits_total,
- &dr->details.success.coin_contribution))
- {
- gorc_report (gorc,
- TALER_EC_MERCHANT_PRIVATE_GET_ORDERS_ID_AMOUNT_ARITHMETIC_FAILURE,
- &tq->coin_pub,
- NULL);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- if (NULL == gorc->tq_head)
- gorc_resume (gorc,
- 0,
- TALER_EC_NONE);
- return;
- }
- if (0 >
- TALER_amount_add (&gorc->deposit_fees_total,
- &gorc->deposit_fees_total,
- &tq->deposit_fee))
- {
- gorc_report (gorc,
- TALER_EC_MERCHANT_PRIVATE_GET_ORDERS_ID_AMOUNT_ARITHMETIC_FAILURE,
- &tq->coin_pub,
- NULL);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- if (NULL == gorc->tq_head)
- gorc_resume (gorc,
- 0,
- TALER_EC_NONE);
- return;
- }
- break;
- }
- case MHD_HTTP_ACCEPTED:
- {
- /* got a 'preliminary' reply from the exchange,
- remember our target UUID */
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Timestamp now;
-
- now = GNUNET_TIME_timestamp_get ();
- qs = TMH_db->account_kyc_set_status (
- TMH_db->cls,
- gorc->hc->instance->settings.id,
- &tq->h_wire,
- tq->exchange_url,
- dr->details.accepted.payment_target_uuid,
- NULL,
- NULL,
- now,
- false);
- if (qs < 0)
- {
- gorc_report (gorc,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- &tq->coin_pub,
- NULL);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- if (NULL == gorc->tq_head)
- gorc_resume (gorc,
- 0,
- TALER_EC_NONE);
- return;
- }
- gorc_report (gorc,
- TALER_EC_NONE,
- &tq->coin_pub,
- &dr->hr);
- break;
- }
- default:
+ /* We need the order for two cases: Either when the contract doesn't exist yet,
+ * or when the order is claimed but unpaid, and we need the claim token. */
+ qs = TMH_db->lookup_order (TMH_db->cls,
+ hc->instance->settings.id,
+ hc->infix,
+ &gorc->claim_token,
+ &unused,
+ &gorc->contract_terms);
+ }
+ 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);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "order"));
+ return;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
+ hc->infix));
+ return;
+ }
+ gorc->phase++;
+}
+
+
+/**
+ * Obtain parse contract terms of the order. Extracts the fulfillment URL,
+ * total amount, summary and timestamp from the contract terms!
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_parse_contract (struct GetOrderRequestContext *gorc)
+{
+ struct TMH_HandlerContext *hc = gorc->hc;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &gorc->contract_amount),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("fulfillment_url",
+ &gorc->fulfillment_url),
+ NULL),
+ GNUNET_JSON_spec_string ("summary",
+ &gorc->summary),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &gorc->timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (gorc->contract_terms,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (
+ gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ hc->infix));
+ return;
+ }
+ if (! gorc->order_only)
+ {
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (gorc->contract_terms,
+ &gorc->h_contract_terms))
{
- gorc_report (gorc,
- TALER_EC_MERCHANT_GET_ORDERS_EXCHANGE_TRACKING_FAILURE,
- &tq->coin_pub,
- &dr->hr);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- if (NULL == gorc->tq_head)
- gorc_resume (gorc,
- 0,
- TALER_EC_NONE);
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+ NULL));
return;
}
- } /* end switch */
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- if (NULL != gorc->tq_head)
- return;
- /* *all* are done, resume! */
- gorc_resume (gorc,
- 0,
- TALER_EC_NONE);
+ }
+ GNUNET_assert (NULL != gorc->contract_terms);
+ gorc->phase++;
}
/**
- * Function called with the result of a #TMH_EXCHANGES_find_exchange()
- * operation.
+ * Check payment status of the order.
*
- * @param cls closure with a `struct GetOrderRequestContext *`
- * @param hr HTTP response details
- * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
- * @param exchange_trusted true if this exchange is trusted by config
+ * @param[in,out] gorc order context to update
*/
static void
-exchange_found_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *eh,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted)
+phase_check_paid (struct GetOrderRequestContext *gorc)
{
- struct TransferQuery *tq = cls;
- struct GetOrderRequestContext *gorc = tq->gorc;
+ struct TMH_HandlerContext *hc = gorc->hc;
- tq->fo = NULL;
- if (NULL == hr)
+ if (gorc->order_only)
{
- /* failed */
- GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
- gorc->tq_tail,
- tq);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- gorc_resume (gorc,
- MHD_HTTP_GATEWAY_TIMEOUT,
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order %s unclaimed, no need to lookup payment status\n",
+ hc->infix);
+ GNUNET_assert (! gorc->paid);
+ GNUNET_assert (! gorc->wired);
+ gorc->phase++;
return;
}
- if (NULL == eh)
+ if (NULL == gorc->session_id)
{
- /* failed */
- GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
- gorc->tq_tail,
- tq);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- gorc->exchange_hc = hr->http_status;
- gorc->exchange_ec = hr->ec;
- gorc_resume (gorc,
- MHD_HTTP_BAD_GATEWAY,
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No session ID, do not need to lookup session-ID specific payment status (%s/%s)\n",
+ gorc->paid ? "paid" : "unpaid",
+ gorc->wired ? "wired" : "unwired");
+ gorc->phase++;
return;
}
- tq->dgh = TALER_EXCHANGE_deposits_get (eh,
- &gorc->hc->instance->merchant_priv,
- &tq->h_wire,
- &gorc->h_contract_terms,
- &tq->coin_pub,
- &deposit_get_cb,
- tq);
- if (NULL == tq->dgh)
+ if (! gorc->paid_session_matches)
{
- GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
- gorc->tq_tail,
- tq);
- GNUNET_free (tq->exchange_url);
- GNUNET_free (tq);
- gorc_resume (gorc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_GET_ORDERS_ID_EXCHANGE_REQUEST_FAILURE);
+ gorc->paid = false;
+ gorc->wired = false;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order %s %s for session %s (%s)\n",
+ hc->infix,
+ gorc->paid ? "paid" : "unpaid",
+ gorc->session_id,
+ gorc->wired ? "wired" : "unwired");
+ gorc->phase++;
}
/**
- * Function called with each @a coin_pub that was deposited into the
- * @a h_wire account of the merchant for the @a deposit_serial as part
- * of the payment for the order identified by @a cls.
+ * Check if re-purchase detection applies to the order.
*
- * Queries the exchange for the payment status associated with the
- * given coin.
- *
- * @param cls a `struct GetOrderRequestContext`
- * @param deposit_serial identifies the deposit operation
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param amount_with_fee amount the exchange will deposit for this coin
- * @param deposit_fee fee the exchange will charge for this coin
- * @param h_wire hash of the merchant's wire account into which the deposit was made
- * @param coin_pub public key of the deposited coin
+ * @param[in,out] gorc order context to update
*/
static void
-deposit_cb (void *cls,
- uint64_t deposit_serial,
- const char *exchange_url,
- const struct TALER_MerchantWireHashP *h_wire,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct TALER_CoinSpendPublicKeyP *coin_pub)
+phase_check_repurchase (struct GetOrderRequestContext *gorc)
{
- struct GetOrderRequestContext *gorc = cls;
- struct TransferQuery *tq;
+ struct TMH_HandlerContext *hc = gorc->hc;
+ char *already_paid_order_id = NULL;
+ enum GNUNET_DB_QueryStatus qs;
+ char *taler_pay_uri;
+ char *order_status_url;
+ MHD_RESULT ret;
- tq = GNUNET_new (struct TransferQuery);
- tq->gorc = gorc;
- tq->exchange_url = GNUNET_strdup (exchange_url);
- tq->deposit_serial = deposit_serial;
- GNUNET_CONTAINER_DLL_insert (gorc->tq_head,
- gorc->tq_tail,
- tq);
- tq->coin_pub = *coin_pub;
- tq->h_wire = *h_wire;
- tq->amount_with_fee = *amount_with_fee;
- tq->deposit_fee = *deposit_fee;
- tq->fo = TMH_EXCHANGES_find_exchange (exchange_url,
- NULL,
- GNUNET_NO,
- &exchange_found_cb,
- tq);
- if (NULL == tq->fo)
+ if ( (gorc->paid) ||
+ (NULL == gorc->fulfillment_url) ||
+ (NULL == gorc->session_id) )
{
- gorc_resume (gorc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_GET_ORDERS_ID_EXCHANGE_LOOKUP_START_FAILURE);
+ /* Repurchase cannot apply */
+ gorc->phase++;
+ return;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Running re-purchase detection for %s/%s\n",
+ gorc->session_id,
+ gorc->fulfillment_url);
+ qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
+ hc->instance->settings.id,
+ gorc->fulfillment_url,
+ gorc->session_id,
+ TALER_EXCHANGE_YNA_NO !=
+ gorc->allow_refunded_for_repurchase,
+ &already_paid_order_id);
+ if (0 > qs)
+ {
+ /* single, read-only SQL statements should never cause
+ serialization problems, and the entry should exist as per above */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "order by fulfillment"));
+ return;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "No already paid order for %s/%s\n",
+ gorc->session_id,
+ gorc->fulfillment_url);
+ gorc->phase++;
+ return;
+ }
+
+ /* User did pay for this order, but under a different session; ask wallet to
+ switch order ID */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Found already paid order %s\n",
+ already_paid_order_id);
+ taler_pay_uri = TMH_make_taler_pay_uri (gorc->sc.con,
+ hc->infix,
+ gorc->session_id,
+ hc->instance->settings.id,
+ &gorc->claim_token);
+ order_status_url = TMH_make_order_status_url (gorc->sc.con,
+ hc->infix,
+ gorc->session_id,
+ hc->instance->settings.id,
+ &gorc->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);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
+ "host"));
+ return;
+ }
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ gorc->sc.con,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("taler_pay_uri",
+ taler_pay_uri),
+ GNUNET_JSON_pack_string ("order_status_url",
+ order_status_url),
+ GNUNET_JSON_pack_string ("order_status",
+ "unpaid"),
+ GNUNET_JSON_pack_string ("already_paid_order_id",
+ already_paid_order_id),
+ GNUNET_JSON_pack_string ("already_paid_fulfillment_url",
+ gorc->fulfillment_url),
+ TALER_JSON_pack_amount ("total_amount",
+ &gorc->contract_amount),
+ GNUNET_JSON_pack_string ("summary",
+ gorc->summary),
+ GNUNET_JSON_pack_timestamp ("creation_time",
+ gorc->timestamp));
+ GNUNET_free (taler_pay_uri);
+ GNUNET_free (already_paid_order_id);
+ phase_end (gorc,
+ ret);
}
/**
- * Clean up the session state for a GET /private/order/ID request.
+ * Check if we should suspend until the order is paid.
*
- * @param cls closure, must be a `struct GetOrderRequestContext *`
+ * @param[in,out] gorc order context to update
*/
static void
-gorc_cleanup (void *cls)
+phase_unpaid_finish (struct GetOrderRequestContext *gorc)
{
- struct GetOrderRequestContext *gorc = cls;
+ struct TMH_HandlerContext *hc = gorc->hc;
+ char *taler_pay_uri;
+ char *order_status_url;
+ MHD_RESULT ret;
- if (NULL != gorc->contract_terms)
- json_decref (gorc->contract_terms);
- if (NULL != gorc->wire_details)
- json_decref (gorc->wire_details);
- if (NULL != gorc->refund_details)
- json_decref (gorc->refund_details);
- if (NULL != gorc->wire_reports)
- json_decref (gorc->wire_reports);
- GNUNET_assert (NULL == gorc->tt);
- if (NULL != gorc->eh)
+ if (gorc->paid)
{
- TMH_db->event_listen_cancel (gorc->eh);
- gorc->eh = NULL;
+ gorc->phase++;
+ return;
}
- if (NULL != gorc->session_eh)
+ /* User never paid for this order, suspend waiting
+ on payment or return details. */
+ if (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
{
- TMH_db->event_listen_cancel (gorc->session_eh);
- gorc->session_eh = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending GET /private/orders/%s\n",
+ hc->infix);
+ GNUNET_CONTAINER_DLL_insert (gorc_head,
+ gorc_tail,
+ gorc);
+ gorc->phase = GOP_SUSPENDED_ON_UNPAID;
+ gorc->suspended = GNUNET_YES;
+ MHD_suspend_connection (gorc->sc.con);
+ return;
}
- GNUNET_free (gorc);
+ if (! gorc->order_only)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order %s claimed but not paid yet\n",
+ hc->infix);
+ phase_end (gorc,
+ TALER_MHD_REPLY_JSON_PACK (
+ gorc->sc.con,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_object_incref ("contract_terms",
+ gorc->contract_terms),
+ GNUNET_JSON_pack_string ("order_status",
+ "claimed")));
+ return;
+ }
+ taler_pay_uri = TMH_make_taler_pay_uri (gorc->sc.con,
+ hc->infix,
+ gorc->session_id,
+ hc->instance->settings.id,
+ &gorc->claim_token);
+ order_status_url = TMH_make_order_status_url (gorc->sc.con,
+ hc->infix,
+ gorc->session_id,
+ hc->instance->settings.id,
+ &gorc->claim_token,
+ NULL);
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ gorc->sc.con,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("taler_pay_uri",
+ taler_pay_uri),
+ GNUNET_JSON_pack_string ("order_status_url",
+ order_status_url),
+ GNUNET_JSON_pack_string ("order_status",
+ "unpaid"),
+ TALER_JSON_pack_amount ("total_amount",
+ &gorc->contract_amount),
+ GNUNET_JSON_pack_string ("summary",
+ gorc->summary),
+ GNUNET_JSON_pack_timestamp ("creation_time",
+ gorc->timestamp));
+ GNUNET_free (taler_pay_uri);
+ GNUNET_free (order_status_url);
+ phase_end (gorc,
+ ret);
+
}
@@ -758,30 +1001,37 @@ gorc_cleanup (void *cls)
* @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_Timestamp 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)
+process_refunds_cb (
+ void *cls,
+ uint64_t refund_serial,
+ struct GNUNET_TIME_Timestamp 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 GetOrderRequestContext *gorc = cls;
- GNUNET_assert (0 ==
- json_array_append_new (
- gorc->refund_details,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("amount",
- refund_amount),
- GNUNET_JSON_pack_bool ("pending",
- pending),
- GNUNET_JSON_pack_timestamp ("timestamp",
- timestamp),
- GNUNET_JSON_pack_string ("reason",
- reason))));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found refund %llu over %s for reason %s\n",
+ (unsigned long long) rtransaction_id,
+ TALER_amount2s (refund_amount),
+ reason);
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ gorc->refund_details,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("amount",
+ refund_amount),
+ GNUNET_JSON_pack_bool ("pending",
+ pending),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ timestamp),
+ GNUNET_JSON_pack_string ("reason",
+ reason))));
/* For refunded coins, we are not charged deposit fees, so subtract those
again */
for (struct TransferQuery *tq = gorc->tq_head;
@@ -792,12 +1042,30 @@ process_refunds_cb (void *cls,
GNUNET_memcmp (&tq->coin_pub,
coin_pub))
{
- GNUNET_assert (0 <=
- TALER_amount_subtract (&gorc->deposit_fees_total,
- &gorc->deposit_fees_total,
- &tq->deposit_fee));
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (
+ &gorc->deposit_fees_total,
+ &tq->deposit_fee))
+ {
+ gorc->refund_currency_mismatch = true;
+ return;
+ }
+
+ GNUNET_assert (
+ 0 <=
+ TALER_amount_subtract (&gorc->deposit_fees_total,
+ &gorc->deposit_fees_total,
+ &tq->deposit_fee));
}
}
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (
+ &gorc->refund_amount,
+ refund_amount))
+ {
+ gorc->refund_currency_mismatch = true;
+ return;
+ }
GNUNET_assert (0 <=
TALER_amount_add (&gorc->refund_amount,
&gorc->refund_amount,
@@ -808,6 +1076,132 @@ process_refunds_cb (void *cls,
/**
+ * Check refund status for the order.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_check_refunds (struct GetOrderRequestContext *gorc)
+{
+ struct TMH_HandlerContext *hc = gorc->hc;
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_assert (! gorc->order_only);
+ GNUNET_assert (gorc->paid);
+ /* Accumulate refunds, if any. */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (gorc->contract_amount.currency,
+ &gorc->refund_amount));
+ qs = TMH_db->lookup_refunds_detailed (
+ TMH_db->cls,
+ hc->instance->settings.id,
+ &gorc->h_contract_terms,
+ &process_refunds_cb,
+ gorc);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (
+ gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "detailed refunds"));
+ return;
+ }
+ if (gorc->refund_currency_mismatch)
+ {
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (
+ gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "refunds in different currency than original order price"));
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Total refunds are %s\n",
+ TALER_amount2s (&gorc->refund_amount));
+ gorc->phase++;
+}
+
+
+/**
+ * Function called with each @a coin_pub that was deposited into the
+ * @a h_wire account of the merchant for the @a deposit_serial as part
+ * of the payment for the order identified by @a cls.
+ *
+ * Queries the exchange for the payment status associated with the
+ * given coin.
+ *
+ * @param cls a `struct GetOrderRequestContext`
+ * @param deposit_serial identifies the deposit operation
+ * @param exchange_url URL of the exchange that issued @a coin_pub
+ * @param h_wire hash of the merchant's wire account into which the deposit was made
+ * @param deposit_timestamp when was the deposit made
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param coin_pub public key of the deposited coin
+ */
+static void
+deposit_cb (
+ void *cls,
+ uint64_t deposit_serial,
+ const char *exchange_url,
+ const struct TALER_MerchantWireHashP *h_wire,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+ struct GetOrderRequestContext *gorc = cls;
+ struct TransferQuery *tq;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Checking deposit status for coin %s (over %s)\n",
+ TALER_B2S (coin_pub),
+ TALER_amount2s (amount_with_fee));
+ gorc->last_payment
+ = GNUNET_TIME_timestamp_max (gorc->last_payment,
+ deposit_timestamp);
+ tq = GNUNET_new (struct TransferQuery);
+ tq->gorc = gorc;
+ tq->exchange_url = GNUNET_strdup (exchange_url);
+ tq->deposit_serial = deposit_serial;
+ GNUNET_CONTAINER_DLL_insert (gorc->tq_head,
+ gorc->tq_tail,
+ tq);
+ tq->coin_pub = *coin_pub;
+ tq->h_wire = *h_wire;
+ tq->amount_with_fee = *amount_with_fee;
+ tq->deposit_fee = *deposit_fee;
+}
+
+
+/**
+ * Check wire transfer status for the order at the exchange.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_check_deposits (struct GetOrderRequestContext *gorc)
+{
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (gorc->contract_amount.currency,
+ &gorc->deposits_total));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (gorc->contract_amount.currency,
+ &gorc->deposit_fees_total));
+ TMH_db->lookup_deposits_by_order (TMH_db->cls,
+ gorc->order_serial,
+ &deposit_cb,
+ gorc);
+ gorc->phase++;
+}
+
+
+/**
* Function called with available wire details, to be added to
* the response.
*
@@ -821,32 +1215,43 @@ process_refunds_cb (void *cls,
* @a wtid over the total amount happened?
*/
static void
-process_transfer_details (void *cls,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const char *exchange_url,
- struct GNUNET_TIME_Timestamp execution_time,
- const struct TALER_Amount *deposit_value,
- const struct TALER_Amount *deposit_fee,
- bool transfer_confirmed)
+process_transfer_details (
+ void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const char *exchange_url,
+ struct GNUNET_TIME_Timestamp execution_time,
+ const struct TALER_Amount *deposit_value,
+ const struct TALER_Amount *deposit_fee,
+ bool transfer_confirmed)
{
struct GetOrderRequestContext *gorc = cls;
json_t *wire_details = gorc->wire_details;
struct TALER_Amount wired;
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&gorc->deposits_total,
+ deposit_value)) ||
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&gorc->deposit_fees_total,
+ deposit_fee)) )
+ {
+ GNUNET_break (0);
+ gorc->deposit_currency_mismatch = true;
+ return;
+ }
+
/* Compute total amount *wired* */
- GNUNET_assert (0 <
+ GNUNET_assert (0 <=
TALER_amount_add (&gorc->deposits_total,
&gorc->deposits_total,
deposit_value));
- GNUNET_assert (0 <
+ GNUNET_assert (0 <=
TALER_amount_add (&gorc->deposit_fees_total,
&gorc->deposit_fees_total,
deposit_fee));
-
- GNUNET_assert
- (0 <= TALER_amount_subtract (&wired,
- deposit_value,
- deposit_fee));
+ GNUNET_assert (0 <= TALER_amount_subtract (&wired,
+ deposit_value,
+ deposit_fee));
GNUNET_assert (0 ==
json_array_append_new (
wire_details,
@@ -864,647 +1269,296 @@ process_transfer_details (void *cls,
}
-MHD_RESULT
-TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
+/**
+ * Check transfer status in local database.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_check_local_transfers (struct GetOrderRequestContext *gorc)
{
- struct GetOrderRequestContext *gorc = hc->ctx;
+ struct TMH_HandlerContext *hc = gorc->hc;
enum GNUNET_DB_QueryStatus qs;
- bool paid;
- bool wired;
- bool order_only = false;
- struct TALER_ClaimTokenP claim_token = { 0 };
- const char *summary;
- struct GNUNET_TIME_Timestamp timestamp;
-
- if (NULL == gorc)
- {
- /* First time here, parse request and check order is known */
- GNUNET_assert (NULL != hc->infix);
- gorc = GNUNET_new (struct GetOrderRequestContext);
- hc->cc = &gorc_cleanup;
- hc->ctx = gorc;
- gorc->sc.con = connection;
- gorc->hc = hc;
- gorc->wire_details = json_array ();
- GNUNET_assert (NULL != gorc->wire_details);
- gorc->refund_details = json_array ();
- GNUNET_assert (NULL != gorc->refund_details);
- gorc->wire_reports = json_array ();
- GNUNET_assert (NULL != gorc->wire_reports);
- gorc->session_id = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "session_id");
- /* process 'transfer' argument */
- {
- const char *transfer_s;
-
- transfer_s = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "transfer");
- if ( (NULL != transfer_s) &&
- (0 == strcasecmp (transfer_s,
- "yes")) )
- gorc->transfer_status_requested = true;
- }
-
- /* process 'timeout_ms' argument */
- {
- const char *long_poll_timeout_s;
- long_poll_timeout_s = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "timeout_ms");
- if (NULL != long_poll_timeout_s)
- {
- unsigned int timeout_ms;
- char dummy;
- struct GNUNET_TIME_Relative timeout;
-
- if (1 != sscanf (long_poll_timeout_s,
- "%u%c",
- &timeout_ms,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "timeout_ms must be non-negative number");
- }
- timeout = GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_MILLISECONDS,
- timeout_ms);
- gorc->sc.long_poll_timeout
- = GNUNET_TIME_relative_to_absolute (timeout);
- if (! GNUNET_TIME_relative_is_zero (timeout))
- {
- 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_CRYPTO_hash (hc->infix,
- strlen (hc->infix),
- &pay_eh.h_order_id);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Subscribing to payment triggers for %p\n",
- gorc);
- gorc->eh = TMH_db->event_listen (TMH_db->cls,
- &pay_eh.header,
- timeout,
- &resume_by_event,
- gorc);
- if ( (NULL != gorc->session_id) &&
- (NULL != gorc->fulfillment_url) )
- {
- struct TMH_SessionEventP session_eh = {
- .header.size = htons (sizeof (session_eh)),
- .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
- .merchant_pub = hc->instance->merchant_pub
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Subscribing to session triggers for %p\n",
- gorc);
- GNUNET_CRYPTO_hash (gorc->session_id,
- strlen (gorc->session_id),
- &session_eh.h_session_id);
- GNUNET_CRYPTO_hash (gorc->fulfillment_url,
- strlen (gorc->fulfillment_url),
- &session_eh.h_fulfillment_url);
- gorc->session_eh = TMH_db->event_listen (TMH_db->cls,
- &session_eh.header,
- timeout,
- &resume_by_event,
- gorc);
- }
- }
- }
- else
- {
- gorc->sc.long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
- }
- }
- } /* end first-time per-request initialization */
-
- if (GNUNET_SYSERR == gorc->suspended)
- return MHD_NO; /* we are in shutdown */
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Starting GET /private/orders/%s processing with timeout %s\n",
- hc->infix,
- GNUNET_STRINGS_absolute_time_to_string (
- gorc->sc.long_poll_timeout));
- if (NULL != gorc->contract_terms)
- {
- /* Free memory filled with old contract terms before fetching the latest
- ones from the DB. Note that we cannot simply skip the database
- interaction as the contract terms loaded previously might be from an
- earlier *unclaimed* order state (which we loaded in a previous
- invocation of this function and we are back here due to long polling)
- and thus the contract terms could have changed during claiming. Thus,
- we need to fetch the latest contract terms from the DB again. */
- json_decref (gorc->contract_terms);
- gorc->contract_terms = NULL;
- gorc->fulfillment_url = NULL;
- }
- TMH_db->preflight (TMH_db->cls);
- qs = TMH_db->lookup_contract_terms (TMH_db->cls,
- hc->instance->settings.id,
- hc->infix,
- &gorc->contract_terms,
- &gorc->order_serial,
- NULL);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (gorc->contract_amount.currency,
+ &gorc->deposits_total));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (gorc->contract_amount.currency,
+ &gorc->deposit_fees_total));
+ qs = TMH_db->lookup_transfer_details_by_order (TMH_db->cls,
+ gorc->order_serial,
+ &process_transfer_details,
+ gorc);
+ if (0 > qs)
{
- order_only = true;
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "transfer details"));
+ return;
}
- if (0 > qs)
+ if (gorc->deposit_currency_mismatch)
{
- /* 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,
- "contract terms");
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "deposits in different currency than original order price"));
+ return;
}
+ if (! gorc->wired)
{
- struct TALER_MerchantPostDataHashP unused;
- json_t *ct = NULL;
-
- /* We need the order for two cases: Either when the contract doesn't exist yet,
- * or when the order is claimed but unpaid, and we need the claim token. */
- qs = TMH_db->lookup_order (TMH_db->cls,
- hc->instance->settings.id,
- hc->infix,
- &claim_token,
- &unused,
- &ct);
-
- 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_GENERIC_DB_FETCH_FAILED,
- "order");
- }
- if (order_only && (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) )
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
- hc->infix);
- }
- if (order_only)
- {
- gorc->contract_terms = ct;
- }
- else if (NULL != ct)
+ /* we believe(d) the wire transfer did not happen yet, check if maybe
+ in light of new evidence it did */
+ struct TALER_Amount expect_total;
+
+ if (0 >
+ TALER_amount_subtract (&expect_total,
+ &gorc->contract_amount,
+ &gorc->refund_amount))
{
- json_decref (ct);
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (
+ gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ "refund exceeds contract value"));
+ return;
}
- }
- /* extract the fulfillment URL, total amount, summary and timestamp
- from the contract terms! */
- {
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("amount",
- TMH_currency,
- &gorc->contract_amount),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("fulfillment_url",
- &gorc->fulfillment_url),
- NULL),
- GNUNET_JSON_spec_string ("summary",
- &summary),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (gorc->contract_terms,
- spec,
- NULL, NULL))
+ if (0 >
+ TALER_amount_subtract (&expect_total,
+ &expect_total,
+ &gorc->deposit_fees_total))
{
GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
- hc->infix);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (
+ gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ "deposit fees exceed total minus refunds"));
+ return;
}
- }
- if (! order_only)
- {
- if (GNUNET_OK !=
- TALER_JSON_contract_hash (gorc->contract_terms,
- &gorc->h_contract_terms))
+ if (0 >=
+ TALER_amount_cmp (&expect_total,
+ &gorc->deposits_total))
{
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
- NULL);
+ /* expect_total <= gorc->deposits_total: good: we got the wire transfer */
+ gorc->wired = true;
+ qs = TMH_db->mark_order_wired (TMH_db->cls,
+ gorc->order_serial);
+ GNUNET_break (qs >= 0); /* just warn if transaction failed */
+ TMH_notify_order_change (hc->instance,
+ TMH_OSF_PAID
+ | TMH_OSF_WIRED,
+ gorc->timestamp,
+ gorc->order_serial);
}
}
- if (TALER_EC_NONE != gorc->wire_ec)
- {
- return TALER_MHD_reply_with_error (connection,
- gorc->wire_hc,
- gorc->wire_ec,
- NULL);
- }
+ gorc->phase++;
+}
- GNUNET_assert (NULL != gorc->contract_terms);
- TMH_db->preflight (TMH_db->cls);
- if (order_only)
- {
- paid = false;
- wired = false;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Order %s unclaimed, no need to lookup payment status\n",
- hc->infix);
- }
- else
- {
- qs = TMH_db->lookup_payment_status (TMH_db->cls,
- gorc->order_serial,
- gorc->session_id,
- &paid,
- &wired);
- if (0 > qs)
- {
- /* single, read-only SQL statements should never cause
- serialization problems, and the entry should exist as per above */
- 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,
- "payment status");
- }
- }
- if ( (! paid) &&
- (NULL != gorc->fulfillment_url) &&
- (NULL != gorc->session_id) )
- {
- char *already_paid_order_id = NULL;
+/**
+ * Generate final result for the status request.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_reply_result (struct GetOrderRequestContext *gorc)
+{
+ struct TMH_HandlerContext *hc = gorc->hc;
+ MHD_RESULT ret;
+ char *order_status_url;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Running re-purchase detection for %s/%s\n",
- gorc->session_id,
- gorc->fulfillment_url);
- qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
- hc->instance->settings.id,
- gorc->fulfillment_url,
- gorc->session_id,
- &already_paid_order_id);
- if (0 > qs)
- {
- /* single, read-only SQL statements should never cause
- serialization problems, and the entry should exist as per above */
- 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_ONE_RESULT == qs)
- {
- /* User did pay for this order, but under a different session; ask wallet
- to switch order ID */
- char *taler_pay_uri;
- char *order_status_url;
- MHD_RESULT ret;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Found already paid order %s\n",
- already_paid_order_id);
- taler_pay_uri = TMH_make_taler_pay_uri (connection,
- hc->infix,
- gorc->session_id,
- hc->instance->settings.id,
- &claim_token);
- order_status_url = TMH_make_order_status_url (connection,
- hc->infix,
- gorc->session_id,
- hc->instance->settings.id,
- &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 (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
- "host");
- }
- ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_string ("taler_pay_uri",
- taler_pay_uri),
- GNUNET_JSON_pack_string ("order_status_url",
- order_status_url),
- GNUNET_JSON_pack_string ("order_status",
- "unpaid"),
- GNUNET_JSON_pack_string ("already_paid_order_id",
- already_paid_order_id),
- GNUNET_JSON_pack_string ("already_paid_fulfillment_url",
- gorc->fulfillment_url),
- TALER_JSON_pack_amount ("total_amount",
- &gorc->contract_amount),
- GNUNET_JSON_pack_string ("summary",
- summary),
- GNUNET_JSON_pack_timestamp ("creation_time",
- timestamp));
- GNUNET_free (taler_pay_uri);
- GNUNET_free (already_paid_order_id);
- return ret;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "No already paid order for %s/%s\n",
- gorc->session_id,
- gorc->fulfillment_url);
- }
- if ( (! paid) &&
- (! order_only) )
{
- if (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Suspending GET /private/orders/%s\n",
- hc->infix);
- GNUNET_CONTAINER_DLL_insert (gorc_head,
- gorc_tail,
- gorc);
- gorc->suspended = GNUNET_YES;
- MHD_suspend_connection (gorc->sc.con);
- return MHD_YES;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Order %s claimed but not paid yet\n",
- hc->infix);
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_object_incref ("contract_terms",
- gorc->contract_terms),
- GNUNET_JSON_pack_string ("order_status",
- "claimed"));
+ struct TALER_PrivateContractHashP *h_contract = NULL;
+
+ /* In a session-bound payment, allow the browser to check the order
+ * status page (e.g. to get a refund).
+ *
+ * Note that we don't allow this outside of session-based payment, as
+ * otherwise this becomes an oracle to convert order_id to h_contract.
+ */
+ if (NULL != gorc->session_id)
+ h_contract = &gorc->h_contract_terms;
+
+ order_status_url =
+ TMH_make_order_status_url (gorc->sc.con,
+ hc->infix,
+ gorc->session_id,
+ hc->instance->settings.id,
+ &gorc->claim_token,
+ h_contract);
}
- if (paid &&
- (! wired) &&
- gorc->transfer_status_requested)
+ if (GNUNET_TIME_absolute_is_zero (gorc->last_payment.abs_time))
{
- /* suspend connection, wait for exchange to check wire transfer status there */
- gorc->transfer_status_requested = false; /* only try ONCE */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
- &gorc->deposits_total));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
- &gorc->deposit_fees_total));
- TMH_db->lookup_deposits_by_order (TMH_db->cls,
- gorc->order_serial,
- &deposit_cb,
- gorc);
- if (NULL != gorc->tq_head)
- {
- GNUNET_CONTAINER_DLL_insert (gorc_head,
- gorc_tail,
- gorc);
- gorc->suspended = GNUNET_YES;
- MHD_suspend_connection (connection);
- gorc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
- &exchange_timeout_cb,
- gorc);
- return MHD_YES;
- }
+ GNUNET_break (GNUNET_YES ==
+ TALER_amount_is_zero (&gorc->contract_amount));
+ gorc->last_payment = gorc->timestamp;
}
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ gorc->sc.con,
+ MHD_HTTP_OK,
+ // Deprecated in protocol v6.
+ GNUNET_JSON_pack_array_steal ("wire_reports",
+ json_array ()),
+ GNUNET_JSON_pack_uint64 ("exchange_code",
+ gorc->exchange_ec),
+ GNUNET_JSON_pack_uint64 ("exchange_http_status",
+ gorc->exchange_hc),
+ /* legacy: */
+ GNUNET_JSON_pack_uint64 ("exchange_ec",
+ gorc->exchange_ec),
+ /* legacy: */
+ GNUNET_JSON_pack_uint64 ("exchange_hc",
+ gorc->exchange_hc),
+ TALER_JSON_pack_amount ("deposit_total",
+ &gorc->deposits_total),
+ GNUNET_JSON_pack_object_incref ("contract_terms",
+ gorc->contract_terms),
+ GNUNET_JSON_pack_string ("order_status",
+ "paid"),
+ GNUNET_JSON_pack_timestamp ("last_payment",
+ gorc->last_payment),
+ GNUNET_JSON_pack_bool ("refunded",
+ gorc->refunded),
+ GNUNET_JSON_pack_bool ("wired",
+ gorc->wired),
+ GNUNET_JSON_pack_bool ("refund_pending",
+ gorc->refund_pending),
+ TALER_JSON_pack_amount ("refund_amount",
+ &gorc->refund_amount),
+ GNUNET_JSON_pack_array_steal ("wire_details",
+ gorc->wire_details),
+ GNUNET_JSON_pack_array_steal ("refund_details",
+ gorc->refund_details),
+ GNUNET_JSON_pack_string ("order_status_url",
+ order_status_url));
+ GNUNET_free (order_status_url);
+ gorc->wire_details = NULL;
+ gorc->refund_details = NULL;
+ phase_end (gorc,
+ ret);
+}
- if ( (! paid) &&
- (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout)) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Suspending GET /private/orders/%s\n",
- hc->infix);
- GNUNET_assert (GNUNET_NO == gorc->suspended);
- GNUNET_CONTAINER_DLL_insert (gorc_head,
- gorc_tail,
- gorc);
- gorc->suspended = GNUNET_YES;
- MHD_suspend_connection (gorc->sc.con);
- return MHD_YES;
- }
- if (! paid)
- {
- /* User never paid for this order */
- char *taler_pay_uri;
- char *order_status_url;
- MHD_RESULT ret;
+/**
+ * End with error status in wire_hc and wire_ec.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_error (struct GetOrderRequestContext *gorc)
+{
+ GNUNET_assert (TALER_EC_NONE != gorc->wire_ec);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (gorc->sc.con,
+ gorc->wire_hc,
+ gorc->wire_ec,
+ NULL));
+}
- taler_pay_uri = TMH_make_taler_pay_uri (connection,
- hc->infix,
- gorc->session_id,
- hc->instance->settings.id,
- &claim_token);
- order_status_url = TMH_make_order_status_url (connection,
- hc->infix,
- gorc->session_id,
- hc->instance->settings.id,
- &claim_token,
- NULL);
- ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_string ("taler_pay_uri",
- taler_pay_uri),
- GNUNET_JSON_pack_string ("order_status_url",
- order_status_url),
- GNUNET_JSON_pack_string ("order_status",
- "unpaid"),
- TALER_JSON_pack_amount ("total_amount",
- &gorc->contract_amount),
- GNUNET_JSON_pack_string ("summary",
- summary),
- GNUNET_JSON_pack_timestamp ("creation_time",
- timestamp));
- GNUNET_free (taler_pay_uri);
- GNUNET_free (order_status_url);
- return ret;
- }
- /* Here we know the user DID pay, compute refunds... */
- GNUNET_assert (! order_only);
- GNUNET_assert (paid);
- /* Accumulate refunds, if any. */
- {
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
- &gorc->refund_amount));
- qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
- hc->instance->settings.id,
- &gorc->h_contract_terms,
- &process_refunds_cb,
- gorc);
- }
- if (0 > qs)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "detailed refunds");
- }
+MHD_RESULT
+TMH_private_get_orders_ID (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct GetOrderRequestContext *gorc = hc->ctx;
- /* Generate final reply, including wire details if we have them */
+ if (NULL == gorc)
{
- MHD_RESULT ret;
- char *order_status_url;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
- &gorc->deposits_total));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TMH_currency,
- &gorc->deposit_fees_total));
- qs = TMH_db->lookup_transfer_details_by_order (TMH_db->cls,
- gorc->order_serial,
- &process_transfer_details,
- gorc);
- if (0 > qs)
- {
- GNUNET_break (0);
+ /* First time here, parse request and check order is known */
+ GNUNET_assert (NULL != hc->infix);
+ gorc = GNUNET_new (struct GetOrderRequestContext);
+ hc->cc = &gorc_cleanup;
+ hc->ctx = gorc;
+ gorc->sc.con = connection;
+ gorc->hc = hc;
+ gorc->wire_details = json_array ();
+ GNUNET_assert (NULL != gorc->wire_details);
+ gorc->refund_details = json_array ();
+ GNUNET_assert (NULL != gorc->refund_details);
+ gorc->session_id = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "session_id");
+ if (! (TALER_arg_to_yna (connection,
+ "allow_refunded_for_repurchase",
+ TALER_EXCHANGE_YNA_NO,
+ &gorc->allow_refunded_for_repurchase)) )
return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "transfer details");
- }
-
- if (! wired)
- {
- /* we believe(d) the wire transfer did not happen yet, check if maybe
- in light of new evidence it did */
- struct TALER_Amount expect_total;
-
- if (0 >
- TALER_amount_subtract (&expect_total,
- &gorc->contract_amount,
- &gorc->refund_amount))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
- "refund exceeds contract value");
- }
- if (0 >
- TALER_amount_subtract (&expect_total,
- &expect_total,
- &gorc->deposit_fees_total))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
- "deposit fees exceed total minus refunds");
- }
- if (0 >=
- TALER_amount_cmp (&expect_total,
- &gorc->deposits_total))
- {
- /* expect_total <= gorc->deposits_total: good: we got paid */
- wired = true;
- qs = TMH_db->mark_order_wired (TMH_db->cls,
- gorc->order_serial);
- GNUNET_break (qs >= 0); /* just warn if transaction failed */
- TMH_notify_order_change (hc->instance,
- TMH_OSF_PAID
- | TMH_OSF_WIRED,
- timestamp,
- gorc->order_serial);
- }
- }
-
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "allow_refunded_for_repurchase");
+ TALER_MHD_parse_request_timeout (connection,
+ &gorc->sc.long_poll_timeout);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting GET /private/orders/%s processing with timeout %s\n",
+ hc->infix,
+ GNUNET_STRINGS_absolute_time_to_string (
+ gorc->sc.long_poll_timeout));
+ }
+ if (GNUNET_SYSERR == gorc->suspended)
+ return MHD_NO; /* we are in shutdown */
+ while (1)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing order %s in phase %d\n",
+ hc->infix,
+ (int) gorc->phase);
+ switch (gorc->phase)
{
- struct TALER_PrivateContractHashP *h_contract = NULL;
-
- /* In a session-bound payment, allow the browser to check the order
- * status page (e.g. to get a refund).
- *
- * Note that we don't allow this outside of session-based payment, as
- * otherwise this becomes an oracle to convert order_id to h_contract.
- */if (NULL != gorc->session_id)
- h_contract = &gorc->h_contract_terms;
-
- order_status_url =
- TMH_make_order_status_url (connection,
- hc->infix,
- gorc->session_id,
- hc->instance->settings.id,
- &claim_token,
- h_contract);
+ case GOP_INIT:
+ phase_init (gorc);
+ break;
+ case GOP_FETCH_CONTRACT:
+ phase_fetch_contract (gorc);
+ break;
+ case GOP_PARSE_CONTRACT:
+ phase_parse_contract (gorc);
+ break;
+ case GOP_CHECK_PAID:
+ phase_check_paid (gorc);
+ break;
+ case GOP_CHECK_REPURCHASE:
+ phase_check_repurchase (gorc);
+ break;
+ case GOP_UNPAID_FINISH:
+ phase_unpaid_finish (gorc);
+ break;
+ case GOP_CHECK_REFUNDS:
+ phase_check_refunds (gorc);
+ break;
+ case GOP_CHECK_DEPOSITS:
+ phase_check_deposits (gorc);
+ break;
+ case GOP_CHECK_LOCAL_TRANSFERS:
+ phase_check_local_transfers (gorc);
+ break;
+ case GOP_REPLY_RESULT:
+ phase_reply_result (gorc);
+ break;
+ case GOP_ERROR:
+ phase_error (gorc);
+ break;
+ case GOP_SUSPENDED_ON_UNPAID:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending order request awaiting payment\n");
+ return MHD_YES;
+ case GOP_END_YES:
+ return MHD_YES;
+ case GOP_END_NO:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Closing connection, no response generated\n");
+ return MHD_NO;
}
-
- ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("wire_reports",
- gorc->wire_reports),
- GNUNET_JSON_pack_uint64 ("exchange_code",
- gorc->exchange_ec),
- GNUNET_JSON_pack_uint64 ("exchange_http_status",
- gorc->exchange_hc),
- /* legacy: */
- GNUNET_JSON_pack_uint64 ("exchange_ec",
- gorc->exchange_ec),
- /* legacy: */
- GNUNET_JSON_pack_uint64 ("exchange_hc",
- gorc->exchange_hc),
- TALER_JSON_pack_amount ("deposit_total",
- &gorc->deposits_total),
- GNUNET_JSON_pack_object_incref ("contract_terms",
- gorc->contract_terms),
- GNUNET_JSON_pack_string ("order_status",
- "paid"),
- GNUNET_JSON_pack_bool ("refunded",
- gorc->refunded),
- GNUNET_JSON_pack_bool ("wired",
- wired),
- GNUNET_JSON_pack_bool ("refund_pending",
- gorc->refund_pending),
- TALER_JSON_pack_amount ("refund_amount",
- &gorc->refund_amount),
- GNUNET_JSON_pack_array_steal ("wire_details",
- gorc->wire_details),
- GNUNET_JSON_pack_array_steal ("refund_details",
- gorc->refund_details),
- GNUNET_JSON_pack_string ("order_status_url",
- order_status_url));
- GNUNET_free (order_status_url);
- gorc->wire_details = NULL;
- gorc->wire_reports = NULL;
- gorc->refund_details = NULL;
- return ret;
- }
+ } /* end first-time per-request initialization */
}