From 78119045b5eaf622ac3752f6cc7f07d9cc5c2feb Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 4 Jun 2020 21:00:32 +0200 Subject: work on GET /private/orders/ID" --- .../taler-merchant-httpd_private-get-orders-ID.c | 444 ++++++++++++++++++++- 1 file changed, 428 insertions(+), 16 deletions(-) (limited to 'src/backend/taler-merchant-httpd_private-get-orders-ID.c') 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 #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. @@ -53,12 +115,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. */ @@ -70,6 +173,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. @@ -85,6 +206,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. * @@ -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); } @@ -140,6 +493,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); } } -- cgit v1.2.3