summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_private-get-orders-ID.c
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-06-04 21:00:32 +0200
committerChristian Grothoff <christian@grothoff.org>2020-06-04 21:00:32 +0200
commit78119045b5eaf622ac3752f6cc7f07d9cc5c2feb (patch)
tree3f9125a04a1bcc1fd9933bf7c6158a1e1add4f65 /src/backend/taler-merchant-httpd_private-get-orders-ID.c
parent59413577cf283700e603192d35f6b060e0135495 (diff)
downloadmerchant-78119045b5eaf622ac3752f6cc7f07d9cc5c2feb.tar.gz
merchant-78119045b5eaf622ac3752f6cc7f07d9cc5c2feb.tar.bz2
merchant-78119045b5eaf622ac3752f6cc7f07d9cc5c2feb.zip
work on GET /private/orders/ID"
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.c444
1 files changed, 428 insertions, 16 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 2de1d77e..327ccf7f 100644
--- a/src/backend/taler-merchant-httpd_private-get-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
@@ -24,6 +24,68 @@
#include <taler/taler_json_lib.h>
#include "taler-merchant-httpd_mhd.h"
+/**
+ * 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;
+
+
+/**
+ * Request to an exchange for details about wire transfers
+ * in response to a coin's deposit operation.
+ */
+struct TransferQuery
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct TransferQuery *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct TransferQuery *prev;
+
+ /**
+ * 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;
+
+ /**
+ * Hash of the merchant's bank account the transfer (presumably) went to.
+ */
+ struct GNUNET_HashCode h_wire;
+
+ /**
+ * Public key of the coin this is about.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Which deposit operation is this about?
+ */
+ uint64_t deposit_serial;
+
+};
+
/**
* Data structure we keep for a check payment request.
@@ -54,12 +116,53 @@ struct GetOrderRequestContext
const char *fulfillment_url;
/**
+ * Kept in a DLL while suspended on exchange.
+ */
+ struct GetOrderRequestContext *next;
+
+ /**
+ * Kept in a DLL while suspended on exchange.
+ */
+ struct GetOrderRequestContext *prev;
+
+ /**
+ * Handle to the exchange, only valid while the @e fo succeeds.
+ */
+ struct TALER_EXCHANGE_Handle *eh;
+
+ /**
+ * Head of DLL of individual queries for transfer data.
+ */
+ struct TransferQuery *tq_head;
+
+ /**
+ * Tail of DLL of individual queries for transfer data.
+ */
+ struct TransferQuery *tq_tail;
+
+ /**
+ * Timeout task while waiting on exchange.
+ */
+ struct GNUNET_SCHEDULER_Task *tt;
+
+ /**
* Contract terms of the payment we are checking. NULL when they
* are not (yet) known.
*/
json_t *contract_terms;
/**
+ * Wire details for the payment, to be returned to the query. NULL
+ * if not available.
+ */
+ json_t *wire_details;
+
+ /**
+ * Hash over the @e contract_terms.
+ */
+ struct GNUNET_HashCode h_contract_terms;
+
+ /**
* Serial ID of the order.
*/
uint64_t order_serial;
@@ -71,6 +174,24 @@ struct GetOrderRequestContext
struct TALER_Amount refund_amount;
/**
+ * Exchange HTTP error code encountered while trying to determine wire transfer
+ * details. #TALER_EC_NONE for no error encountered.
+ */
+ unsigned int exchange_hc;
+
+ /**
+ * Exchange error code encountered while trying to determine wire transfer
+ * details. #TALER_EC_NONE for no error encountered.
+ */
+ enum TALER_ErrorCode exchange_ec;
+
+ /**
+ * Error code encountered while trying to determine wire transfer
+ * details. #TALER_EC_NONE for no error encountered.
+ */
+ enum TALER_ErrorCode wire_ec;
+
+ /**
* Set to true if this payment has been refunded and
* @e refund_amount is initialized.
*/
@@ -86,6 +207,236 @@ struct GetOrderRequestContext
/**
+ * Head of list of suspended requests waiting on the exchange.
+ */
+static struct GetOrderRequestContext *gorc_head;
+
+/**
+ * Tail of list of suspended requests waiting on the exchange.
+ */
+static struct GetOrderRequestContext *gorc_tail;
+
+
+/**
+ *
+ * @param gorc request to resume
+ * @param ec error code for the request, #TALER_EC_NONE on success
+ * @param exchange_hr details from exchange, NULL if exchange is blameless
+ */
+static void
+gorc_resume (struct GetOrderRequestContext *gorc,
+ enum TALER_ErrorCode ec,
+ const struct TALER_EXCHANGE_HttpResponse *exchange_hr)
+{
+ 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;
+ }
+ }
+ GNUNET_CONTAINER_DLL_remove (gorc_head,
+ gorc_tail,
+ gorc);
+ MHD_resume_connection (gorc->sc.connection);
+ GNUNET_CONTAINER_DLL_remove (gorc_head,
+ gorc_tail,
+ gorc);
+ gorc->wire_ec = ec;
+ if (NULL != exchange_hr)
+ {
+ gorc->exchange_hc = exchange_hr->http_status;
+ gorc->exchange_ec = exchange_hr->error_code;
+ }
+ MHD_resume_connection (gorc->sc.connection);
+}
+
+
+/**
+ * Timeout trying to get current wire transfer data from the exchange.
+ * Clean up and continue.
+ *
+ * @param cls closure, must be a `struct GetOrderRequestContext *`
+ */
+static void
+exchange_timeout_cb (void *cls)
+{
+ struct GetOrderRequestContext *gorc = cls;
+
+ gorc->tt = NULL;
+ gorc_resume (gorc,
+ 42, // FIXME: EC
+ NULL);
+}
+
+
+/**
+ * Function called with detailed wire transfer data.
+ *
+ * @param cls closure with a `struct TransferQuery *`
+ * @param hr HTTP response data
+ * @param dd details about the deposit (NULL on errors)
+ */
+static void
+deposit_get_cb (void *cls,
+ const struct TALER_EXCHANGE_HttpResponse *hr,
+ const struct TALER_EXCHANGE_DepositData *dd)
+{
+ struct TransferQuery *tq = cls;
+ struct GetOrderRequestContext *gorc = tq->gorc;
+
+ GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
+ gorc->tq_tail,
+ tq);
+ if (NULL == dd)
+ {
+ gorc_resume (gorc,
+ 42, // FIXME: EC
+ hr);
+ GNUNET_free (tq);
+ return;
+ }
+ else
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->insert_deposit_to_transfer (TMH_db->cls,
+ tq->deposit_serial,
+ dd);
+ if (qs < 0)
+ {
+ gorc_resume (gorc,
+ 42, // FIXME: EC
+ NULL);
+ GNUNET_free (tq);
+ return;
+ }
+ }
+ GNUNET_free (tq);
+ if (NULL != gorc->tq_head)
+ return;
+ /* *all* are done, resume! */
+ gorc_resume (gorc,
+ TALER_EC_NONE,
+ NULL);
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * operation.
+ *
+ * @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
+ */
+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)
+{
+ struct TransferQuery *tq = cls;
+ struct GetOrderRequestContext *gorc = tq->gorc;
+
+ tq->fo = NULL;
+ if (NULL == eh)
+ {
+ /* failed */
+ GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
+ gorc->tq_tail,
+ tq);
+ GNUNET_free (tq);
+ gorc_resume (gorc,
+ 42, // FIXME: EC!
+ hr);
+ 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_callback,
+ tq);
+ if (NULL == tq->dgh)
+ {
+ GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
+ gorc->tq_tail,
+ tq);
+ GNUNET_free (tq);
+ gorc_resume (gorc,
+ 42,
+ NULL);
+ }
+}
+
+
+/**
+ * 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 coin_pub public key of the deposited coin
+ */
+static void
+deposit_cb (void *cls,
+ uint64_t deposit_serial,
+ const char *exchange_url,
+ const struct GNUNET_HashCode *h_wire,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+ struct GetOrderRequestContext *gorc = cls;
+ struct TransferQuery *tq;
+
+ tq = GNUNET_new (struct TransferQuery);
+ tq->gorc = gorc;
+ 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->fo = TMH_EXCHANGES_find_exchange (exchange_url,
+ NULL,
+ GNUNET_NO,
+ &exchange_found_cb,
+ tq);
+ if (NULL = tq->fo)
+ {
+ gorc_resume (gorc,
+ 42, // FIXME: EC
+ NULL);
+ }
+}
+
+
+/**
* Clean up the session state for a GET /private/order/ID request.
*
* @param cls closure, must be a `struct GetOrderRequestContext *`
@@ -97,6 +448,8 @@ gorc_cleanup (void *cls)
if (NULL != gorc->contract_terms)
json_decref (gorc->contract_terms);
+ GNUNET_assert (NULL == gorc->tt);
+ GNUNET_assert (NULL == gorc->fo);
GNUNET_free (gorc);
}
@@ -141,6 +494,30 @@ process_refunds_cb (void *cls,
/**
+ * Function called with available wire details, to be added to
+ * the response.
+ *
+ * @param cls a `struct GetOrderRequestContext`
+ */
+static void
+process_wire_details (void *cls,
+ ...)
+{
+ struct GetOrderRequestContext *gorc = cls;
+ json_t *wire_details = gorc->wire_details;
+
+
+ GNUNET_assert (0 ==
+ json_array_append_new (wire_details,
+ json_pack ("{s:s, s:b}",
+ "blah",
+ "stuff",
+ "FIXME",
+ false)));
+}
+
+
+/**
* Manages a GET /private/orders/ID call, checking the status of a payment and
* refunds and, if necessary, constructing the URL for a payment redirect URL.
*
@@ -235,9 +612,14 @@ TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh,
TALER_EC_CHECK_PAYMENT_DB_FETCH_CONTRACT_TERMS_ERROR,
"db error fetching contract terms");
}
- // FIXME: handle qs!
- // => mainly: if 0 return 404!
-
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_ORDERS_GET_ORDER_NOT_FOUND,
+ "Did not find order in DB");
+ }
/* extract the fulfillment URL from the contract terms! */
{
@@ -260,6 +642,19 @@ TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh,
"Merchant database error (contract terms corrupted)");
}
}
+ if (GNUNET_OK !=
+ TALER_JSON_hash (gorc->contract_terms,
+ &gorc->h_contract_terms))
+ {
+ GNUNET_break (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ORDERS_GET_FAILED_COMPUTE_PROPOSAL_HASH,
+ "Failed to hash contract terms"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
}
GNUNET_assert (NULL != gorc->contract_terms);
@@ -333,17 +728,35 @@ TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh,
(! wired) &&
gorc->transfer_status_requested)
{
- // FIXME: suspend connection, go to exhange to ask for the
- // wire transfer status, and resume once we have it!
- // (make sure to set gorc->transfer_status_requested to FALSE
- // while we are at it!)
- return MHD_YES;
+ /* suspend connection, wait for exchange to check wire transfer status there */
+ gorc->transfer_status_requested = false; /* only try ONCE */
+ TMH_db->lookup_deposits (TMH_db->cls,
+ gorc->order_serial,
+ &deposit_cb,
+ gorc);
+ if (NULL != gorc->tq)
+ {
+ GNUNET_CONTAINER_DLL_insert (gorc_head,
+ gorc_tail,
+ gorc);
+ gorc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
+ &exchange_timeout_cb,
+ gorc);
+ MHD_suspend_connection (connection);
+ return MHD_YES;
+ }
}
if ( (! paid) &&
(0 != gorc.sc.long_poll_timeout.abs_value_us) )
{
- // FIXME: suspend connection, wait for payment!
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending GET /private/orders/%s\n",
+ hc->infix);
+ TMH_long_poll_suspend (hc->infix,
+ hc->instance,
+ &gorc->sc,
+ NULL);
return MHD_YES;
}
@@ -372,9 +785,7 @@ TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh,
/* Accumulate refunds, if any. */
{
- struct GNUNET_HashCode h_contract_terms;
- // FIXME: init h_contract_terms!
qs = TMH_db->lookup_refunds (TMH_db->cls,
hc->instance.settings->id,
&h_contract_terms,
@@ -386,20 +797,20 @@ TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh,
GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
+ TALER_EC_ORDERS_GET_DB_FETCH_TRANSACTION_ERROR,
"Merchant database error");
}
} /* end of scope of 'paid/wired' */
/* Generate final reply, including wire details if we have them */
{
- json_t *wire_details = NULL;
-
+ gorc->wire_details = json_array_new ();
+ GNUNET_assert (NULL != gorc->wire_details);
#if FIXME
qs = TMH_db->select_wire_details_by_order (TMH_db->cls,
gorc->order_serial,
&process_wire_details,
- &wire_details);
+ gorc);
#endif
if (0 > qs)
{
@@ -409,6 +820,7 @@ TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh,
TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
"Merchant database error");
}
+ // FIXME: also return gorc->ec/exchange_(hc,ec) codes!
return TALER_MHD_reply_json_pack (connection,
MHD_HTTP_OK,
"{s:O, s:b, s:b, s:o?, s:o?}",
@@ -424,6 +836,6 @@ TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh,
&gorc->refund_amount)
: NULL,
"wire_details",
- wire_details);
+ gorc->wire_details);
}
}