merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit 552e5882589fe22838bd2519a2491d4b4d2cdce3
parent 29976e055994339b651c9a26e3a1adb873625f22
Author: Christian Grothoff <christian@grothoff.org>
Date:   Tue, 31 May 2016 17:31:59 +0200

Merge branch 'master' of git+ssh://taler.net/var/git/merchant

Diffstat:
Msrc/backend/Makefile.am | 3++-
Msrc/backend/merchant.conf | 3++-
Msrc/backend/taler-merchant-httpd.c | 26+++++++++++++++-----------
Msrc/backend/taler-merchant-httpd.h | 6+++---
Msrc/backend/taler-merchant-httpd_contract.c | 25+------------------------
Msrc/backend/taler-merchant-httpd_exchanges.c | 125++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/backend/taler-merchant-httpd_mhd.c | 8++++----
Msrc/backend/taler-merchant-httpd_parsing.c | 55+++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/backend/taler-merchant-httpd_pay.c | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/backend/taler-merchant-httpd_responses.c | 36++++++++++++++++++++++++++++++++++++
Msrc/backend/taler-merchant-httpd_responses.h | 21+++++++++++++++++++++
Asrc/backend/taler-merchant-httpd_track.c | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/taler-merchant-httpd_track.h | 41+++++++++++++++++++++++++++++++++++++++++
Dsrc/backend/taler-merchant-httpd_util.c | 105-------------------------------------------------------------------------------
Dsrc/backend/taler-merchant-httpd_util.h | 43-------------------------------------------
Msrc/include/taler_merchant_service.h | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/lib/Makefile.am | 3++-
Msrc/lib/merchant_api_contract.c | 1-
Msrc/lib/merchant_api_pay.c | 14+++++++-------
Asrc/lib/merchant_api_track.c | 188+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/test_merchant_api.c | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/tests/test_contract.c | 18+++++++++++-------
22 files changed, 825 insertions(+), 331 deletions(-)

diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am @@ -21,7 +21,8 @@ taler_merchant_httpd_SOURCES = \ taler-merchant-httpd_exchanges.c taler-merchant-httpd_exchanges.h \ taler-merchant-httpd_contract.c taler-merchant-httpd_contract.h \ taler-merchant-httpd_pay.c taler-merchant-httpd_pay.h \ - taler-merchant-httpd_util.c taler-merchant-httpd_util.h + taler-merchant-httpd_track.c taler-merchant-httpd_track.h + taler_merchant_httpd_LDADD = \ $(top_srcdir)/src/backenddb/libtalermerchantdb.la \ diff --git a/src/backend/merchant.conf b/src/backend/merchant.conf @@ -20,10 +20,11 @@ KEYFILE = ${TALER_DATA_HOME}/merchant/merchant.priv DB = postgres # Which wireformat does this merchant use? (test/sepa/etc.) -# WIREFORMAT = "test" +WIREFORMAT = test # Determines which wire plugin will be used. We currently only # support one wire plugin at a time! +WIRE_TRANSFER_DELAY = 3 week # Configuration for postgres database. [merchantdb-postgres] diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c @@ -37,7 +37,7 @@ #include "taler-merchant-httpd_exchanges.h" #include "taler-merchant-httpd_contract.h" #include "taler-merchant-httpd_pay.h" -#include "taler-merchant-httpd_util.h" +#include "taler-merchant-httpd_track.h" /** @@ -80,7 +80,7 @@ static char *keyfile; * This value tells the exchange by which date this merchant would like * to receive the funds for a deposited payment */ -struct GNUNET_TIME_Relative edate_delay; +struct GNUNET_TIME_Relative wire_transfer_delay; /** * Which currency is supported by this merchant? @@ -174,12 +174,6 @@ url_handler (void *cls, { "/", MHD_HTTP_METHOD_GET, "text/plain", "Hello, I'm a merchant's Taler backend. This HTTP server is not for humans.\n", 0, &TMH_MHD_handler_static_response, MHD_HTTP_OK }, - { "/hash-contract", MHD_HTTP_METHOD_POST, "application/json", - NULL, 0, - &MH_handler_hash_contract, MHD_HTTP_OK }, - { "/hash-contract", NULL, "text/plain", - "Only POST is allowed", 0, - &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, { "/contract", MHD_HTTP_METHOD_POST, "application/json", NULL, 0, &MH_handler_contract, MHD_HTTP_OK }, @@ -193,6 +187,12 @@ url_handler (void *cls, { "/pay", NULL, "text/plain", "Only POST is allowed", 0, &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/track/deposit", MHD_HTTP_METHOD_GET, "application/json", + NULL, 0, + &MH_handler_track_deposit, MHD_HTTP_OK}, + { "/track/deposit", NULL, "text/plain", + "Only GET is allowed", 0, + &TMH_MHD_handler_static_response, MHD_HTTP_OK}, {NULL, NULL, NULL, NULL, 0, 0 } }; @@ -483,6 +483,10 @@ run (void *cls, result = GNUNET_SYSERR; GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-merchant-httpd", + "INFO", + NULL)); if (GNUNET_SYSERR == TMH_EXCHANGES_init (config)) { @@ -511,12 +515,12 @@ run (void *cls, if (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_time (config, "merchant", - "EDATE", - &edate_delay)) + "WIRE_TRANSFER_DELAY", + &wire_transfer_delay)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "merchant", - "EDATE"); + "WIRE_TRANSFER_DELAY"); GNUNET_SCHEDULER_shutdown (); return; } diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h @@ -179,10 +179,10 @@ extern struct TALER_MERCHANTDB_Plugin *db; /** * If the frontend does NOT specify an execution date, how long should * we tell the exchange to wait to aggregate transactions before - * executing? This delay is added to the current time when we - * generate the advisory execution time for the exchange. + * executing the wire transfer? This delay is added to the current + * time when we generate the advisory execution time for the exchange. */ -extern struct GNUNET_TIME_Relative edate_delay; +extern struct GNUNET_TIME_Relative wire_transfer_delay; /** * Kick MHD to run now, to be called after MHD_resume_connection(). diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c @@ -57,7 +57,7 @@ check_products (json_t *products) struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("description", &description), /* FIXME: there are other fields in the product specification - that rre currently not labeled as optional. Maybe check + that are currently not labeled as optional. Maybe check those as well, or make them truly optional. */ GNUNET_JSON_spec_end() }; @@ -187,29 +187,6 @@ MH_handler_contract (struct TMH_RequestHandler *rh, "products in contract request malformed"); } - /* Check if this transaction ID erroneously corresponds to a - contract that already paid, in which case we should refuse - to sign it again (frontend buggy, it should use a fresh - transaction ID each time)! */ - if (GNUNET_OK == - db->check_payment (db->cls, - transaction_id)) - { - struct MHD_Response *resp; - int ret; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Transaction %llu already paid in the past, refusing to sign!\n", - (unsigned long long) transaction_id); - resp = MHD_create_response_from_buffer (strlen ("Duplicate transaction ID!"), - "Duplicate transaction ID!", - MHD_RESPMEM_PERSISTENT); - ret = MHD_queue_response (connection, - MHD_HTTP_FORBIDDEN, - resp); - MHD_destroy_response (resp); - return ret; - } /* add fields to the contract that the backend should provide */ json_object_set (jcontract, diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c @@ -24,10 +24,26 @@ #include "taler-merchant-httpd_exchanges.h" + +/** + * Delay after which we'll re-fetch key information from the exchange. + */ +#define RELOAD_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 2) + /** - * How often do we retry fetching /keys? + * Threshold after which exponential backoff should not increase. */ -#define KEYS_RETRY_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10) +#define RETRY_BACKOFF_THRESHOLD GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60) + + +/** + * Perform our exponential back-off calculation, starting at 1 ms + * and then going by a factor of 2 up unto a maximum of RETRY_BACKOFF_THRESHOLD. + * + * @param r current backoff time, initially zero + */ +#define RETRY_BACKOFF(r) GNUNET_TIME_relative_min (RETRY_BACKOFF_THRESHOLD, \ + GNUNET_TIME_relative_multiply (GNUNET_TIME_relative_max (GNUNET_TIME_UNIT_MILLISECONDS, (r)), 2)); /** @@ -68,7 +84,8 @@ struct TMH_EXCHANGES_FindOperation struct Exchange *my_exchange; /** - * Task scheduled to asynchrnously return the result. + * Task scheduled to asynchronously return the result to + * the find continuation. */ struct GNUNET_SCHEDULER_Task *at; @@ -118,9 +135,9 @@ struct Exchange struct TALER_MasterPublicKeyP master_pub; /** - * At what time should we try to fetch /keys again? + * How long should we wait between the next retry? */ - struct GNUNET_TIME_Absolute retry_time; + struct GNUNET_TIME_Relative retry_delay; /** * Task where we retry fetching /keys from the exchange. @@ -128,8 +145,9 @@ struct Exchange struct GNUNET_SCHEDULER_Task *retry_task; /** - * Flag which indicates whether some HTTP transfer between - * this merchant and the exchange is still ongoing + * #GNUNET_YES to indicate that there is an ongoing + * transfer we are waiting for, + * #GNUNET_NO to indicate that key data is up-to-date. */ int pending; @@ -201,12 +219,16 @@ retry_exchange (void *cls) { struct Exchange *exchange = cls; + /* might be a scheduled reload and not our first attempt */ exchange->retry_task = NULL; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting to exchange exchange %s in retry_exchange\n", exchange->uri); - - exchange->pending = GNUNET_SYSERR; /* failed hard */ + if (NULL != exchange->conn) + { + TALER_EXCHANGE_disconnect (exchange->conn); + exchange->conn = NULL; + } exchange->conn = TALER_EXCHANGE_connect (merchant_curl_ctx, exchange->uri, &keys_mgmt_cb, @@ -236,39 +258,41 @@ keys_mgmt_cb (void *cls, { struct Exchange *exchange = cls; struct TMH_EXCHANGES_FindOperation *fo; + struct GNUNET_TIME_Absolute expire; + struct GNUNET_TIME_Relative delay; - if (NULL != keys) - { - exchange->pending = GNUNET_NO; - } - else + if (NULL == keys) { + exchange->pending = GNUNET_YES; + exchange->retry_delay = RETRY_BACKOFF (exchange->retry_delay); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to fetch /keys from `%s'\n", - exchange->uri); - TALER_EXCHANGE_disconnect (exchange->conn); - exchange->conn = NULL; - exchange->retry_time = GNUNET_TIME_relative_to_absolute (KEYS_RETRY_FREQ); - /* Always retry trusted exchanges in the background, so that we don't have - * to wait for a customer to trigger it and thus delay his response */ - if (GNUNET_YES == exchange->trusted) - { - exchange->retry_task = GNUNET_SCHEDULER_add_delayed (KEYS_RETRY_FREQ, - &retry_exchange, - exchange); - } - else - { - exchange->pending = GNUNET_SYSERR; /* failed hard */ - } + "Failed to fetch /keys from `%s', retrying in %s\n", + exchange->uri, + GNUNET_STRINGS_relative_time_to_string (exchange->retry_delay, + GNUNET_YES)); + exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay, + &retry_exchange, + exchange); + return; } + expire = TALER_EXCHANGE_check_keys_current (exchange->conn); + if (0 == expire.abs_value_us) + delay = RELOAD_DELAY; + else + delay = GNUNET_TIME_absolute_get_remaining (expire); + exchange->retry_delay = GNUNET_TIME_UNIT_ZERO; + exchange->retry_task + = GNUNET_SCHEDULER_add_delayed (delay, + &retry_exchange, + exchange); + exchange->pending = GNUNET_NO; while (NULL != (fo = exchange->fo_head)) { GNUNET_CONTAINER_DLL_remove (exchange->fo_head, exchange->fo_tail, fo); fo->fc (fo->fc_cls, - (NULL != keys) ? exchange->conn : NULL, + exchange->conn, exchange->trusted); GNUNET_free (fo); } @@ -312,7 +336,7 @@ return_result (void *cls) */ struct TMH_EXCHANGES_FindOperation * TMH_EXCHANGES_find_exchange (const char *chosen_exchange, - TMH_EXCHANGES_FindContinuation fc, // process payment + TMH_EXCHANGES_FindContinuation fc, void *fc_cls) { struct Exchange *exchange; @@ -359,29 +383,6 @@ TMH_EXCHANGES_find_exchange (const char *chosen_exchange, chosen_exchange); } - if (GNUNET_SYSERR == exchange->pending) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Maybe retrying previously contacted exchange `%s'\n", - chosen_exchange); - /* check if we should resume this exchange */ - if (0 == GNUNET_TIME_absolute_get_remaining (exchange->retry_time).rel_value_us) - { - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Retrying exchange `%s'\n", - chosen_exchange); - exchange->pending = GNUNET_YES; - } - else - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Not retrying exchange `%s', too early\n", - chosen_exchange); - } - } - - fo = GNUNET_new (struct TMH_EXCHANGES_FindOperation); fo->fc = fc; fo->fc_cls = fc_cls; @@ -390,7 +391,7 @@ TMH_EXCHANGES_find_exchange (const char *chosen_exchange, exchange->fo_tail, fo); - if (GNUNET_YES != exchange->pending) // can post coins + if (GNUNET_YES != exchange->pending) { /* We are not currently waiting for a reply, immediately return result */ @@ -434,13 +435,14 @@ TMH_EXCHANGES_find_exchange_cancel (struct TMH_EXCHANGES_FindOperation *fo) /** * Function called on each configuration section. Finds sections - * about exchanges and parses the entries. + * about exchanges, parses the entries and tries to connect to + * it in order to fetch /keys. * * @param cls closure, with a `const struct GNUNET_CONFIGURATION_Handle *` * @param section name of the section */ static void -parse_exchanges (void *cls, +accept_exchanges (void *cls, const char *section) { const struct GNUNET_CONFIGURATION_Handle *cfg = cls; @@ -524,10 +526,11 @@ TMH_EXCHANGES_init (const struct GNUNET_CONFIGURATION_Handle *cfg) return GNUNET_SYSERR; } merchant_curl_rc = GNUNET_CURL_gnunet_rc_create (merchant_curl_ctx); + /* get exchanges from the merchant configuration and try to connect to them */ GNUNET_CONFIGURATION_iterate_sections (cfg, - &parse_exchanges, + &accept_exchanges, (void *) cfg); - /* build JSON with list of trusted exchanges */ + /* build JSON with list of trusted exchanges (will be included in contracts) */ trusted_exchanges = json_array (); for (exchange = exchange_head; NULL != exchange; exchange = exchange->next) { diff --git a/src/backend/taler-merchant-httpd_mhd.c b/src/backend/taler-merchant-httpd_mhd.c @@ -41,10 +41,10 @@ */ int TMH_MHD_handler_static_response (struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size) + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) { struct MHD_Response *response; int ret; diff --git a/src/backend/taler-merchant-httpd_parsing.c b/src/backend/taler-merchant-httpd_parsing.c @@ -27,12 +27,6 @@ #include "taler-merchant-httpd_parsing.h" #include "taler-merchant-httpd_responses.h" -/* Although the following declaration isn't in any case useful -to a merchant's activity, it's needed here to make the function -'TMH_PARSE_nagivate_json ()' compile fine; so its value will be -kept on some merchant's accepted currency. For multi currencies -merchants, that of course would require a patch */ -extern char *TMH_merchant_currency_string; /** * Initial size for POST request buffer. @@ -129,9 +123,9 @@ buffer_append (struct Buffer *buf, if (data_size + buf->fill > buf->alloc) { char *new_buf; - size_t new_size = buf->alloc; + size_t new_size = buf->alloc ? buf->alloc : 1; while (new_size < buf->fill + data_size) - new_size += 2; + new_size *= 2; if (new_size > max_size) return GNUNET_NO; new_buf = GNUNET_malloc (new_size); @@ -313,4 +307,49 @@ TMH_PARSE_json_data (struct MHD_Connection *connection, } + +/** + * Extract base32crockford encoded data from request. + * + * Queues an error response to the connection if the parameter is + * missing or invalid. + * + * @param connection the MHD connection + * @param param_name the name of the parameter with the key + * @param[out] out_data pointer to store the result + * @param out_size expected size of data + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is absent or malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +int +TMH_PARSE_mhd_request_arg_data (struct MHD_Connection *connection, + const char *param_name, + void *out_data, + size_t out_size) +{ + const char *str; + + str = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + param_name); + if (NULL == str) + { + return (MHD_NO == + TMH_RESPONSE_reply_arg_missing (connection, param_name)) + ? GNUNET_SYSERR : GNUNET_NO; + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (str, + strlen (str), + out_data, + out_size)) + return (MHD_NO == + TMH_RESPONSE_reply_arg_invalid (connection, param_name)) + ? GNUNET_SYSERR : GNUNET_NO; + return GNUNET_OK; +} + + /* end of taler-merchant-httpd_parsing.c */ diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c @@ -18,6 +18,7 @@ * @brief handling of /pay requests * @author Marcello Stanisci * @author Christian Grothoff + * @author Florian Dold */ #include "platform.h" #include <jansson.h> @@ -33,6 +34,12 @@ /** + * How long to wait before giving up processing with the exchange? + */ +#define PAY_TIMEOUT (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)) + + +/** * Information we keep for an individual call to the /pay handler. */ struct PayContext; @@ -175,11 +182,11 @@ struct PayContext struct GNUNET_HashCode h_contract; /** - * Execution date. How soon would the merchant like the - * transaction to be executed? (Can be given by the frontend - * or be determined by our configuration via #edate_delay.) + * Wire transfer deadline. How soon would the merchant like the + * wire transfer to be executed? (Can be given by the frontend + * or be determined by our configuration via #wire_transfer_delay.) */ - struct GNUNET_TIME_Absolute edate; + struct GNUNET_TIME_Absolute wire_transfer_deadline; /** * Response to return, NULL if we don't have one yet. @@ -206,6 +213,13 @@ struct PayContext */ unsigned int response_code; + /** + * Task called when the (suspended) processing for + * the /pay request times out. + * Happens when we don't get a response from the exchange. + */ + struct GNUNET_SCHEDULER_Task *timeout_task; + }; @@ -228,10 +242,16 @@ resume_pay_with_response (struct PayContext *pc, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Resuming /pay handling as exchange interaction is done (%u)\n", response_code); + if (NULL != pc->timeout_task) + { + GNUNET_SCHEDULER_cancel (pc->timeout_task); + pc->timeout_task = NULL; + } MHD_resume_connection (pc->connection); TMH_trigger_daemon (); /* we resumed, kick MHD */ } + /** * Convert denomination key to its base32 representation * @@ -311,10 +331,13 @@ deposit_cb (void *cls, if (NULL == proof) { - /* FIXME: is this the right code for when the exchange fails? */ + /* We can't do anything meaningful here, the exchange did something wrong */ + /* FIXME: any useful information we can include? */ resume_pay_with_response (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TMH_RESPONSE_make_internal_error ("Exchange failed, no proof available")); + MHD_HTTP_SERVICE_UNAVAILABLE, + TMH_RESPONSE_make_json_pack ("{s:s, s:s}", + "error", "exchange failed", + "hint", "The exchange provided an unexpected response")); } else { @@ -375,6 +398,12 @@ pay_context_cleanup (struct TM_HandlerContext *hc) struct PayContext *pc = (struct PayContext *) hc; unsigned int i; + if (NULL != pc->timeout_task) + { + GNUNET_SCHEDULER_cancel (pc->timeout_task); + pc->timeout_task = NULL; + } + TMH_PARSE_post_cleanup_callback (pc->json_parse_context); for (i=0;i<pc->coins_cnt;i++) { @@ -466,9 +495,10 @@ process_pay_with_exchange (void *cls, GNUNET_break_op (0); resume_pay_with_response (pc, MHD_HTTP_BAD_REQUEST, - TMH_RESPONSE_make_json_pack ("{s:s, s:o}", - "hint", "unknown denom to exchange", - "denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key))); + TMH_RESPONSE_make_json_pack ("{s:s, s:o, s:o}", + "error", "denomination not found", + "denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key), + "exchange_keys", TALER_EXCHANGE_get_keys_raw (mh))); denom_enc = denomination_to_string_alloc (&dc->denom); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "unknown denom to exchange: %s\n", denom_enc); GNUNET_free (denom_enc); @@ -484,10 +514,10 @@ process_pay_with_exchange (void *cls, resume_pay_with_response (pc, MHD_HTTP_BAD_REQUEST, TMH_RESPONSE_make_json_pack ("{s:s, s:o}", - "hint", "no acceptable auditor for denomination", + "error", "invalid denomination", "denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key))); denom_enc = denomination_to_string_alloc (&dc->denom); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "no acceptable auditor for denomination: %s\n", denom_enc); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "client offered invalid denomination: %s\n", denom_enc); GNUNET_free (denom_enc); return; } @@ -593,7 +623,7 @@ process_pay_with_exchange (void *cls, dc->dh = TALER_EXCHANGE_deposit (mh, &dc->percoin_amount, - pc->edate, + pc->wire_transfer_deadline, j_wire, &pc->h_contract, &dc->coin_pub, @@ -623,6 +653,33 @@ process_pay_with_exchange (void *cls, /** + * Handle a timeout for the processing of the pay request. + * + * @param cls closure + */ +static void +handle_pay_timeout (void *cls) +{ + struct PayContext *pc = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Resuming /pay with error after timeout\n"); + + pc->timeout_task = NULL; + + if (NULL != pc->fo) + { + TMH_EXCHANGES_find_exchange_cancel (pc->fo); + pc->fo = NULL; + } + + resume_pay_with_response (pc, + MHD_HTTP_SERVICE_UNAVAILABLE, + TMH_RESPONSE_make_internal_error ("exchange not reachable")); +} + + +/** * Accomplish this payment. * * @param rh context of the handler @@ -748,18 +805,24 @@ MH_handler_pay (struct TMH_RequestHandler *rh, "invalid merchant signature supplied"); } - /* 'edate' is optional, if it is not present, generate it here; it - will be timestamp plus the edate_delay supplied in config - file */ - if (NULL == json_object_get (root, "edate")) + /* 'wire_transfer_deadline' is optional, if it is not present, + generate it here; it will be timestamp plus the + wire_transfer_delay supplied in config file */ + if (NULL == json_object_get (root, "wire_transfer_deadline")) { - pc->edate = GNUNET_TIME_absolute_add (pc->timestamp, - edate_delay); + pc->wire_transfer_deadline = GNUNET_TIME_absolute_add (pc->timestamp, + wire_transfer_delay); + if (pc->wire_transfer_deadline.abs_value_us < pc->refund_deadline.abs_value_us) + { + /* Refund value very large, delay wire transfer accordingly */ + pc->wire_transfer_deadline = pc->refund_deadline; + } } else { struct GNUNET_JSON_Specification espec[] = { - GNUNET_JSON_spec_absolute_time ("edate", &pc->edate), + GNUNET_JSON_spec_absolute_time ("wire_transfer_deadline", + &pc->wire_transfer_deadline), GNUNET_JSON_spec_end() }; @@ -772,6 +835,14 @@ MH_handler_pay (struct TMH_RequestHandler *rh, GNUNET_break (0); return (GNUNET_NO == res) ? MHD_YES : MHD_NO; } + if (pc->wire_transfer_deadline.abs_value_us < pc->refund_deadline.abs_value_us) + { + GNUNET_break (0); + json_decref (root); + return TMH_RESPONSE_reply_external_error (connection, + "refund deadline after wire transfer deadline"); + } + } @@ -833,9 +904,6 @@ MH_handler_pay (struct TMH_RequestHandler *rh, /* Payment succeeded in the past; take short cut and accept immediately */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Transaction %llu already paid in the past, taking short cut.\n", - (unsigned long long) pc->transaction_id); resp = MHD_create_response_from_buffer (0, NULL, MHD_RESPMEM_PERSISTENT); @@ -859,6 +927,7 @@ MH_handler_pay (struct TMH_RequestHandler *rh, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Suspending /pay handling while working with the exchange\n"); MHD_suspend_connection (connection); + pc->timeout_task = GNUNET_SCHEDULER_add_delayed (PAY_TIMEOUT, handle_pay_timeout, pc); json_decref (root); return MHD_YES; } diff --git a/src/backend/taler-merchant-httpd_responses.c b/src/backend/taler-merchant-httpd_responses.c @@ -289,5 +289,41 @@ TMH_RESPONSE_make_external_error (const char *hint) "hint", hint); } +/** + * Send a response indicating a missing argument. + * + * @param connection the MHD connection to use + * @param param_name the parameter that is missing + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_arg_missing (struct MHD_Connection *connection, + const char *param_name) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{ s:s, s:s}", + "error", "missing parameter", + "parameter", param_name); +} + + +/** + * Send a response indicating an invalid argument. + * + * @param connection the MHD connection to use + * @param param_name the parameter that is invalid + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_arg_invalid (struct MHD_Connection *connection, + const char *param_name) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:s}", + "error", "invalid parameter", + "parameter", param_name); +} /* end of taler-exchange-httpd_responses.c */ diff --git a/src/backend/taler-merchant-httpd_responses.h b/src/backend/taler-merchant-httpd_responses.h @@ -157,4 +157,25 @@ TMH_RESPONSE_reply_request_too_large (struct MHD_Connection *connection); void TMH_RESPONSE_add_global_headers (struct MHD_Response *response); +/** + * Send a response indicating a missing argument. + * + * @param connection the MHD connection to use + * @param param_name the parameter that is missing + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_arg_missing (struct MHD_Connection *connection, + const char *param_name); + +/** + * Send a response indicating an invalid argument. + * + * @param connection the MHD connection to use + * @param param_name the parameter that is invalid + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_arg_invalid (struct MHD_Connection *connection, + const char *param_name); #endif diff --git a/src/backend/taler-merchant-httpd_track.c b/src/backend/taler-merchant-httpd_track.c @@ -0,0 +1,70 @@ +/* + This file is part of TALER + (C) 2014, 2015, 2016 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file backend/taler-merchant-httpd_track.c + * @brief implement API for tracking deposits and wire transfers + * @author Marcello Stanisci + */ +#include "platform.h" +#include <jansson.h> +#include <taler/taler_signatures.h> +#include <taler/taler_json_lib.h> +#include "taler-merchant-httpd.h" +#include "taler-merchant-httpd_mhd.h" +#include "taler-merchant-httpd_parsing.h" +#include "taler-merchant-httpd_auditors.h" +#include "taler-merchant-httpd_exchanges.h" +#include "taler-merchant-httpd_responses.h" + + +extern char *TMH_merchant_currency_string; + + +/** + * Manages a /track/deposit call, thus it calls the /wire/deposit + * offered by the exchange in order to return the set of deposits + * (of coins) associated with a given wire transfer + * + * @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 + * @return MHD result code + */ +int +MH_handler_track_deposit (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + static struct TMH_RequestHandler pong = + { + "", NULL, "text/html", + "/track/deposit served\n", 0, + &TMH_MHD_handler_static_response, MHD_HTTP_OK + }; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "serving /track/deposit\n"); + return TMH_MHD_handler_static_response (&pong, + connection, + connection_cls, + upload_data, + upload_data_size); +} + +/* end of taler-merchant-httpd_contract.c */ diff --git a/src/backend/taler-merchant-httpd_track.h b/src/backend/taler-merchant-httpd_track.h @@ -0,0 +1,41 @@ +/* + This file is part of TALER + (C) 2014, 2015 GNUnet e.V. + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file backend/taler-merchant-httpd_track.h + * @brief headers for /track/{deposit,wtid} handler + * @author Marcello Stanisci + */ +#include <microhttpd.h> +#include "taler-merchant-httpd.h" + +/** + * Manages a /track/deposit call, thus it calls the /wire/deposit + * offered by the exchange in order to return the set of deposits + * (of coins) associated with a given wire transfer + * + * @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 + * @return MHD result code + */ +int +MH_handler_track_deposit (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); diff --git a/src/backend/taler-merchant-httpd_util.c b/src/backend/taler-merchant-httpd_util.c @@ -1,105 +0,0 @@ -/* - This file is part of TALER - (C) 2014, 2015, 2016 GNUnet e.V. and INRIA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - 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, If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file backend/taler-merchant-httpd_contract.c - * @brief HTTP serving layer mainly intended to communicate with the frontend - * @author Marcello Stanisci - */ -#include "platform.h" -#include <jansson.h> -#include <taler/taler_signatures.h> -#include <taler/taler_json_lib.h> -#include "taler-merchant-httpd.h" -#include "taler-merchant-httpd_parsing.h" -#include "taler-merchant-httpd_auditors.h" -#include "taler-merchant-httpd_exchanges.h" -#include "taler-merchant-httpd_responses.h" - - -/** - * Hashes a plain JSON contract sending the result to the other end of - * HTTP communication - * - * @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 - * @return MHD result code - */ -int -MH_handler_hash_contract (struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size) -{ - json_t *root; - json_t *jcontract; - int res; - struct GNUNET_HashCode hc; - struct TMH_JsonParseContext *ctx; - - if (NULL == *connection_cls) - { - ctx = GNUNET_new (struct TMH_JsonParseContext); - ctx->hc.cc = &TMH_json_parse_cleanup; - *connection_cls = ctx; - } - else - { - ctx = *connection_cls; - } - - res = TMH_PARSE_post_json (connection, - &ctx->json_parse_context, - upload_data, - upload_data_size, - &root); - - if (GNUNET_SYSERR == res) - return MHD_NO; - /* the POST's body has to be further fetched */ - if ((GNUNET_NO == res) || (NULL == root)) - return MHD_YES; - - jcontract = json_object_get (root, "contract"); - - if (NULL == jcontract) - { - return TMH_RESPONSE_reply_external_error (connection, - "missing 'contract' field"); - } - - if (GNUNET_OK != TALER_JSON_hash (jcontract, - &hc)) - { - return TMH_RESPONSE_reply_external_error (connection, - "expected object as contract"); - } - - GNUNET_assert (GNUNET_OK == - TALER_JSON_hash (jcontract, - &hc)); - - /* return final response */ - res = TMH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_OK, - "{s:O}", - "hash", GNUNET_JSON_from_data_auto (&hc)); - json_decref (root); - return res; -} diff --git a/src/backend/taler-merchant-httpd_util.h b/src/backend/taler-merchant-httpd_util.h @@ -1,43 +0,0 @@ -/* - This file is part of TALER - (C) 2014, 2015 GNUnet e.V. - - 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, If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file backend/taler-merchant-httpd_contract.h - * @brief headers for /contract handler - * @author Marcello Stanisci - */ -#ifndef TALER_EXCHANGE_HTTPD_UTIL_H -#define TALER_EXCHANGE_HTTPD_UTIL_H -#include <microhttpd.h> -#include "taler-merchant-httpd.h" - -/** - * Manage a contract request - * - * @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 - * @return MHD result code - */ -int -MH_handler_hash_contract (struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size); - -#endif diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h @@ -17,6 +17,7 @@ * @file include/taler_merchant_service.h * @brief C interface of libtalermerchant, a C library to use merchant's HTTP API * @author Christian Grothoff + * @author Marcello Stanisci */ #ifndef _TALER_MERCHANT_SERVICE_H #define _TALER_MERCHANT_SERVICE_H @@ -25,6 +26,49 @@ #include <gnunet/gnunet_curl_lib.h> #include <jansson.h> +/* ********************* /track/deposit *********************** */ + +/** + * @brief Handle to a /contract operation at a merchant's backend. + */ +struct TALER_MERCHANT_TrackDepositOperation; + +/** + * Callbacks of this type are used to work the result of submitting a /track/deposit request to a merchant + */ +typedef void +(*TALER_MERCHANT_TrackDepositCallback) (void *cls, + unsigned int http_status, + const json_t *obj); + +/** + * Request backend to return deposits associated with a given wtid. + * + * @param ctx execution context + * @param backend_uri URI of the backend (having /track/deposit appended) + * @param wtid base32 string indicating a wtid + * @param exchange base URL of the exchange in charge of returning the wanted information + * @param trackdeposit_cb the callback to call when a reply for this request is available + * @param trackdeposit_cb_cls closure for @a contract_cb + * @return a handle for this request + */ +struct TALER_MERCHANT_TrackDepositOperation * +TALER_MERCHANT_track_deposit (struct GNUNET_CURL_Context *ctx, + const char *backend_uri, + const char *wtid, + const char *exchange_uri, + TALER_MERCHANT_TrackDepositCallback trackdeposit_cb, + void *trackdeposit_cb_cls); + +/** + * Cancel a /track/deposit request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param co the deposit's tracking operation + */ +void +TALER_MERCHANT_track_deposit_cancel (struct TALER_MERCHANT_TrackDepositOperation *tdo); + /* ********************* /contract *********************** */ @@ -196,8 +240,8 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, /** - * Information we need from the frontend when forwarding - * a payment to the backend. + * Information we need from the frontend (ok, the frontend sends just JSON) + * when forwarding a payment to the backend. */ struct TALER_MERCHANT_PaidCoin { @@ -254,7 +298,7 @@ struct TALER_MERCHANT_PaidCoin * @param merchant_sig the signature of the merchant over the original contract * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed) * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant - * @param execution_deadline date by which the merchant would like the exchange to execute the transaction (can be zero if there is no specific date desired by the frontend). If non-zero, must be larger than @a refund_deadline. + * @param wire_transfer_deadline date by which the merchant would like the exchange to execute the wire transfer (can be zero if there is no specific date desired by the frontend). If non-zero, must be larger than @a refund_deadline. * @param exchange_uri URI of the exchange that the coins belong to * @param num_coins number of coins used to pay * @param coins array of coins we use to pay @@ -273,7 +317,7 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx, const struct TALER_MerchantSignatureP *merchant_sig, struct GNUNET_TIME_Absolute refund_deadline, struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute execution_deadline, + struct GNUNET_TIME_Absolute wire_transfer_deadline, const char *exchange_uri, unsigned int num_coins, const struct TALER_MERCHANT_PaidCoin *coins, @@ -295,4 +339,24 @@ void TALER_MERCHANT_pay_cancel (struct TALER_MERCHANT_Pay *ph); + +/** + * Request backend to return deposits associated with a given wtid. + * + * @param ctx execution context + * @param backend_uri URI of the backend + * @param wtid base32 string indicating a wtid + * @param exchange_uri base URL of the exchange in charge of returning the wanted information + * @param trackdeposit_cb the callback to call when a reply for this request is available + * @param trackdeposit_cb_cls closure for @a contract_cb + * @return a handle for this request + */ +struct TALER_MERCHANT_TrackDepositOperation * +TALER_MERCHANT_track_deposit (struct GNUNET_CURL_Context *ctx, + const char *backend_uri, + const char *wtid, + const char *exchange_uri, + TALER_MERCHANT_TrackDepositCallback trackdeposit_cb, + void *trackdeposit_cb_cls); + #endif /* _TALER_MERCHANT_SERVICE_H */ diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am @@ -15,7 +15,8 @@ libtalermerchant_la_LDFLAGS = \ libtalermerchant_la_SOURCES = \ merchant_api_contract.c \ - merchant_api_pay.c + merchant_api_pay.c \ + merchant_api_track.c libtalermerchant_la_LIBADD = \ -ltalerexchange \ diff --git a/src/lib/merchant_api_contract.c b/src/lib/merchant_api_contract.c @@ -123,7 +123,6 @@ handle_contract_finished (void *cls, (or API version conflict); just pass JSON reply to the application */ break; case MHD_HTTP_FORBIDDEN: - /* Duplicate transaction ID, frontend is buggy! */ break; case MHD_HTTP_UNAUTHORIZED: /* Nothing really to verify, merchant says one of the signatures is diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c @@ -358,7 +358,7 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant * @param transaction_id transaction id for the transaction between merchant and customer * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed) - * @param execution_deadline date by which the merchant would like the exchange to execute the transaction (can be zero if there is no specific date desired by the frontend). If non-zero, must be larger than @a refund_deadline. + * @param wire_transfer_deadline date by which the merchant would like the exchange to execute the wire transfer (can be zero if there is no specific date desired by the frontend). If non-zero, must be larger than @a refund_deadline. * @param num_coins number of coins used to pay * @param coins array of coins we use to pay * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. @@ -378,7 +378,7 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx, const struct TALER_MerchantSignatureP *merchant_sig, struct GNUNET_TIME_Absolute refund_deadline, struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute execution_deadline, + struct GNUNET_TIME_Absolute wire_transfer_deadline, const char *exchange_uri, unsigned int num_coins, const struct TALER_MERCHANT_PaidCoin *coins, @@ -400,8 +400,8 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx, GNUNET_break (0); return NULL; } - if ( (0 != execution_deadline.abs_value_us) && - (execution_deadline.abs_value_us < refund_deadline.abs_value_us) ) + if ( (0 != wire_transfer_deadline.abs_value_us) && + (wire_transfer_deadline.abs_value_us < wire_transfer_deadline.abs_value_us) ) { GNUNET_break (0); return NULL; @@ -549,12 +549,12 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx, "amount", TALER_JSON_from_amount (amount), "merchant_sig", GNUNET_JSON_from_data_auto (merchant_sig)); - if (0 != execution_deadline.abs_value_us) + if (0 != wire_transfer_deadline.abs_value_us) { /* Frontend did have an execution date in mind, add it */ json_object_set_new (pay_obj, - "edate", - GNUNET_JSON_from_time_abs (execution_deadline)); + "wire_transfer_deadline", + GNUNET_JSON_from_time_abs (wire_transfer_deadline)); } ph = GNUNET_new (struct TALER_MERCHANT_Pay); diff --git a/src/lib/merchant_api_track.c b/src/lib/merchant_api_track.c @@ -0,0 +1,188 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. and INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING.LGPL. If not, If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/merchant_api_contract.c + * @brief Implementation of the /track/deposit and /track/wtid request of the + * merchant's HTTP API + * @author Marcello Stanisci + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * @brief A Contract Operation Handle + */ +struct TALER_MERCHANT_TrackDepositOperation +{ + + /** + * The url for this request. + */ + char *url; + + /** + * base32 identifier being the 'witd' parameter required by the + * exchange + */ + char *wtid; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_TrackDepositCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP /track/deposit request. + * + * @param cls the `struct TALER_MERCHANT_TrackDepositOperation` + * @param response_code HTTP response code, 0 on error + * @param json response body, NULL if not in JSON + */ +static void +handle_trackdeposit_finished (void *cls, + long response_code, + const json_t *json) // body +{ + struct TALER_MERCHANT_TrackDepositOperation *tdo = cls; + + tdo->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + /* Work out argument for external callback from the body .. */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "200 returned from /track/deposit"); + } + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "track deposit URI not found"); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + /* FIXME figure out wich parameters ought to be passed back */ + tdo->cb (tdo->cb_cls, + response_code, + json); +} + + +/** + * Request backend to return deposits associated with a given wtid. + * + * @param ctx execution context + * @param backend_uri URI of the backend (having /track/deposit appended) + * @param wtid base32 string indicating a wtid + * @param exchange base URL of the exchange in charge of returning the wanted information + * @param trackdeposit_cb the callback to call when a reply for this request is available + * @param trackdeposit_cb_cls closure for @a contract_cb + * @return a handle for this request + */ +struct TALER_MERCHANT_TrackDepositOperation * +TALER_MERCHANT_track_deposit (struct GNUNET_CURL_Context *ctx, + const char *backend_uri, + const char *wtid, + const char *exchange_uri, + TALER_MERCHANT_TrackDepositCallback trackdeposit_cb, + void *trackdeposit_cb_cls) +{ + struct TALER_MERCHANT_TrackDepositOperation *tdo; + CURL *eh; + + tdo = GNUNET_new (struct TALER_MERCHANT_TrackDepositOperation); + tdo->ctx = ctx; + tdo->cb = trackdeposit_cb; + tdo->cb_cls = trackdeposit_cb_cls; + /* TO BE FREED SOMEWHERE. GOTTEN WITH /track/deposit ALREADY APPENDED */ + GNUNET_asprintf (&tdo->url, + "%s?wtid=%s&exchange=%s", + backend_uri, + wtid, + exchange_uri); + eh = curl_easy_init (); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + tdo->url)); + tdo->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_trackdeposit_finished, + tdo); + return tdo; +} + +/** + * Cancel a /track/deposit request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param co the deposit's tracking operation + */ +void +TALER_MERCHANT_track_deposit_cancel (struct TALER_MERCHANT_TrackDepositOperation *tdo) +{ + if (NULL != tdo->job) + { + GNUNET_CURL_job_cancel (tdo->job); + tdo->job = NULL; + } + GNUNET_free (tdo->url); + GNUNET_free (tdo->wtid); + GNUNET_free (tdo); +} + +/* end of merchant_api_track.c */ diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c @@ -104,7 +104,12 @@ enum OpCode /** * Pay with coins. */ - OC_PAY + OC_PAY, + + /** + * Retrieve deposit permissions for a given wire transfer + */ + OC_TRACK_DEPOSIT }; @@ -203,9 +208,14 @@ struct Command const char *amount; /** - * Wire details (JSON). + * Sender's bank account details (JSON). */ - const char *wire; + const char *sender_details; + + /** + * Transfer details (JSON) + */ + const char *transfer_details; /** * Set (by the interpreter) to the reserve's private key @@ -283,7 +293,7 @@ struct Command /** * Blinding key used for the operation. */ - struct TALER_DenominationBlindingKey blinding_key; + struct TALER_DenominationBlindingKeyP blinding_key; /** * Withdraw handle (while operation is running). @@ -370,6 +380,20 @@ struct Command } pay; + struct { + + /** + * Wire transfer ID whose deposit permissions are to be retrieved + */ + char *wtid; + + /** + * Handle to a /track/deposit operation + */ + struct TALER_MERCHANT_TrackDepositOperation *tdo; + + } track_deposit; + } details; }; @@ -813,6 +837,30 @@ pay_cb (void *cls, is); } +/** + * Callback for a /track/deposit operation + * + * @param cls closure for this function + * @param http_status HTTP response code returned by the server + * @param obj server response's body + */ +static void +track_deposit_cb (void *cls, + unsigned int http_status, + const json_t *obj) +{ + if (MHD_HTTP_OK == http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Ok from /track/deposit handler\n"); + result = GNUNET_OK; + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Not Ok from /track/deposit handler\n"); + result = GNUNET_SYSERR; + } +} + /** * Find denomination key matching the given amount. @@ -884,7 +932,8 @@ interpreter_run (void *cls) struct TALER_CoinSpendPublicKeyP coin_pub; struct TALER_Amount amount; struct GNUNET_TIME_Absolute execution_date; - json_t *wire; + json_t *sender_details; + json_t *transfer_details; is->task = NULL; tc = GNUNET_SCHEDULER_get_task_context (); @@ -938,28 +987,46 @@ interpreter_run (void *cls) fail (is); return; } - wire = json_loads (cmd->details.admin_add_incoming.wire, - JSON_REJECT_DUPLICATES, - NULL); - if (NULL == wire) + + execution_date = GNUNET_TIME_absolute_get (); + GNUNET_TIME_round_abs (&execution_date); + sender_details = json_loads (cmd->details.admin_add_incoming.sender_details, + JSON_REJECT_DUPLICATES, + NULL); + if (NULL == sender_details) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to parse wire details `%s' at %u\n", - cmd->details.admin_add_incoming.wire, + "Failed to parse sender details `%s' at %u\n", + cmd->details.admin_add_incoming.sender_details, is->ip); fail (is); return; } - execution_date = GNUNET_TIME_absolute_get (); - GNUNET_TIME_round_abs (&execution_date); + transfer_details = json_loads (cmd->details.admin_add_incoming.transfer_details, + JSON_REJECT_DUPLICATES, + NULL); + + if (NULL == transfer_details) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse transfer details `%s' at %u\n", + cmd->details.admin_add_incoming.transfer_details, + is->ip); + fail (is); + return; + } + cmd->details.admin_add_incoming.aih = TALER_EXCHANGE_admin_add_incoming (exchange, &reserve_pub, &amount, execution_date, - wire, + sender_details, + transfer_details, &add_incoming_cb, is); + json_decref (sender_details); + json_decref (transfer_details); if (NULL == cmd->details.admin_add_incoming.aih) { GNUNET_break (0); @@ -1024,8 +1091,10 @@ interpreter_run (void *cls) } GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.reserve_withdraw.coin_priv.eddsa_priv, &coin_pub.eddsa_pub); - cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key - = GNUNET_CRYPTO_rsa_blinding_key_create (GNUNET_CRYPTO_rsa_public_key_len (cmd->details.reserve_withdraw.pk->key.rsa_public_key)); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &cmd->details.reserve_withdraw.blinding_key, + sizeof (cmd->details.reserve_withdraw.blinding_key)); + cmd->details.reserve_withdraw.wsh = TALER_EXCHANGE_reserve_withdraw (exchange, cmd->details.reserve_withdraw.pk, @@ -1194,6 +1263,14 @@ interpreter_run (void *cls) return; } return; + case OC_TRACK_DEPOSIT: + TALER_MERCHANT_track_deposit (ctx, + MERCHANT_URI "/track/deposit", + cmd->details.track_deposit.wtid, + EXCHANGE_URI, + track_deposit_cb, + is); + return; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown instruction %d at %u (%s)\n", @@ -1285,11 +1362,6 @@ do_shutdown (void *cls) GNUNET_CRYPTO_rsa_signature_free (cmd->details.reserve_withdraw.sig.rsa_signature); cmd->details.reserve_withdraw.sig.rsa_signature = NULL; } - if (NULL != cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key) - { - GNUNET_CRYPTO_rsa_blinding_key_free (cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key); - cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key = NULL; - } break; case OC_CONTRACT: if (NULL != cmd->details.contract.co) @@ -1318,9 +1390,17 @@ do_shutdown (void *cls) cmd->details.pay.ph = NULL; } break; + case OC_TRACK_DEPOSIT: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "shutting down /track/deposit\n"); + if (NULL != cmd->details.track_deposit.tdo) + { + TALER_MERCHANT_track_deposit_cancel (cmd->details.track_deposit.tdo); + cmd->details.track_deposit.tdo = NULL; + } + break; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unknown instruction %d at %u (%s)\n", + "Shutdown: unknown instruction %d at %u (%s)\n", cmd->oc, i, cmd->label); @@ -1398,11 +1478,20 @@ run (void *cls) struct InterpreterState *is; static struct Command commands[] = { + + { .oc = OC_TRACK_DEPOSIT, + .label = "track-deposit-1", + .expected_response_code = MHD_HTTP_OK, + .details.track_deposit.wtid = "TESTWTID"}, + + { .oc = OC_END }, + /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config */ { .oc = OC_ADMIN_ADD_INCOMING, .label = "create-reserve-1", .expected_response_code = MHD_HTTP_OK, - .details.admin_add_incoming.wire = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost/\", \"account_number\":62, \"uuid\":1 }", + .details.admin_add_incoming.sender_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost/\", \"account_number\":62, \"uuid\":1 }", + .details.admin_add_incoming.transfer_details = "{ \"uuid\": 1}", .details.admin_add_incoming.amount = "EUR:5.01" }, /* Withdraw a 5 EUR coin, at fee of 1 ct */ { .oc = OC_WITHDRAW_SIGN, @@ -1449,14 +1538,16 @@ run (void *cls) { .oc = OC_ADMIN_ADD_INCOMING, .label = "create-reserve-2", .expected_response_code = MHD_HTTP_OK, - .details.admin_add_incoming.wire = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost/\", \"account_number\":63, \"uuid\":2 }", + .details.admin_add_incoming.sender_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost/\", \"account_number\":63, \"uuid\":2 }", + .details.admin_add_incoming.transfer_details = "{ \"uuid\": 2}", .details.admin_add_incoming.amount = "EUR:1" }, /* Add another 4.01 EUR to reserve #2 */ { .oc = OC_ADMIN_ADD_INCOMING, .label = "create-reserve-2b", .expected_response_code = MHD_HTTP_OK, .details.admin_add_incoming.reserve_reference = "create-reserve-2", - .details.admin_add_incoming.wire = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost/\", \"account_number\":63, \"uuid\":3 }", + .details.admin_add_incoming.sender_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost/\", \"account_number\":63, \"uuid\":3 }", + .details.admin_add_incoming.transfer_details = "{ \"uuid\": 3}", .details.admin_add_incoming.amount = "EUR:4.01" }, /* Withdraw a 5 EUR coin, at fee of 1 ct */ @@ -1549,6 +1640,12 @@ main (int argc, "taler-exchange-keyup", "-c", "test_merchant_api.conf", NULL); + if (NULL == proc) + { + fprintf (stderr, + "Failed to run taler-exchange-keyup. Check your PATH.\n"); + return 77; + } GNUNET_OS_process_wait (proc); GNUNET_OS_process_destroy (proc); proc = GNUNET_OS_start_process (GNUNET_NO, @@ -1557,7 +1654,14 @@ main (int argc, "taler-exchange-dbinit", "taler-exchange-dbinit", "-c", "test_merchant_api.conf", + "-r", NULL); + if (NULL == proc) + { + fprintf (stderr, + "Failed to run taler-exchange-dbinit. Check your PATH.\n"); + return 77; + } GNUNET_OS_process_wait (proc); GNUNET_OS_process_destroy (proc); exchanged = GNUNET_OS_start_process (GNUNET_NO, @@ -1567,9 +1671,15 @@ main (int argc, "taler-exchange-httpd", "-c", "test_merchant_api.conf", NULL); + if (NULL == exchanged) + { + fprintf (stderr, + "Failed to run taler-exchange-httpd. Check your PATH.\n"); + return 77; + } /* give child time to start and bind against the socket */ fprintf (stderr, - "Waiting for taler-exchange-httpd to be ready"); + "Waiting for taler-exchange-httpd to be ready\n"); cnt = 0; do { @@ -1596,9 +1706,19 @@ main (int argc, "taler-merchant-httpd", "-c", "test_merchant_api.conf", NULL); + if (NULL == merchantd) + { + fprintf (stderr, + "Failed to run taler-merchant-httpd. Check your PATH.\n"); + GNUNET_OS_process_kill (exchanged, + SIGKILL); + GNUNET_OS_process_wait (exchanged); + GNUNET_OS_process_destroy (exchanged); + return 77; + } /* give child time to start and bind against the socket */ fprintf (stderr, - "Waiting for taler-merchant-httpd to be ready"); + "Waiting for taler-merchant-httpd to be ready\n"); cnt = 0; do { @@ -1613,6 +1733,10 @@ main (int argc, SIGKILL); GNUNET_OS_process_wait (merchantd); GNUNET_OS_process_destroy (merchantd); + GNUNET_OS_process_kill (exchanged, + SIGKILL); + GNUNET_OS_process_wait (exchanged); + GNUNET_OS_process_destroy (exchanged); return 77; } } diff --git a/src/tests/test_contract.c b/src/tests/test_contract.c @@ -65,7 +65,7 @@ extern uint32_t TALER_MERCHANTDB_contract_get_values (PGconn *conn, const struct GNUNET_HashCode *h_contract, uint64_t *nounce, - struct GNUNET_TIME_Absolute *edate); + struct GNUNET_TIME_Absolute *wire_transfer_deadline); /** @@ -104,7 +104,7 @@ run (void *cls, char *const *args, const char *cfgfile, int64_t t_id; int64_t p_id; struct Contract contract; - struct GNUNET_TIME_Absolute edate; + struct GNUNET_TIME_Absolute wire_transfer_deadline; struct GNUNET_TIME_Absolute now; uint64_t nounce; struct GNUNET_HashCode h_contract_str; @@ -294,17 +294,21 @@ run (void *cls, char *const *args, const char *cfgfile, printf ("contract string : %s\n", aa); GNUNET_CRYPTO_hash (aa, strlen (aa) + 1, &h_contract_str); - if (GNUNET_SYSERR == TALER_MERCHANTDB_contract_get_values (db_conn, &h_contract_str, &nounce, &edate)) + if (GNUNET_SYSERR == + TALER_MERCHANTDB_contract_get_values (db_conn, + &h_contract_str, + &nounce, + &wire_transfer_deadline)) + { printf ("no hash found\n"); + } else { - fancy_time = GNUNET_STRINGS_absolute_time_to_string (edate); + fancy_time = GNUNET_STRINGS_absolute_time_to_string (wire_transfer_deadline); printf ("hash found!, nounce is : %llu\n", nounce); - printf ("hash found!, time is : %s\n", fancy_time); + printf ("hash found!, wire transfer time is : %s\n", fancy_time); } - - return; }