diff options
author | Christian Grothoff <christian@grothoff.org> | 2019-11-02 13:40:41 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2019-11-02 13:40:41 +0100 |
commit | ea48d9fc008e34cf954f497db635fe343ba902f0 (patch) | |
tree | 550fa4fc50400b7a5bff3d952a88697b358251bd | |
parent | ae06bf1930ab7196938dd1eaa7db8c8d51d94cf4 (diff) |
implement /public/poll-payment API, refactor to avoid code duplication
-rw-r--r-- | src/backend/Makefile.am | 21 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd.c | 81 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd.h | 31 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_check-payment.c | 102 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_poll-payment.c | 557 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_poll-payment.h | 47 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_tip-pickup.c | 4 |
7 files changed, 731 insertions, 112 deletions
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am index ec4b2046..966d8f41 100644 --- a/src/backend/Makefile.am +++ b/src/backend/Makefile.am @@ -14,26 +14,25 @@ bin_PROGRAMS = \ taler_merchant_httpd_SOURCES = \ taler-merchant-httpd.c taler-merchant-httpd.h \ - taler-merchant-httpd_parsing.c taler-merchant-httpd_parsing.h \ - taler-merchant-httpd_responses.c taler-merchant-httpd_responses.h \ - taler-merchant-httpd_mhd.c taler-merchant-httpd_mhd.h \ taler-merchant-httpd_auditors.c taler-merchant-httpd_auditors.h \ + taler-merchant-httpd_config.c taler-merchant-httpd_config.h \ + taler-merchant-httpd_check-payment.c taler-merchant-httpd_check-payment.h \ taler-merchant-httpd_exchanges.c taler-merchant-httpd_exchanges.h \ + taler-merchant-httpd_history.c taler-merchant-httpd_history.h \ + taler-merchant-httpd_mhd.c taler-merchant-httpd_mhd.h \ taler-merchant-httpd_order.c taler-merchant-httpd_order.h \ - taler-merchant-httpd_proposal.c taler-merchant-httpd_proposal.h \ + taler-merchant-httpd_parsing.c taler-merchant-httpd_parsing.h \ taler-merchant-httpd_pay.c taler-merchant-httpd_pay.h \ - taler-merchant-httpd_history.c taler-merchant-httpd_history.h \ + taler-merchant-httpd_poll-payment.c taler-merchant-httpd_poll-payment.h \ + taler-merchant-httpd_proposal.c taler-merchant-httpd_proposal.h \ + taler-merchant-httpd_refund.c taler-merchant-httpd_refund.h \ + taler-merchant-httpd_responses.c taler-merchant-httpd_responses.h \ taler-merchant-httpd_tip-authorize.c taler-merchant-httpd_tip-authorize.h \ taler-merchant-httpd_tip-pickup.c taler-merchant-httpd_tip-pickup.h \ taler-merchant-httpd_tip-query.c taler-merchant-httpd_tip-query.h \ taler-merchant-httpd_tip-reserve-helper.c taler-merchant-httpd_tip-reserve-helper.h \ taler-merchant-httpd_track-transaction.c taler-merchant-httpd_track-transaction.h \ - taler-merchant-httpd_track-transfer.c taler-merchant-httpd_track-transfer.h \ - taler-merchant-httpd_refund.c taler-merchant-httpd_refund.h \ - taler-merchant-httpd_check-payment.c taler-merchant-httpd_check-payment.h \ - taler-merchant-httpd_config.c taler-merchant-httpd_config.h - - + taler-merchant-httpd_track-transfer.c taler-merchant-httpd_track-transfer.h taler_merchant_httpd_LDADD = \ $(top_builddir)/src/backenddb/libtalermerchantdb.la \ -ltalerexchange \ diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index e06e1441..8abb4449 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -48,6 +48,7 @@ #include "taler-merchant-httpd_history.h" #include "taler-merchant-httpd_refund.h" #include "taler-merchant-httpd_check-payment.h" +#include "taler-merchant-httpd_poll-payment.h" #include "taler-merchant-httpd_config.h" /** @@ -168,6 +169,11 @@ struct GNUNET_CONTAINER_Heap *resume_timeout_heap; */ struct GNUNET_CONTAINER_MultiHashMap *payment_trigger_map; +/** + * Task responsible for timeouts in the #resume_timeout_heap. + */ +struct GNUNET_SCHEDULER_Task *resume_timeout_task; + /** * Return #GNUNET_YES if given a valid correlation ID and @@ -277,6 +283,73 @@ TMH_compute_pay_key (const char *order_id, /** + * 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" + * @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) +{ + const char *host; + const char *forwarded_host; + const char *uri_path; + const char *uri_instance_id; + const char *query; + char *result; + + host = MHD_lookup_connection_value (con, + MHD_HEADER_KIND, + "Host"); + forwarded_host = MHD_lookup_connection_value (con, + MHD_HEADER_KIND, + "X-Forwarded-Host"); + + uri_path = MHD_lookup_connection_value (con, + MHD_HEADER_KIND, + "X-Forwarded-Prefix"); + if (NULL == uri_path) + uri_path = "-"; + if (NULL != forwarded_host) + host = forwarded_host; + if (0 == strcmp (instance_id, + "default")) + uri_instance_id = "-"; + else + uri_instance_id = instance_id; + if (NULL == host) + { + /* Should never happen, at least the host header should be defined */ + GNUNET_break (0); + return NULL; + } + + if (GNUNET_YES == TALER_mhd_is_https (con)) + query = ""; + else + query = "?insecure=1"; + GNUNET_assert (NULL != order_id); + GNUNET_assert (0 < GNUNET_asprintf (&result, + "taler://pay/%s/%s/%s/%s%s%s%s", + host, + uri_path, + uri_instance_id, + order_id, + (NULL == session_id) ? "" : "/", + (NULL == session_id) ? "" : session_id, + query)); + return result; +} + + +/** * Shutdown task (magically invoked when the application is being * quit) * @@ -311,6 +384,11 @@ do_shutdown (void *cls) GNUNET_CONTAINER_heap_destroy (resume_timeout_heap); resume_timeout_heap = NULL; } + if (NULL != resume_timeout_task) + { + GNUNET_SCHEDULER_cancel (resume_timeout_task); + resume_timeout_task = NULL; + } if (NULL != mhd) { MHD_stop_daemon (mhd); @@ -1196,6 +1274,9 @@ url_handler (void *cls, { "/check-payment", MHD_HTTP_METHOD_GET, "text/plain", NULL, 0, &MH_handler_check_payment, MHD_HTTP_OK}, + { "/public/poll-payment", MHD_HTTP_METHOD_GET, "text/plain", + NULL, 0, + &MH_handler_poll_payment, MHD_HTTP_OK}, { "/config", MHD_HTTP_METHOD_GET, "text/plain", NULL, 0, &MH_handler_config, MHD_HTTP_OK}, diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h index bc53bbc6..53dedcb4 100644 --- a/src/backend/taler-merchant-httpd.h +++ b/src/backend/taler-merchant-httpd.h @@ -277,14 +277,20 @@ struct TMH_SuspendedConnection struct MHD_Connection *con; /** + * Associated heap node. + */ + struct GNUNET_CONTAINER_HeapNode *hn; + + /** * Key of this entry in the #payment_trigger_map */ struct GNUNET_HashCode key; /** - * Associated heap node. + * At what time does this request expire? If set in the future, we + * may wait this long for a payment to arrive before responding. */ - struct GNUNET_CONTAINER_HeapNode *hn; + struct GNUNET_TIME_Absolute long_poll_timeout; }; @@ -320,6 +326,11 @@ extern unsigned long long default_wire_fee_amortization; extern struct GNUNET_CONTAINER_Heap *resume_timeout_heap; /** + * Task responsible for timeouts in the #resume_timeout_heap. + */ +extern struct GNUNET_SCHEDULER_Task *resume_timeout_task; + +/** * Hash map from H(order_id,merchant_pub) to `struct TMH_SuspendedConnection` * entries to resume when a payment is made for the given order. */ @@ -397,4 +408,20 @@ TMH_compute_pay_key (const char *order_id, struct GNUNET_HashCode *key); +/** + * 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" + * @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); + #endif diff --git a/src/backend/taler-merchant-httpd_check-payment.c b/src/backend/taler-merchant-httpd_check-payment.c index ac73a90b..fad3fdc6 100644 --- a/src/backend/taler-merchant-httpd_check-payment.c +++ b/src/backend/taler-merchant-httpd_check-payment.c @@ -48,9 +48,6 @@ struct CheckPaymentRequestContext */ struct TM_HandlerContext hc; - /** - * Connection we are processing a request for. - */ struct MHD_Connection *connection; /** @@ -85,12 +82,6 @@ struct CheckPaymentRequestContext const char *fulfillment_url; /** - * At what time does this request expire? If set in the future, we - * may wait this long for a payment to arrive before responding. - */ - struct GNUNET_TIME_Absolute long_poll_timeout; - - /** * Contract terms of the payment we are checking. NULL when they * are not (yet) known. */ @@ -142,67 +133,6 @@ cprc_cleanup (struct TM_HandlerContext *hc) /** - * Make a taler://pay URI - * - * @param cprc payment request context - * @returns the URI, must be freed with #GNUNET_free - */ -static char * -make_taler_pay_uri (const struct CheckPaymentRequestContext *cprc) -{ - const char *host; - const char *forwarded_host; - const char *uri_path; - const char *uri_instance_id; - const char *query; - char *result; - - host = MHD_lookup_connection_value (cprc->connection, - MHD_HEADER_KIND, - "Host"); - forwarded_host = MHD_lookup_connection_value (cprc->connection, - MHD_HEADER_KIND, - "X-Forwarded-Host"); - - uri_path = MHD_lookup_connection_value (cprc->connection, - MHD_HEADER_KIND, - "X-Forwarded-Prefix"); - if (NULL == uri_path) - uri_path = "-"; - if (NULL != forwarded_host) - host = forwarded_host; - if (0 == strcmp (cprc->mi->id, - "default")) - uri_instance_id = "-"; - else - uri_instance_id = cprc->mi->id; - if (NULL == host) - { - /* Should never happen, at least the host header should be defined */ - GNUNET_break (0); - return NULL; - } - - if (GNUNET_YES == TALER_mhd_is_https (cprc->connection)) - query = ""; - else - query = "?insecure=1"; - GNUNET_assert (NULL != cprc->order_id); - GNUNET_assert (0 < GNUNET_asprintf (&result, - "taler://pay/%s/%s/%s/%s%s%s%s", - host, - uri_path, - uri_instance_id, - cprc->order_id, - (cprc->session_id == NULL) ? "" : "/", - (cprc->session_id == NULL) ? "" : - cprc->session_id, - query)); - return result; -} - - -/** * Function called with information about a refund. * It is responsible for summing up the refund amount. * @@ -274,7 +204,10 @@ send_pay_request (const struct CheckPaymentRequestContext *cprc) "db error fetching pay session info"); } } - taler_pay_uri = make_taler_pay_uri (cprc); + taler_pay_uri = TMH_make_taler_pay_uri (cprc->connection, + cprc->order_id, + cprc->session_id, + cprc->mi->id); ret = TMH_RESPONSE_reply_json_pack (cprc->connection, MHD_HTTP_OK, "{s:s, s:s, s:b, s:s?}", @@ -413,8 +346,6 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh, if (NULL == cprc) { /* First time here, parse request and check order is known */ - const char *long_poll_timeout_s; - cprc = GNUNET_new (struct CheckPaymentRequestContext); cprc->hc.cc = &cprc_cleanup; cprc->ret = GNUNET_SYSERR; @@ -433,31 +364,6 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh, TALER_EC_PARAMETER_MISSING, "order_id required"); } - long_poll_timeout_s = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "timeout"); - if (NULL != long_poll_timeout_s) - { - unsigned int timeout; - - if (1 != sscanf (long_poll_timeout_s, - "%u", - &timeout)) - { - GNUNET_break_op (0); - return TMH_RESPONSE_reply_bad_request (connection, - TALER_EC_PARAMETER_MALFORMED, - "timeout must be non-negative number"); - } - cprc->long_poll_timeout - = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply ( - GNUNET_TIME_UNIT_SECONDS, - timeout)); - } - else - { - cprc->long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS; - } cprc->contract_url = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "contract_url"); diff --git a/src/backend/taler-merchant-httpd_poll-payment.c b/src/backend/taler-merchant-httpd_poll-payment.c new file mode 100644 index 00000000..945a356f --- /dev/null +++ b/src/backend/taler-merchant-httpd_poll-payment.c @@ -0,0 +1,557 @@ +/* + This file is part of TALER + (C) 2017, 2019 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file backend/taler-merchant-httpd_poll-payment.c + * @brief implementation of /public/poll-payment handler + * @author Florian Dold + * @author Christian Grothoff + */ +#include "platform.h" +#include <string.h> +#include <microhttpd.h> +#include <jansson.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> +#include "taler-merchant-httpd.h" +#include "taler-merchant-httpd_mhd.h" +#include "taler-merchant-httpd_parsing.h" +#include "taler-merchant-httpd_exchanges.h" +#include "taler-merchant-httpd_responses.h" +#include "taler-merchant-httpd_poll-payment.h" + +/** + * Maximum number of retries for database operations. + */ +#define MAX_RETRIES 5 + + +/** + * Data structure we keep for a check payment request. + */ +struct PollPaymentRequestContext +{ + /** + * Must be first for #handle_mhd_completion_callback. + */ + struct TM_HandlerContext hc; + + /** + * Entry in the #resume_timeout_heap for this check payment, if we are + * suspended. + */ + struct TMH_SuspendedConnection sc; + + /** + * Which merchant instance is this for? + */ + struct MerchantInstance *mi; + + /** + * URL where the final contract can be found for this payment. + */ + char *final_contract_url; + + /** + * order ID for the payment + */ + const char *order_id; + + /** + * Where to get the contract + */ + const char *contract_url; + + /** + * fulfillment URL of the contract (valid as long as + * @e contract_terms is valid). + */ + const char *fulfillment_url; + + /** + * session of the client + */ + const char *session_id; + + /** + * Contract terms of the payment we are checking. NULL when they + * are not (yet) known. + */ + json_t *contract_terms; + + /** + * Hash of @e contract_terms, set only once @e contract_terms + * is available. + */ + struct GNUNET_HashCode h_contract_terms; + + /** + * Total refunds granted for this payment. Only initialized + * if @e refunded is set to #GNUNET_YES. + */ + struct TALER_Amount refund_amount; + + /** + * Set to #GNUNET_YES if this payment has been refunded and + * @e refund_amount is initialized. + */ + int refunded; + + /** + * Initially #GNUNET_SYSERR. If we queued a response, set to the + * result code (i.e. #MHD_YES or #MHD_NO). + */ + int ret; + +}; + + +/** + * Clean up the session state for a check payment request. + * + * @param hc must be a `struct PollPaymentRequestContext *` + */ +static void +pprc_cleanup (struct TM_HandlerContext *hc) +{ + struct PollPaymentRequestContext *pprc = (struct + PollPaymentRequestContext *) hc; + + if (NULL != pprc->contract_terms) + json_decref (pprc->contract_terms); + GNUNET_free_non_null (pprc->final_contract_url); + GNUNET_free (pprc); +} + + +/** + * Function called with information about a refund. + * It is responsible for summing up the refund amount. + * + * @param cls closure + * @param coin_pub public coin from which the refund comes from + * @param rtransaction_id identificator of the refund + * @param reason human-readable explaination of the refund + * @param refund_amount refund amount which is being taken from coin_pub + * @param refund_fee cost of this refund operation + */ +static void +process_refunds_cb (void *cls, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + uint64_t rtransaction_id, + const char *reason, + const struct TALER_Amount *refund_amount, + const struct TALER_Amount *refund_fee) +{ + struct PollPaymentRequestContext *pprc = cls; + + if (pprc->refunded) + { + GNUNET_assert (GNUNET_SYSERR != + TALER_amount_add (&pprc->refund_amount, + &pprc->refund_amount, + refund_amount)); + return; + } + pprc->refund_amount = *refund_amount; + pprc->refunded = GNUNET_YES; +} + + +/** + * Resume processing all suspended connections past timeout. + * + * @param cls unused + */ +static void +do_resume (void *cls) +{ + struct TMH_SuspendedConnection *sc; + + (void) cls; + resume_timeout_task = NULL; + while (1) + { + sc = GNUNET_CONTAINER_heap_peek (resume_timeout_heap); + if (NULL == sc) + return; + if (0 != + GNUNET_TIME_absolute_get_remaining ( + sc->long_poll_timeout).rel_value_us) + break; + GNUNET_assert (sc == + GNUNET_CONTAINER_heap_remove_root (resume_timeout_heap)); + sc->hn = NULL; + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multihashmap_remove (payment_trigger_map, + &sc->key, + sc)); + MHD_resume_connection (sc->con); + } + resume_timeout_task = GNUNET_SCHEDULER_add_at (sc->long_poll_timeout, + &do_resume, + NULL); +} + + +/** + * The client did not yet pay, send it the payment request. + * + * @param pprc check pay request context + * @return #MHD_YES on success + */ +static int +send_pay_request (struct PollPaymentRequestContext *pprc) +{ + int ret; + char *already_paid_order_id = NULL; + char *taler_pay_uri; + + if (0 != + GNUNET_TIME_absolute_get_remaining ( + pprc->sc.long_poll_timeout).rel_value_us) + { + struct TMH_SuspendedConnection *sc; + + /* long polling: do not queue a response, suspend connection instead */ + TMH_compute_pay_key (pprc->order_id, + &pprc->mi->pubkey, + &pprc->sc.key); + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put (payment_trigger_map, + &pprc->sc.key, + &pprc->sc, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE)); + pprc->sc.hn = GNUNET_CONTAINER_heap_insert (resume_timeout_heap, + &pprc->sc, + pprc->sc.long_poll_timeout. + abs_value_us); + MHD_suspend_connection (pprc->sc.con); + if (NULL != resume_timeout_task) + { + GNUNET_SCHEDULER_cancel (resume_timeout_task); + resume_timeout_task = NULL; + } + sc = GNUNET_CONTAINER_heap_peek (resume_timeout_heap); + resume_timeout_task = GNUNET_SCHEDULER_add_at (sc->long_poll_timeout, + &do_resume, + NULL); + return MHD_YES; + } + + /* Check if resource_id has been paid for in the same session + * with another order_id. + */ + if ( (NULL != pprc->session_id) && + (NULL != pprc->fulfillment_url) ) + { + enum GNUNET_DB_QueryStatus qs; + + qs = db->find_session_info (db->cls, + &already_paid_order_id, + pprc->session_id, + pprc->fulfillment_url, + &pprc->mi->pubkey); + 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 TMH_RESPONSE_reply_internal_error (pprc->sc.con, + TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR, + "db error fetching pay session info"); + } + } + taler_pay_uri = TMH_make_taler_pay_uri (pprc->sc.con, + pprc->order_id, + pprc->session_id, + pprc->mi->id); + ret = TMH_RESPONSE_reply_json_pack (pprc->sc.con, + MHD_HTTP_OK, + "{s:s, s:s, s:b, s:s?}", + "taler_pay_uri", taler_pay_uri, + "contract_url", pprc->final_contract_url, + "paid", 0, + "already_paid_order_id", + already_paid_order_id); + GNUNET_free (taler_pay_uri); + GNUNET_free_non_null (already_paid_order_id); + return ret; +} + + +/** + * Manages a /public/poll-payment call, checking the status + * of a payment and, if necessary, constructing the URL + * for a payment redirect URL. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @param mi merchant backend instance, never NULL + * @return MHD result code + */ +int +MH_handler_poll_payment (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size, + struct MerchantInstance *mi) +{ + struct PollPaymentRequestContext *pprc = *connection_cls; + enum GNUNET_DB_QueryStatus qs; + int ret; + + if (NULL == pprc) + { + /* First time here, parse request and check order is known */ + const char *long_poll_timeout_s; + const char *cts; + + pprc = GNUNET_new (struct PollPaymentRequestContext); + pprc->hc.cc = &pprc_cleanup; + pprc->ret = GNUNET_SYSERR; + pprc->sc.con = connection; + pprc->mi = mi; + *connection_cls = pprc; + + pprc->order_id = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "order_id"); + if (NULL == pprc->order_id) + { + /* order_id is required but missing */ + GNUNET_break_op (0); + return TMH_RESPONSE_reply_bad_request (connection, + TALER_EC_PARAMETER_MISSING, + "order_id required"); + } + cts = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "h_contract"); + if (NULL == cts) + { + /* h_contract required but missing */ + GNUNET_break_op (0); + return TMH_RESPONSE_reply_bad_request (connection, + TALER_EC_PARAMETER_MISSING, + "h_contract required"); + } + if (GNUNET_OK != + GNUNET_CRYPTO_hash_from_string (cts, + &pprc->h_contract_terms)) + { + /* cts has wrong encoding */ + GNUNET_break_op (0); + return TMH_RESPONSE_reply_bad_request (connection, + TALER_EC_PARAMETER_MALFORMED, + "h_contract malformed"); + } + long_poll_timeout_s = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "timeout"); + if (NULL != long_poll_timeout_s) + { + unsigned int timeout; + + if (1 != sscanf (long_poll_timeout_s, + "%u", + &timeout)) + { + GNUNET_break_op (0); + return TMH_RESPONSE_reply_bad_request (connection, + TALER_EC_PARAMETER_MALFORMED, + "timeout must be non-negative number"); + } + pprc->sc.long_poll_timeout + = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply ( + GNUNET_TIME_UNIT_SECONDS, + timeout)); + } + else + { + pprc->sc.long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS; + } + pprc->contract_url = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "contract_url"); + if (NULL == pprc->contract_url) + { + pprc->final_contract_url = TALER_url_absolute_mhd (connection, + "/public/proposal", + "instance", mi->id, + "order_id", + pprc->order_id, + NULL); + GNUNET_assert (NULL != pprc->final_contract_url); + } + else + { + pprc->final_contract_url = GNUNET_strdup (pprc->contract_url); + } + pprc->session_id = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "session_id"); + + /* obtain contract terms, indirectly checking that the client's contract + terms hash is actually valid and known. */ + db->preflight (db->cls); + qs = db->find_contract_terms_from_hash (db->cls, + &pprc->contract_terms, + &pprc->h_contract_terms, + &mi->pubkey); + if (0 > qs) + { + /* Always report on hard error as well to enable diagnostics */ + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + return TMH_RESPONSE_reply_internal_error (connection, + TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR, + "Merchant database error"); + } + if (0 == qs) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Contract unknown\n"); + return TMH_RESPONSE_reply_not_found (connection, + TALER_EC_POLL_PAYMENT_CONTRACT_NOT_FOUND, + "Given order_id doesn't map to any proposal"); + } + GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); + + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("fulfillment_url", + &pprc->fulfillment_url), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (pprc->contract_terms, + spec, + NULL, NULL)) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_error (connection, + TALER_EC_CHECK_PAYMENT_DB_FETCH_CONTRACT_TERMS_ERROR, + "Merchant database error (contract terms corrupted)"); + } + } + + } /* end of first-time initialization / sanity checks */ + + + db->preflight (db->cls); + + /* Check if the order has been paid for. */ + if (NULL != pprc->session_id) + { + /* Check if paid within a session. */ + char *already_paid_order_id = NULL; + + qs = db->find_session_info (db->cls, + &already_paid_order_id, + pprc->session_id, + pprc->fulfillment_url, + &mi->pubkey); + 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 TMH_RESPONSE_reply_internal_error (connection, + TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR, + "db error fetching pay session info"); + } + else if (0 == qs) + { + ret = send_pay_request (pprc); + GNUNET_free_non_null (already_paid_order_id); + return ret; + } + GNUNET_break (1 == qs); + GNUNET_break (0 == strcmp (pprc->order_id, + already_paid_order_id)); + GNUNET_free_non_null (already_paid_order_id); + } + else + { + /* Check if paid regardless of session. */ + json_t *xcontract_terms = NULL; + + qs = db->find_paid_contract_terms_from_hash (db->cls, + &xcontract_terms, + &pprc->h_contract_terms, + &mi->pubkey); + if (0 > qs) + { + /* Always report on hard error as well to enable diagnostics */ + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + return TMH_RESPONSE_reply_internal_error (connection, + TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR, + "Merchant database error"); + } + if (0 == qs) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "not paid yet\n"); + return send_pay_request (pprc); + } + GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); + GNUNET_assert (NULL != xcontract_terms); + json_decref (xcontract_terms); + } + + /* Accumulate refunds, if any. */ + for (unsigned int i = 0; i<MAX_RETRIES; i++) + { + qs = db->get_refunds_from_contract_terms_hash (db->cls, + &mi->pubkey, + &pprc->h_contract_terms, + &process_refunds_cb, + pprc); + if (GNUNET_DB_STATUS_SOFT_ERROR != qs) + break; + } + if (0 > qs) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Database hard error on refunds_from_contract_terms_hash lookup: %s\n", + GNUNET_h2s (&pprc->h_contract_terms)); + return TMH_RESPONSE_reply_internal_error (connection, + TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR, + "Merchant database error"); + } + if (pprc->refunded) + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:b, s:b, s:o}", + "paid", 1, + "refunded", pprc->refunded, + "refund_amount", + TALER_JSON_from_amount ( + &pprc->refund_amount)); + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:b, s:b }", + "paid", 1, + "refunded", 0); +} diff --git a/src/backend/taler-merchant-httpd_poll-payment.h b/src/backend/taler-merchant-httpd_poll-payment.h new file mode 100644 index 00000000..e9f54c26 --- /dev/null +++ b/src/backend/taler-merchant-httpd_poll-payment.h @@ -0,0 +1,47 @@ +/* + This file is part of TALER + (C) 2017 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file backend/taler-merchant-httpd_poll-payment.h + * @brief headers for /public/poll-payment handler + * @author Christian Grothoff + * @author Florian Dold + */ +#ifndef TALER_MERCHANT_HTTPD_POLL_PAYMENT_H +#define TALER_MERCHANT_HTTPD_POLL_PAYMENT_H +#include <microhttpd.h> +#include "taler-merchant-httpd.h" + +/** + * Manages a /public/poll-payment call, checking the status + * of a payment. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @param mi merchant backend instance, never NULL + * @return MHD result code + */ +int +MH_handler_poll_payment (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size, + struct MerchantInstance *mi); + +#endif diff --git a/src/backend/taler-merchant-httpd_tip-pickup.c b/src/backend/taler-merchant-httpd_tip-pickup.c index bead3419..d53596cc 100644 --- a/src/backend/taler-merchant-httpd_tip-pickup.c +++ b/src/backend/taler-merchant-httpd_tip-pickup.c @@ -649,7 +649,9 @@ MH_handler_tip_pickup_get (struct TMH_RequestHandler *rh, "tip_id required"); } - if (GNUNET_OK != GNUNET_CRYPTO_hash_from_string (tip_id_str, &tip_id)) + if (GNUNET_OK != + GNUNET_CRYPTO_hash_from_string (tip_id_str, + &tip_id)) { /* tip_id has wrong encoding */ GNUNET_break_op (0); |