From 5da121e9b0ec83f20a1a404f7049f9ff19aca32b Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 27 Apr 2020 16:32:59 +0200 Subject: implement order claiming --- src/backend/taler-merchant-httpd.c | 2 +- .../taler-merchant-httpd_private-post-transfers.c | 1089 ++++++++++++++++++++ .../taler-merchant-httpd_private-post-transfers.h | 49 + src/include/taler_merchant_service.h | 113 +- src/include/taler_merchant_testing_lib.h | 24 +- src/lib/Makefile.am | 3 +- src/lib/merchant_api_post_order_claim.c | 269 +++++ src/lib/merchant_api_post_products.c | 17 +- src/lib/merchant_api_proposal_lookup.c | 279 ----- src/testing/Makefile.am | 2 +- src/testing/test_merchant_api.c | 10 +- src/testing/testing_api_cmd_claim_order.c | 311 ++++++ src/testing/testing_api_cmd_post_orders.c | 41 +- src/testing/testing_api_cmd_proposal_lookup.c | 311 ------ 14 files changed, 1821 insertions(+), 699 deletions(-) create mode 100644 src/backend/taler-merchant-httpd_private-post-transfers.c create mode 100644 src/backend/taler-merchant-httpd_private-post-transfers.h create mode 100644 src/lib/merchant_api_post_order_claim.c delete mode 100644 src/lib/merchant_api_proposal_lookup.c create mode 100644 src/testing/testing_api_cmd_claim_order.c delete mode 100644 src/testing/testing_api_cmd_proposal_lookup.c (limited to 'src') diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index f11d18ca..7d255b9e 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -908,7 +908,7 @@ url_handler (void *cls, (GNUNET_YES != GNUNET_CURL_is_valid_scope_id (correlation_id)) ) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "illegal incoming correlation ID\n"); + "Illegal incoming correlation ID\n"); correlation_id = NULL; } if (NULL != correlation_id) diff --git a/src/backend/taler-merchant-httpd_private-post-transfers.c b/src/backend/taler-merchant-httpd_private-post-transfers.c new file mode 100644 index 00000000..7f55c917 --- /dev/null +++ b/src/backend/taler-merchant-httpd_private-post-transfers.c @@ -0,0 +1,1089 @@ +/* + This file is part of TALER + (C) 2014-2020 Taler Systems SA + + 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, see +*/ +/** + * @file backend/taler-merchant-httpd_track-transfer.c + * @brief implement API for tracking transfers and wire transfers + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include "taler-merchant-httpd.h" +#include "taler-merchant-httpd_mhd.h" +#include "taler-merchant-httpd_auditors.h" +#include "taler-merchant-httpd_exchanges.h" +#include "taler-merchant-httpd_track-transfer.h" + + +/** + * How long to wait before giving up processing with the exchange? + */ +#define TRACK_TIMEOUT (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, \ + 30)) + +/** + * How often do we retry the simple INSERT database transaction? + */ +#define MAX_RETRIES 3 + +/** + * Context used for handing /track/transfer requests. + */ +struct TrackTransferContext +{ + + /** + * This MUST be first! + */ + struct TM_HandlerContext hc; + + /** + * Handle to the exchange. + */ + struct TALER_EXCHANGE_Handle *eh; + + /** + * Handle for the /wire/transfers request. + */ + struct TALER_EXCHANGE_TransfersGetHandle *wdh; + + /** + * For which merchant instance is this tracking request? + */ + struct MerchantInstance *mi; + + /** + * HTTP connection we are handling. + */ + struct MHD_Connection *connection; + + /** + * Response to return upon resume. + */ + struct MHD_Response *response; + + /** + * Handle for operation to lookup /keys (and auditors) from + * the exchange used for this transaction; NULL if no operation is + * pending. + */ + struct TMH_EXCHANGES_FindOperation *fo; + + /** + * Task run on timeout. + */ + struct GNUNET_SCHEDULER_Task *timeout_task; + + /** + * URL of the exchange. + */ + char *url; + + /** + * Wire method used for the transfer. + */ + char *wire_method; + + /** + * Pointer to the detail that we are currently + * checking in #check_transfer(). + */ + const struct TALER_TrackTransferDetails *current_detail; + + /** + * Argument for the /wire/transfers request. + */ + struct TALER_WireTransferIdentifierRawP wtid; + + /** + * Full original response we are currently processing. + */ + const json_t *original_response; + + /** + * Modified response to return to the frontend. + */ + json_t *deposits_response; + + /** + * Which transaction detail are we currently looking at? + */ + unsigned int current_offset; + + /** + * Response code to return. + */ + unsigned int response_code; + + /** + * #GNUNET_NO if we did not find a matching coin. + * #GNUNET_SYSERR if we found a matching coin, but the amounts do not match. + * #GNUNET_OK if we did find a matching coin. + */ + int check_transfer_result; +}; + + +/** + * Represents an entry in the table used to sum up + * individual deposits for each h_contract_terms. + */ +struct Entry +{ + + /** + * Sum accumulator for deposited value. + */ + struct TALER_Amount deposit_value; + + /** + * Sum accumulator for deposit fee. + */ + struct TALER_Amount deposit_fee; + +}; + + +/** + * Free the @a rctx. + * + * @param rctx data to free + */ +static void +free_transfer_track_context (struct TrackTransferContext *rctx) +{ + if (NULL != rctx->fo) + { + TMH_EXCHANGES_find_exchange_cancel (rctx->fo); + rctx->fo = NULL; + } + if (NULL != rctx->timeout_task) + { + GNUNET_SCHEDULER_cancel (rctx->timeout_task); + rctx->timeout_task = NULL; + } + if (NULL != rctx->wdh) + { + TALER_EXCHANGE_transfers_get_cancel (rctx->wdh); + rctx->wdh = NULL; + } + if (NULL != rctx->url) + { + GNUNET_free (rctx->url); + rctx->url = NULL; + } + if (NULL != rctx->wire_method) + { + GNUNET_free (rctx->wire_method); + rctx->wire_method = NULL; + } + GNUNET_free (rctx); +} + + +/** + * Callback that frees all the elements in the hashmap + * + * @param cls closure, NULL + * @param key current key + * @param value a `struct Entry` + * @return #GNUNET_YES if the iteration should continue, + * #GNUNET_NO otherwise. + */ +static int +hashmap_free (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + struct TALER_Entry *entry = value; + + (void) cls; + (void) key; + GNUNET_free (entry); + return GNUNET_YES; +} + + +/** + * Builds JSON response containing the summed-up amounts + * from individual deposits. + * + * @param cls closure + * @param key map's current key + * @param map's current value + * @return #GNUNET_YES if iteration is to be continued, + * #GNUNET_NO otherwise. + */ +static int +build_deposits_response (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + struct TrackTransferContext *rctx = cls; + struct Entry *entry = value; + json_t *element; + json_t *contract_terms; + json_t *order_id; + + db->preflight (db->cls); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + db->find_contract_terms_from_hash (db->cls, + &contract_terms, + key, + &rctx->mi->pubkey)) + { + GNUNET_break_op (0); + return GNUNET_NO; + } + + order_id = json_object_get (contract_terms, + "order_id"); + if (NULL == order_id) + { + GNUNET_break_op (0); + json_decref (contract_terms); + return GNUNET_NO; + } + element = json_pack ("{s:O, s:o, s:o}", + "order_id", order_id, + "deposit_value", TALER_JSON_from_amount ( + &entry->deposit_value), + "deposit_fee", TALER_JSON_from_amount ( + &entry->deposit_fee)); + json_decref (contract_terms); + if (NULL == element) + { + GNUNET_break_op (0); + return GNUNET_NO; + } + GNUNET_break (0 == + json_array_append_new (rctx->deposits_response, + element)); + return GNUNET_YES; +} + + +/** + * Transform /track/transfer result as gotten from the exchange + * and transforms it in a format liked by the backoffice Web interface. + * + * @param result response from exchange's /track/transfer + * @result pointer to new JSON, or NULL upon errors. + */ +static json_t * +transform_response (const json_t *result, + struct TrackTransferContext *rctx) +{ + json_t *deposits; + json_t *value; + json_t *result_mod = NULL; + size_t index; + const char *key; + struct GNUNET_HashCode h_key; + struct GNUNET_CONTAINER_MultiHashMap *map; + struct TALER_Amount iter_value; + struct TALER_Amount iter_fee; + struct Entry *current_entry; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("deposit_value", &iter_value), + TALER_JSON_spec_amount ("deposit_fee", &iter_fee), + GNUNET_JSON_spec_string ("h_contract_terms", &key), + GNUNET_JSON_spec_end () + }; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transforming /track/transfer response.\n"); + map = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + deposits = json_object_get (result, + "deposits"); + + json_array_foreach (deposits, index, value) + { + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return NULL; + } + GNUNET_CRYPTO_hash_from_string (key, + &h_key); + + if (NULL != (current_entry = + GNUNET_CONTAINER_multihashmap_get (map, + &h_key))) + { + /* The map already knows this h_contract_terms*/ + if ( (0 > + TALER_amount_add (¤t_entry->deposit_value, + ¤t_entry->deposit_value, + &iter_value)) || + (0 > + TALER_amount_add (¤t_entry->deposit_fee, + ¤t_entry->deposit_fee, + &iter_fee)) ) + { + GNUNET_JSON_parse_free (spec); + goto cleanup; + } + } + else + { + /* First time in the map for this h_contract_terms*/ + current_entry = GNUNET_new (struct Entry); + current_entry->deposit_value = iter_value; + current_entry->deposit_fee = iter_fee; + + if (GNUNET_SYSERR == + GNUNET_CONTAINER_multihashmap_put (map, + &h_key, + current_entry, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + GNUNET_JSON_parse_free (spec); + goto cleanup; + } + } + GNUNET_JSON_parse_free (spec); + } + rctx->deposits_response = json_array (); + + if (GNUNET_SYSERR == + GNUNET_CONTAINER_multihashmap_iterate (map, + &build_deposits_response, + rctx)) + goto cleanup; + + result_mod = json_copy ((struct json_t *) result); + json_object_del (result_mod, + "deposits"); + json_object_set_new (result_mod, + "deposits_sums", + rctx->deposits_response); + rctx->deposits_response = NULL; +cleanup: + GNUNET_CONTAINER_multihashmap_iterate (map, + &hashmap_free, + NULL); + GNUNET_CONTAINER_multihashmap_destroy (map); + return result_mod; +} + + +/** + * Resume the given /track/transfer operation and send the given response. + * Stores the response in the @a rctx and signals MHD to resume + * the connection. Also ensures MHD runs immediately. + * + * @param rctx transfer tracking context + * @param response_code response code to use + * @param response response data to send back + */ +static void +resume_track_transfer_with_response (struct TrackTransferContext *rctx, + unsigned int response_code, + struct MHD_Response *response) +{ + rctx->response_code = response_code; + rctx->response = response; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Resuming /track/transfer handling as exchange interaction is done (%u)\n", + response_code); + if (NULL != rctx->timeout_task) + { + GNUNET_SCHEDULER_cancel (rctx->timeout_task); + rctx->timeout_task = NULL; + } + MHD_resume_connection (rctx->connection); + TMH_trigger_daemon (); /* we resumed, kick MHD */ +} + + +/** + * Custom cleanup routine for a `struct TrackTransferContext`. + * + * @param hc the `struct TrackTransferContext` to clean up. + */ +static void +track_transfer_cleanup (struct TM_HandlerContext *hc) +{ + struct TrackTransferContext *rctx = (struct TrackTransferContext *) hc; + + free_transfer_track_context (rctx); +} + + +/** + * This function checks that the information about the coin which + * was paid back by _this_ wire transfer matches what _we_ (the merchant) + * knew about this coin. + * + * @param cls closure with our `struct TrackTransferContext *` + * @param transaction_id of the contract + * @param coin_pub public key of the coin + * @param exchange_url URL of the exchange that issued @a coin_pub + * @param amount_with_fee amount the exchange will transfer for this coin + * @param deposit_fee fee the exchange will charge for this coin + * @param refund_fee fee the exchange will charge for refunding this coin + * @param exchange_proof proof from exchange that coin was accepted + */ +static void +check_transfer (void *cls, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const char *exchange_url, + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *deposit_fee, + const struct TALER_Amount *refund_fee, + const struct TALER_Amount *wire_fee, + const json_t *exchange_proof) +{ + struct TrackTransferContext *rctx = cls; + const struct TALER_TrackTransferDetails *ttd = rctx->current_detail; + + if (GNUNET_SYSERR == rctx->check_transfer_result) + return; /* already had a serious issue; odd that we're called more than once as well... */ + if ( (0 != TALER_amount_cmp (amount_with_fee, + &ttd->coin_value)) || + (0 != TALER_amount_cmp (deposit_fee, + &ttd->coin_fee)) ) + { + /* Disagreement between the exchange and us about how much this + coin is worth! */ + GNUNET_break_op (0); + rctx->check_transfer_result = GNUNET_SYSERR; + /* Build the `TrackTransferConflictDetails` */ + rctx->response + = TALER_MHD_make_json_pack ( + "{s:I, s:s, s:o, s:I, s:o, s:o, s:s, s:o, s:o}", + "code", (json_int_t) TALER_EC_TRACK_TRANSFER_CONFLICTING_REPORTS, + "hint", "disagreement about deposit valuation", + "exchange_deposit_proof", exchange_proof, + "conflict_offset", (json_int_t) rctx->current_offset, + "exchange_transfer_proof", rctx->original_response, + "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), + "h_contract_terms", GNUNET_JSON_from_data_auto ( + &ttd->h_contract_terms), + "amount_with_fee", TALER_JSON_from_amount (amount_with_fee), + "deposit_fee", TALER_JSON_from_amount (deposit_fee)); + return; + } + rctx->check_transfer_result = GNUNET_OK; +} + + +/** + * Check that the given @a wire_fee is what the + * @a exchange_pub should charge at the @a execution_time. + * If the fee is correct (according to our database), + * return #GNUNET_OK. If we do not have the fee structure + * in our DB, we just accept it and return #GNUNET_NO; + * if we have proof that the fee is bogus, we respond with + * the proof to the client and return #GNUNET_SYSERR. + * + * @param rctx context of the transfer to respond to + * @param json response from the exchange + * @param execution_time time of the wire transfer + * @param wire_fee fee claimed by the exchange + * @return #GNUNET_SYSERR if we returned hard proof of + * missbehavior from the exchange to the client + */ +static int +check_wire_fee (struct TrackTransferContext *rctx, + const json_t *json, + struct GNUNET_TIME_Absolute execution_time, + const struct TALER_Amount *wire_fee) +{ + const struct TALER_MasterPublicKeyP *master_pub; + struct GNUNET_HashCode h_wire_method; + struct TALER_Amount expected_fee; + struct TALER_Amount closing_fee; + struct TALER_MasterSignatureP master_sig; + struct GNUNET_TIME_Absolute start_date; + struct GNUNET_TIME_Absolute end_date; + enum GNUNET_DB_QueryStatus qs; + const struct TALER_EXCHANGE_Keys *keys; + + keys = TALER_EXCHANGE_get_keys (rctx->eh); + if (NULL == keys) + { + GNUNET_break (0); + return GNUNET_NO; + } + master_pub = &keys->master_pub; + GNUNET_CRYPTO_hash (rctx->wire_method, + strlen (rctx->wire_method) + 1, + &h_wire_method); + db->preflight (db->cls); + qs = db->lookup_wire_fee (db->cls, + master_pub, + &h_wire_method, + execution_time, + &expected_fee, + &closing_fee, + &start_date, + &end_date, + &master_sig); + if (0 >= qs) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to find wire fee for `%s' and method `%s' at %s in DB, accepting blindly that the fee is %s\n", + TALER_B2S (master_pub), + rctx->wire_method, + GNUNET_STRINGS_absolute_time_to_string (execution_time), + TALER_amount2s (wire_fee)); + return GNUNET_NO; + } + if (0 <= TALER_amount_cmp (&expected_fee, + wire_fee)) + return GNUNET_OK; /* expected_fee >= wire_fee */ + + /* Wire fee check failed, export proof to client */ + resume_track_transfer_with_response ( + rctx, + MHD_HTTP_FAILED_DEPENDENCY, + TALER_MHD_make_json_pack ( + "{s:I, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:O}", + "code", (json_int_t) TALER_EC_TRACK_TRANSFER_JSON_BAD_WIRE_FEE, + "wire_fee", TALER_JSON_from_amount (wire_fee), + "execution_time", GNUNET_JSON_from_time_abs (execution_time), + "expected_wire_fee", TALER_JSON_from_amount (&expected_fee), + "expected_closing_fee", TALER_JSON_from_amount (&closing_fee), + "start_date", GNUNET_JSON_from_time_abs (start_date), + "end_date", GNUNET_JSON_from_time_abs (end_date), + "master_sig", GNUNET_JSON_from_data_auto (&master_sig), + "master_pub", GNUNET_JSON_from_data_auto (master_pub), + "json", json)); + return GNUNET_SYSERR; +} + + +/** + * Function called with detailed wire transfer data, including all + * of the coin transactions that were combined into the wire transfer. + * + * @param cls closure + * @param hr HTTP response details + * @param exchange_pub public key of the exchange used to sign @a json + * @param h_wire hash of the wire transfer address the transfer went to, or NULL on error + * @param execution_time time when the exchange claims to have performed the wire transfer + * @param total_amount total amount of the wire transfer, or NULL if the exchange could + * not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) + * @param wire_fee wire fee that was charged by the exchange + * @param details_length length of the @a details array + * @param details array with details about the combined transactions + */ +static void +wire_transfer_cb (void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const struct GNUNET_HashCode *h_wire, + struct GNUNET_TIME_Absolute execution_time, + const struct TALER_Amount *total_amount, + const struct TALER_Amount *wire_fee, + unsigned int details_length, + const struct TALER_TrackTransferDetails *details) +{ + struct TrackTransferContext *rctx = cls; + json_t *jresponse; + enum GNUNET_DB_QueryStatus qs; + + rctx->wdh = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Got response code %u from exchange for /track/transfer\n", + hr->http_status); + if (MHD_HTTP_OK != hr->http_status) + { + resume_track_transfer_with_response ( + rctx, + MHD_HTTP_FAILED_DEPENDENCY, + TALER_MHD_make_json_pack ( + "{s:I, s:I, s:I, s:O}", + "code", (json_int_t) TALER_EC_TRACK_TRANSFER_EXCHANGE_ERROR, + "exchange_code", (json_int_t) hr->ec, + "exchange_http_status", (json_int_t) hr->http_status, + "exchange_reply", hr->reply)); + return; + } + for (unsigned int i = 0; ipreflight (db->cls); + qs = db->store_transfer_to_proof (db->cls, + rctx->url, + &rctx->wtid, + execution_time, + exchange_pub, + hr->reply); + if (GNUNET_DB_STATUS_SOFT_ERROR != qs) + break; + } + if (0 > qs) + { + /* Special report if retries insufficient */ + 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); + resume_track_transfer_with_response + (rctx, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_MHD_make_json_pack ("{s:I, s:s}", + "code", + (json_int_t) + TALER_EC_TRACK_TRANSFER_DB_STORE_TRANSFER_ERROR, + "details", + "failed to store response from exchange to local database")); + return; + } + rctx->original_response = hr->reply; + + if (GNUNET_SYSERR == + check_wire_fee (rctx, + hr->reply, + execution_time, + wire_fee)) + return; + + /* Now we want to double-check that any (Taler coin) deposit + * which is accounted into _this_ wire transfer, does exist + * into _our_ database. This is the rationale: if the + * exchange paid us for it, we must have received it _beforehands_! + * + * details_length is how many (Taler coin) deposits have been + * aggregated into _this_ wire transfer. + */// + for (unsigned int i = 0; icurrent_offset = i; + rctx->current_detail = &details[i]; + /* Set the coin as "never seen" before. */ + rctx->check_transfer_result = GNUNET_NO; + db->preflight (db->cls); + qs = db->find_payments_by_hash_and_coin (db->cls, + &details[i].h_contract_terms, + &rctx->mi->pubkey, + &details[i].coin_pub, + &check_transfer, + rctx); + if (0 > qs) + { + /* single, read-only SQL statements should never cause + serialization problems */ + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); + /* Always report on hard error as well to enable diagnostics */ + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + resume_track_transfer_with_response + (rctx, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_MHD_make_json_pack ("{s:I, s:s}", + "code", + (json_int_t) + TALER_EC_TRACK_TRANSFER_DB_FETCH_DEPOSIT_ERROR, + "details", + "failed to obtain deposit data from local database")); + return; + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + /* The exchange says we made this deposit, but WE do not + recall making it (corrupted / unreliable database?)! + Well, let's say thanks and accept the money! */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to find payment data in DB\n"); + rctx->check_transfer_result = GNUNET_OK; + } + if (GNUNET_NO == rctx->check_transfer_result) + { + /* Internal error: how can we have called #check_transfer() + but still have no result? */ + GNUNET_break (0); + resume_track_transfer_with_response + (rctx, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_MHD_make_json_pack ("{s:I, s:s, s:I, s:s}", + "code", + (json_int_t) + TALER_EC_TRACK_TRANSFER_DB_INTERNAL_LOGIC_ERROR, + "details", "internal logic error", + "line", (json_int_t) __LINE__, + "file", __FILE__)); + return; + } + if (GNUNET_SYSERR == rctx->check_transfer_result) + { + /* #check_transfer() failed, report conflict! */ + GNUNET_break_op (0); + GNUNET_assert (NULL != rctx->response); + resume_track_transfer_with_response + (rctx, + MHD_HTTP_FAILED_DEPENDENCY, + rctx->response); + rctx->response = NULL; + return; + } + /* Response is consistent with the /deposit we made, + remember it for future reference */ + for (unsigned int r = 0; rpreflight (db->cls); + qs = db->store_coin_to_transfer (db->cls, + &details[i].h_contract_terms, + &details[i].coin_pub, + &rctx->wtid); + if (GNUNET_DB_STATUS_SOFT_ERROR != qs) + break; + } + if (0 > qs) + { + /* Special report if retries insufficient */ + 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); + resume_track_transfer_with_response + (rctx, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_MHD_make_json_pack ("{s:I, s:s}", + "code", + (json_int_t) + TALER_EC_TRACK_TRANSFER_DB_STORE_COIN_ERROR, + "details", + "failed to store response from exchange to local database")); + return; + } + } + rctx->original_response = NULL; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "About to call tracks transformator.\n"); + + if (NULL == (jresponse = + transform_response (hr->reply, + rctx))) + { + resume_track_transfer_with_response + (rctx, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_MHD_make_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR, + "Fail to elaborate the response.")); + return; + } + + resume_track_transfer_with_response (rctx, + MHD_HTTP_OK, + TALER_MHD_make_json (jresponse)); + json_decref (jresponse); +} + + +/** + * Function called with the result of our exchange lookup. + * + * @param cls the `struct TrackTransferContext` + * @param hr HTTP response details + * @param eh NULL if exchange was not found to be acceptable + * @param wire_fee NULL (we did not specify a wire method) + * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config + */ +static void +process_track_transfer_with_exchange (void *cls, + const struct + TALER_EXCHANGE_HttpResponse *hr, + struct TALER_EXCHANGE_Handle *eh, + const struct TALER_Amount *wire_fee, + int exchange_trusted) +{ + struct TrackTransferContext *rctx = cls; + + rctx->fo = NULL; + if (MHD_HTTP_OK != hr->http_status) + { + /* The request failed somehow */ + GNUNET_break_op (0); + resume_track_transfer_with_response ( + rctx, + MHD_HTTP_FAILED_DEPENDENCY, + TALER_MHD_make_json_pack ( + (NULL != hr->reply) + ? "{s:s, s:I, s:I, s:I, s:O}" + : "{s:s, s:I, s:I, s:I}", + "hint", "failed to obtain meta-data from exchange", + "code", (json_int_t) TALER_EC_TRACK_TRANSFER_EXCHANGE_KEYS_FAILURE, + "exchange_http_status", (json_int_t) hr->http_status, + "exchange_code", (json_int_t) hr->ec, + "exchange_reply", hr->reply)); + return; + } + rctx->eh = eh; + rctx->wdh = TALER_EXCHANGE_transfers_get (eh, + &rctx->wtid, + &wire_transfer_cb, + rctx); + if (NULL == rctx->wdh) + { + GNUNET_break (0); + resume_track_transfer_with_response + (rctx, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_MHD_make_json_pack ("{s:I, s:s}", + "code", + (json_int_t) + TALER_EC_TRACK_TRANSFER_REQUEST_ERROR, + "error", + "failed to run /transfers/ GET on exchange")); + } +} + + +/** + * Handle a timeout for the processing of the track transfer request. + * + * @param cls closure + */ +static void +handle_track_transfer_timeout (void *cls) +{ + struct TrackTransferContext *rctx = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Resuming /track/transfer with error after timeout\n"); + rctx->timeout_task = NULL; + + if (NULL != rctx->fo) + { + TMH_EXCHANGES_find_exchange_cancel (rctx->fo); + rctx->fo = NULL; + } + resume_track_transfer_with_response (rctx, + MHD_HTTP_SERVICE_UNAVAILABLE, + TALER_MHD_make_error ( + TALER_EC_TRACK_TRANSFER_EXCHANGE_TIMEOUT, + "exchange not reachable")); +} + + +/** + * Function called with information about a wire transfer identifier. + * Generate a response based on the given @a proof. + * + * @param cls closure + * @param proof proof from exchange about what the wire transfer was for. + * should match the `TrackTransactionResponse` format + * of the exchange + */ +static void +proof_cb (void *cls, + const json_t *proof) +{ + struct TrackTransferContext *rctx = cls; + json_t *transformed_response; + + if (NULL == (transformed_response = + transform_response (proof, + rctx))) + { + rctx->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; + rctx->response + = TALER_MHD_make_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR, + "Fail to elaborate response."); + return; + } + + rctx->response_code = MHD_HTTP_OK; + rctx->response = TALER_MHD_make_json (transformed_response); + json_decref (transformed_response); +} + + +/** + * Manages a /track/transfer call, thus it calls the /track/wtid + * offered by the exchange in order to return the set of transfers + * (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 + * @param mi merchant backend instance, never NULL + * @return MHD result code + */ +MHD_RESULT +MH_handler_track_transfer (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size, + struct MerchantInstance *mi) +{ + struct TrackTransferContext *rctx; + const char *str; + const char *url; + const char *wire_method; + MHD_RESULT ret; + enum GNUNET_DB_QueryStatus qs; + + if (NULL == *connection_cls) + { + rctx = GNUNET_new (struct TrackTransferContext); + rctx->hc.cc = &track_transfer_cleanup; + rctx->connection = connection; + *connection_cls = rctx; + } + else + { + /* not first call, recover state */ + rctx = *connection_cls; + } + + if (0 != rctx->response_code) + { + /* We are *done* processing the request, just queue the response (!) */ + if (UINT_MAX == rctx->response_code) + { + GNUNET_break (0); + return MHD_NO; /* hard error */ + } + ret = MHD_queue_response (connection, + rctx->response_code, + rctx->response); + if (NULL != rctx->response) + { + MHD_destroy_response (rctx->response); + rctx->response = NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Queueing response (%u) for /track/transfer (%s).\n", + (unsigned int) rctx->response_code, + ret ? "OK" : "FAILED"); + return ret; + } + if ( (NULL != rctx->fo) || + (NULL != rctx->eh) ) + { + /* likely old MHD version */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Not sure why we are here, should be suspended\n"); + return MHD_YES; /* still work in progress */ + } + + url = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "exchange"); + if (NULL == url) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MISSING, + "exchange"); + rctx->url = GNUNET_strdup (url); + + /* FIXME: change again: we probably don't want the wire_method + but rather the _account_ (section) here! */ + wire_method = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "wire_method"); + if (NULL == wire_method) + { + if (1) + { + /* temporary work-around until demo is adjusted... */ + GNUNET_break (0); + wire_method = "x-taler-bank"; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Client needs fixing, see API change for #4943!\n"); + } + else + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MISSING, + "wire_method"); + } + rctx->wire_method = GNUNET_strdup (wire_method); + rctx->mi = mi; + str = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "wtid"); + if (NULL == str) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MISSING, + "wtid"); + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (str, + strlen (str), + &rctx->wtid, + sizeof (rctx->wtid))) + { + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MALFORMED, + "wtid"); + } + + /* Check if reply is already in database! */ + db->preflight (db->cls); + qs = db->find_proof_by_wtid (db->cls, + rctx->url, + &rctx->wtid, + &proof_cb, + rctx); + if (0 > qs) + { + /* Simple select queries should not cause serialization issues */ + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); + /* Always report on hard error as well to enable diagnostics */ + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_TRACK_TRANSFER_DB_FETCH_DEPOSIT_ERROR, + "Fail to query database about proofs"); + } + if (0 != rctx->response_code) + { + ret = MHD_queue_response (connection, + rctx->response_code, + rctx->response); + if (NULL != rctx->response) + { + MHD_destroy_response (rctx->response); + rctx->response = NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Queueing response (%u) for /track/transfer (%s).\n", + (unsigned int) rctx->response_code, + ret ? "OK" : "FAILED"); + return ret; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Suspending /track/transfer handling while working with the exchange\n"); + MHD_suspend_connection (connection); + rctx->fo = TMH_EXCHANGES_find_exchange (url, + NULL, + GNUNET_NO, + &process_track_transfer_with_exchange, + rctx); + rctx->timeout_task + = GNUNET_SCHEDULER_add_delayed (TRACK_TIMEOUT, + &handle_track_transfer_timeout, + rctx); + return MHD_YES; +} + + +/* end of taler-merchant-httpd_track-transfer.c */ diff --git a/src/backend/taler-merchant-httpd_private-post-transfers.h b/src/backend/taler-merchant-httpd_private-post-transfers.h new file mode 100644 index 00000000..0463295e --- /dev/null +++ b/src/backend/taler-merchant-httpd_private-post-transfers.h @@ -0,0 +1,49 @@ +/* + This file is part of TALER + (C) 2014-2020 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 +*/ +/** + * @file backend/taler-merchant-httpd_track-transfer.h + * @brief headers for /track/transfer handler + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#ifndef TALER_MERCHANT_HTTPD_TRACK_TRANSFER_H +#define TALER_MERCHANT_HTTPD_TRACK_TRANSFER_H +#include +#include "taler-merchant-httpd.h" + +/** + * Manages a /track/transfer call, thus it calls the /wire/transfer + * offered by the exchange in order to return the set of transfers + * (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 + * @param mi merchant backend instance, never NULL + * @return MHD result code + */ +MHD_RESULT +MH_handler_track_transfer (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/include/taler_merchant_service.h b/src/include/taler_merchant_service.h index bc5eae14..a4312c09 100644 --- a/src/include/taler_merchant_service.h +++ b/src/include/taler_merchant_service.h @@ -1363,6 +1363,62 @@ TALER_MERCHANT_order_delete_cancel ( struct TALER_MERCHANT_OrderDeleteHandle *odh); +/** + * Handle to a POST /orders/$ID/claim handle + */ +struct TALER_MERCHANT_OrderClaimHandle; + + +/** + * Callback called to process a POST /orders/$ID/claim response. + * + * @param cls closure + * @param hr HTTP response details + * @param contract_terms the details of the contract + * @param sig merchant's signature over @a contract_terms (already verified) + * @param h_contract_terms hash over @a contract_terms (computed + * client-side to verify @a sig) + */ +typedef void +(*TALER_MERCHANT_OrderClaimCallback) ( + void *cls, + const struct TALER_MERCHANT_HttpResponse *hr, + const json_t *contract_terms, + const struct TALER_MerchantSignatureP *sig, + const struct GNUNET_HashCode *h_contract_terms); + + +/** + * Calls the POST /orders/$ID/claim API at the backend. That is, + * retrieve the final contract terms including the client nonce. + * This is a PUBLIC API for wallets. + * + * @param ctx execution context + * @param backend_url base URL of the merchant backend + * @param order_id order id used to perform the lookup + * @param nonce nonce to use to claim the proposal + * @param cb callback which will work the response gotten from the backend + * @param cb_cls closure to pass to @a cb + * @return handle for this handle, NULL upon errors + */ +struct TALER_MERCHANT_OrderClaimHandle * +TALER_MERCHANT_order_claim (struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *order_id, + const struct GNUNET_CRYPTO_EddsaPublicKey *nonce, + TALER_MERCHANT_OrderClaimCallback cb, + void *cb_cls); + + +/** + * Cancel a POST /order/$ID/claim request. + * + * @param och handle to the request to be canceled + */ +void +TALER_MERCHANT_order_claim_cancel (struct TALER_MERCHANT_OrderClaimHandle *och); + + /* ********************* OLD ************************** */ @@ -1520,63 +1576,6 @@ TALER_MERCHANT_refund_increase_cancel ( /* ********************* /proposal *********************** */ -/** - * Handle to a GET /proposal operation - */ -struct TALER_MERCHANT_ProposalLookupOperation; - - -/** - * Callback called to work a GET /proposal response. - * - * @param cls closure - * @param hr HTTP response details - * @param contract_terms the details of the contract - * @param sig merchant's signature over @a contract_terms - * @param contract_hash hash over @a contract_terms - */ -typedef void -(*TALER_MERCHANT_ProposalLookupOperationCallback) ( - void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, - const json_t *contract_terms, - const struct TALER_MerchantSignatureP *sig, - const struct GNUNET_HashCode *contract_hash); - - -/** - * Calls the GET /proposal API at the backend. That is, - * retrieve a proposal data by providing its transaction id. - * - * @param ctx execution context - * @param backend_url base URL of the merchant backend - * @param order_id order id used to perform the lookup - * @param nonce nonce to use, only used when requesting the proposal the first time, - * can be NULL to omit the nonce (after the first request) - * @param plo_cb callback which will work the response gotten from the backend - * @param plo_cb_cls closure to pass to @a history_cb - * @return handle for this operation, NULL upon errors - */ -struct TALER_MERCHANT_ProposalLookupOperation * -TALER_MERCHANT_proposal_lookup ( - struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const char *order_id, - const struct GNUNET_CRYPTO_EddsaPublicKey *nonce, - TALER_MERCHANT_ProposalLookupOperationCallback plo_cb, - void *plo_cb_cls); - - -/** - * Cancel a GET /proposal request. - * - * @param plo handle to the request to be canceled - */ -void -TALER_MERCHANT_proposal_lookup_cancel ( - struct TALER_MERCHANT_ProposalLookupOperation *plo); - - /* ********************* /pay *********************** */ diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h index 33d23e50..4e0b3fd6 100644 --- a/src/include/taler_merchant_testing_lib.h +++ b/src/include/taler_merchant_testing_lib.h @@ -425,27 +425,27 @@ TALER_TESTING_cmd_merchant_post_orders (const char *label, const char *order); -/* ******************** OLD ******************* */ - - /** - * Make a "proposal lookup" command. + * Make a "claim order" command. * * @param label command label. * @param merchant_url base URL of the merchant backend * serving the proposal lookup request. * @param http_status expected HTTP response code. - * @param proposal_reference reference to a "proposal" CMD. - * @param order_id order id to lookup, can be NULL. - * + * @param order_reference reference to a POST order CMD, can be NULL if @a order_id given + * @param order_id order id to lookup, can be NULL (then we use @a order_reference) * @return the command. */ struct TALER_TESTING_Command -TALER_TESTING_cmd_merchant_post_orders_lookup (const char *label, - const char *merchant_url, - unsigned int http_status, - const char *proposal_reference, - const char *order_id); +TALER_TESTING_cmd_merchant_claim_order (const char *label, + const char *merchant_url, + unsigned int http_status, + const char *order_reference, + const char *order_id); + + +/* ******************** OLD ******************* */ + /** * Make a "check payment" test command. diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 2911c758..6e6fd54f 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -29,9 +29,10 @@ libtalermerchant_la_SOURCES = \ merchant_api_post_instances.c \ merchant_api_post_products.c \ merchant_api_post_orders.c \ + merchant_api_post_order_claim.c \ + \ merchant_api_check_payment.c \ merchant_api_history.c \ - merchant_api_proposal_lookup.c \ merchant_api_pay.c \ merchant_api_poll_payment.c \ merchant_api_refund.c \ diff --git a/src/lib/merchant_api_post_order_claim.c b/src/lib/merchant_api_post_order_claim.c new file mode 100644 index 00000000..31be1235 --- /dev/null +++ b/src/lib/merchant_api_post_order_claim.c @@ -0,0 +1,269 @@ +/* + This file is part of TALER + Copyright (C) 2014-2020 Taler Systems SA + + 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, + see +*/ +/** + * @file lib/merchant_api_post_order_claim.c + * @brief Implementation of POST /orders/$ID/claim + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include +#include +#include /* just for HTTP status codes */ +#include +#include +#include "taler_merchant_service.h" +#include +#include +#include + + +/** + * Structure representing a POST /orders/$ID/claim operation. + */ +struct TALER_MERCHANT_OrderClaimHandle +{ + /** + * Full URL, includes "/orders/$ID/claim". + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_OrderClaimCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; +}; + + +/** + * Function called when we're done processing the + * POST /orders/$ID/claim request. + * + * @param cls the `struct TALER_MERCHANT_OrderClaimHandle` + * @param response_code HTTP response code, 0 on error + * @param json response body, should be NULL + */ +static void +handle_post_order_claim_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_OrderClaimHandle *och = cls; + json_t *contract_terms; + struct TALER_MerchantSignatureP sig; + struct GNUNET_HashCode hash; + const json_t *json = response; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("contract_terms", + &contract_terms), + GNUNET_JSON_spec_fixed_auto ("sig", + &sig), + GNUNET_JSON_spec_end () + }; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + och->job = NULL; + if (MHD_HTTP_OK != response_code) + { + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Proposal lookup failed with HTTP status code %u/%d\n", + (unsigned int) response_code, + (int) hr.ec); + och->cb (och->cb_cls, + &hr, + NULL, + NULL, + NULL); + TALER_MERCHANT_order_claim_cancel (och); + return; + } + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Claiming order failed: could not parse JSON response\n"); + GNUNET_break_op (0); + hr.ec = TALER_EC_INVALID_RESPONSE; + hr.http_status = 0; + och->cb (och->cb_cls, + &hr, + NULL, + NULL, + NULL); + TALER_MERCHANT_order_claim_cancel (och); + return; + } + + if (GNUNET_OK != + TALER_JSON_hash (contract_terms, + &hash)) + { + GNUNET_break (0); + hr.ec = TALER_EC_CLIENT_INTERNAL_FAILURE; + hr.http_status = 0; + GNUNET_JSON_parse_free (spec); + och->cb (och->cb_cls, + &hr, + NULL, + NULL, + NULL); + TALER_MERCHANT_order_claim_cancel (och); + return; + } + + och->cb (och->cb_cls, + &hr, + contract_terms, + &sig, + &hash); + GNUNET_JSON_parse_free (spec); + TALER_MERCHANT_order_claim_cancel (och); +} + + +/** + * Calls the GET /proposal API at the backend. That is, + * retrieve a proposal data by providing its transaction id. + * + * @param ctx execution context + * @param backend_url base URL of the merchant backend + * @param order_id order id used to perform the lookup + * @param nonce nonce used to perform the lookup + * @param cb callback which will work the response gotten from the backend + * @param cb_cls closure to pass to @a cb + * @return handle for this handle, NULL upon errors + */ +struct TALER_MERCHANT_OrderClaimHandle * +TALER_MERCHANT_order_claim (struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *order_id, + const struct GNUNET_CRYPTO_EddsaPublicKey *nonce, + TALER_MERCHANT_OrderClaimCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_OrderClaimHandle *och; + json_t *req_obj; + + req_obj = json_pack ("{s:o}", + "nonce", + GNUNET_JSON_from_data_auto (nonce)); + if (NULL == req_obj) + { + GNUNET_break (0); + return NULL; + } + och = GNUNET_new (struct TALER_MERCHANT_OrderClaimHandle); + och->ctx = ctx; + och->cb = cb; + och->cb_cls = cb_cls; + { + char *path; + + GNUNET_asprintf (&path, + "orders/%s/claim", + order_id); + och->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == och->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + json_decref (req_obj); + GNUNET_free (och); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Claiming order at %s\n", + och->url); + { + CURL *eh; + + eh = curl_easy_init (); + GNUNET_assert (NULL != eh); + GNUNET_assert (GNUNET_OK == + TALER_curl_easy_post (&och->post_ctx, + eh, + req_obj)); + json_decref (req_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + och->url)); + och->job = GNUNET_CURL_job_add2 (ctx, + eh, + och->post_ctx.headers, + &handle_post_order_claim_finished, + och); + GNUNET_assert (NULL != och->job); + } + return och; +} + + +/** + * Cancel a POST /orders/$ID/claim request. + * + * @param och handle to the request to be canceled + */ +void +TALER_MERCHANT_order_claim_cancel (struct TALER_MERCHANT_OrderClaimHandle *och) +{ + if (NULL != och->job) + { + GNUNET_CURL_job_cancel (och->job); + och->job = NULL; + } + TALER_curl_easy_post_finished (&och->post_ctx); + GNUNET_free (och->url); + GNUNET_free (och); +} + + +/* end of merchant_api_post_order_claim.c */ diff --git a/src/lib/merchant_api_post_products.c b/src/lib/merchant_api_post_products.c index af2e4ea6..f354fb5b 100644 --- a/src/lib/merchant_api_post_products.c +++ b/src/lib/merchant_api_post_products.c @@ -243,17 +243,11 @@ TALER_MERCHANT_products_post ( CURL *eh; eh = curl_easy_init (); - if (GNUNET_OK != - TALER_curl_easy_post (&pph->post_ctx, - eh, - req_obj)) - { - GNUNET_break (0); - json_decref (req_obj); - GNUNET_free (pph); - return NULL; - } - + GNUNET_assert (NULL != eh); + GNUNET_assert (GNUNET_OK == + TALER_curl_easy_post (&pph->post_ctx, + eh, + req_obj)); json_decref (req_obj); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_URL, @@ -263,6 +257,7 @@ TALER_MERCHANT_products_post ( pph->post_ctx.headers, &handle_post_products_finished, pph); + GNUNET_assert (NULL != pph->job); } return pph; } diff --git a/src/lib/merchant_api_proposal_lookup.c b/src/lib/merchant_api_proposal_lookup.c deleted file mode 100644 index 0f2b0f3e..00000000 --- a/src/lib/merchant_api_proposal_lookup.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA - - 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, - see -*/ -/** - * @file lib/merchant_api_proposal_lookup.c - * @brief Implementation of the /proposal GET - * @author Christian Grothoff - * @author Marcello Stanisci - */ -#include "platform.h" -#include -#include -#include /* just for HTTP status codes */ -#include -#include -#include "taler_merchant_service.h" -#include -#include -#include - - -/** - * Structure representing a GET /proposal operation. - */ -struct TALER_MERCHANT_ProposalLookupOperation -{ - /** - * Full URL, includes "/proposal". - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_MERCHANT_ProposalLookupOperationCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; - - /** - * Should we send the lookup operation with a nonce? - */ - int has_nonce; - - /** - * Nonce, only initialized if has_nonce is GNUNET_YES. - */ - struct GNUNET_CRYPTO_EddsaPublicKey nonce; - -}; - - -/** - * Function called when we're done processing the GET /proposal request. - * - * @param cls the `struct TALER_MERCHANT_ProposalLookupOperation` - * @param response_code HTTP response code, 0 on error - * @param json response body, should be NULL - */ -static void -handle_proposal_lookup_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_MERCHANT_ProposalLookupOperation *plo = cls; - json_t *contract_terms; - struct TALER_MerchantSignatureP sig; - struct GNUNET_HashCode hash; - const json_t *json = response; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("contract_terms", - &contract_terms), - GNUNET_JSON_spec_fixed_auto ("sig", - &sig), - GNUNET_JSON_spec_end () - }; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json - }; - - plo->job = NULL; - if (MHD_HTTP_OK != response_code) - { - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Proposal lookup failed with HTTP status code %u/%d\n", - (unsigned int) response_code, - (int) hr.ec); - plo->cb (plo->cb_cls, - &hr, - NULL, - NULL, - NULL); - TALER_MERCHANT_proposal_lookup_cancel (plo); - return; - } - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "proposal lookup failed to parse JSON\n"); - GNUNET_break_op (0); - hr.ec = TALER_EC_INVALID_RESPONSE; - hr.http_status = 0; - plo->cb (plo->cb_cls, - &hr, - NULL, - NULL, - NULL); - TALER_MERCHANT_proposal_lookup_cancel (plo); - return; - } - - if (GNUNET_OK != - TALER_JSON_hash (contract_terms, - &hash)) - { - GNUNET_break (0); - hr.ec = TALER_EC_CLIENT_INTERNAL_FAILURE; - hr.http_status = 0; - GNUNET_JSON_parse_free (spec); - plo->cb (plo->cb_cls, - &hr, - NULL, - NULL, - NULL); - TALER_MERCHANT_proposal_lookup_cancel (plo); - return; - } - - plo->job = NULL; - /** - * As no data is supposed to be extracted from this - * call, we just invoke the provided callback. - */ - plo->cb (plo->cb_cls, - &hr, - contract_terms, - &sig, - &hash); - GNUNET_JSON_parse_free (spec); - TALER_MERCHANT_proposal_lookup_cancel (plo); -} - - -/** - * Calls the GET /proposal API at the backend. That is, - * retrieve a proposal data by providing its transaction id. - * - * @param ctx execution context - * @param backend_url base URL of the merchant backend - * @param order_id order id used to perform the lookup - * @param nonce nonce used to perform the lookup - * @param plo_cb callback which will work the response gotten from the backend - * @param plo_cb_cls closure to pass to @a history_cb - * @return handle for this operation, NULL upon errors - */ -struct TALER_MERCHANT_ProposalLookupOperation * -TALER_MERCHANT_proposal_lookup ( - struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const char *order_id, - const struct GNUNET_CRYPTO_EddsaPublicKey *nonce, - TALER_MERCHANT_ProposalLookupOperationCallback plo_cb, - void *plo_cb_cls) -{ - struct TALER_MERCHANT_ProposalLookupOperation *plo; - CURL *eh; - char *nonce_str = NULL; - - plo = GNUNET_new (struct TALER_MERCHANT_ProposalLookupOperation); - plo->ctx = ctx; - plo->cb = plo_cb; - plo->cb_cls = plo_cb_cls; - if (NULL != nonce) - { - plo->has_nonce = GNUNET_YES; - plo->nonce = *nonce; - nonce_str = GNUNET_STRINGS_data_to_string_alloc ( - nonce, - sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); - } - plo->url = TALER_url_join (backend_url, - "proposal", - "order_id", - order_id, - "nonce", - nonce_str, - NULL); - GNUNET_free_non_null (nonce_str); - if (NULL == plo->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (plo); - return NULL; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "looking up proposal from %s\n", - plo->url); - eh = curl_easy_init (); - if (CURLE_OK != curl_easy_setopt (eh, - CURLOPT_URL, - plo->url)) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - GNUNET_free (plo->url); - GNUNET_free (plo); - return NULL; - } - - if (NULL == (plo->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_proposal_lookup_finished, - plo))) - { - GNUNET_break (0); - GNUNET_free (plo->url); - GNUNET_free (plo); - return NULL; - } - return plo; -} - - -/** - * Cancel a GET /proposal request. - * - * @param plo handle to the request to be canceled - */ -void -TALER_MERCHANT_proposal_lookup_cancel ( - struct TALER_MERCHANT_ProposalLookupOperation *plo) -{ - if (NULL != plo->job) - { - GNUNET_CURL_job_cancel (plo->job); - plo->job = NULL; - } - GNUNET_free (plo->url); - GNUNET_free (plo); -} - - -/* end of merchant_api_proposal_lookup.c */ diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index d7d0256b..c04c5b3f 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -15,6 +15,7 @@ libtalermerchanttesting_la_LDFLAGS = \ libtalermerchanttesting_la_SOURCES = \ testing_api_cmd_config.c \ + testing_api_cmd_claim_order.c \ testing_api_cmd_get_instance.c \ testing_api_cmd_get_instances.c \ testing_api_cmd_get_product.c \ @@ -34,7 +35,6 @@ libtalermerchanttesting_la_SOURCES = \ testing_api_cmd_pay_abort.c \ testing_api_cmd_pay_abort_refund.c \ testing_api_cmd_poll_payment.c \ - testing_api_cmd_proposal_lookup.c \ testing_api_cmd_refund_increase.c \ testing_api_cmd_refund_lookup.c \ testing_api_cmd_rewind.c \ diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c index e2fbfc29..a739807b 100644 --- a/src/testing/test_merchant_api.c +++ b/src/testing/test_merchant_api.c @@ -348,11 +348,11 @@ run (void *cls, \"fulfillment_url\": \"https://example.com/\",\ \"products\": [ {\"description\":\"ice cream\",\ \"value\":\"{EUR:5}\"} ] }"), - TALER_TESTING_cmd_merchant_post_orders_lookup ("fetch-proposal-2", - merchant_url, - MHD_HTTP_OK, - "create-proposal-2", - NULL), + TALER_TESTING_cmd_merchant_claim_order ("fetch-proposal-2", + merchant_url, + MHD_HTTP_OK, + "create-proposal-2", + NULL), TALER_TESTING_cmd_pay ("deposit-double-2", merchant_url, MHD_HTTP_CONFLICT, diff --git a/src/testing/testing_api_cmd_claim_order.c b/src/testing/testing_api_cmd_claim_order.c new file mode 100644 index 00000000..0cdfff0e --- /dev/null +++ b/src/testing/testing_api_cmd_claim_order.c @@ -0,0 +1,311 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018, 2020 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 + +*/ + +/** + * @file exchange/testing_api_cmd_claim_order.c + * @brief command to claim an order + * @author Marcello Stanisci + */ +#include "platform.h" +#include +#include +#include "taler_merchant_service.h" +#include "taler_merchant_testing_lib.h" + +/** + * State for a "order claim" CMD. Not used by + * the initial claim operation. + */ +struct OrderClaimState +{ + /** + * The interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * URL of the merchant backend. + */ + const char *merchant_url; + + /** + * Contract terms we downloaded. Only set if we got #MHD_HTTP_OK. + */ + json_t *contract_terms; + + /** + * Hash over the contract terms. Only set if we got #MHD_HTTP_OK. + */ + struct GNUNET_HashCode contract_terms_hash; + + /** + * Signature of the merchant. Only set if we got #MHD_HTTP_OK. + */ + struct TALER_MerchantSignatureP merchant_sig; + + /** + * Public key of the merchant. Only set if we got #MHD_HTTP_OK. + */ + struct TALER_MerchantPublicKeyP merchant_pub; + + /** + * Expected status code. + */ + unsigned int http_status; + + /** + * /order/claim operation handle. + */ + struct TALER_MERCHANT_OrderClaimHandle *och; + + /** + * Reference to a order operation. Will offer the + * nonce for the operation. + */ + const char *order_reference; + + /** + * Order id to claim upon. If null, the @a order_reference + * will offer this value. + */ + const char *order_id; +}; + + +/** + * Free the state of a "order claim" CMD, and possibly + * cancel it if it did not complete. + * + * @param cls closure. + * @param cmd command being freed. + */ +static void +order_claim_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct OrderClaimState *pls = cls; + + if (NULL != pls->och) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command '%s' did not complete\n", + cmd->label); + TALER_MERCHANT_order_claim_cancel (pls->och); + pls->och = NULL; + } + if (NULL != pls->contract_terms) + { + json_decref (pls->contract_terms); + pls->contract_terms = NULL; + } + GNUNET_free (pls); +} + + +/** + * Callback for "order claim" operation, to check the + * response code is as expected. + * + * @param cls closure + * @param hr HTTP response we got + * @param contract_terms the contract terms; they are the + * backend-filled up order minus cryptographic + * information. + * @param sig merchant signature over the contract terms. + * @param hash hash code of the contract terms. + */ +static void +order_claim_cb (void *cls, + const struct TALER_MERCHANT_HttpResponse *hr, + const json_t *contract_terms, + const struct TALER_MerchantSignatureP *sig, + const struct GNUNET_HashCode *hash) +{ + struct OrderClaimState *pls = cls; + + pls->och = NULL; + if (pls->http_status != hr->http_status) + TALER_TESTING_FAIL (pls->is); + if (MHD_HTTP_OK == hr->http_status) + { + pls->contract_terms = json_object_get (hr->reply, + "contract_terms"); + if (NULL == pls->contract_terms) + TALER_TESTING_FAIL (pls->is); + json_incref (pls->contract_terms); + pls->contract_terms_hash = *hash; + pls->merchant_sig = *sig; + { + const char *error_name; + unsigned int error_line; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &pls->merchant_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (contract_terms, + spec, + &error_name, + &error_line)) + TALER_TESTING_FAIL (pls->is); + } + } + TALER_TESTING_interpreter_next (pls->is); +} + + +/** + * Run the "order claim" CMD. + * + * @param cls closure. + * @param cmd command currently being run. + * @param is interpreter state. + */ +static void +order_claim_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct OrderClaimState *pls = cls; + const char *order_id; + const struct TALER_MerchantPublicKeyP *nonce; + /* Only used if we do NOT use the nonce from traits. */ + struct TALER_MerchantPublicKeyP dummy_nonce; +#define GET_TRAIT_NONCE(cmd,ptr) \ + TALER_TESTING_get_trait_merchant_pub (cmd, 1, ptr) + + pls->is = is; + if (NULL != pls->order_id) + { + order_id = pls->order_id; + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &dummy_nonce, + sizeof (dummy_nonce)); + nonce = &dummy_nonce; + } + else + { + const struct TALER_TESTING_Command *order_cmd; + + order_cmd + = TALER_TESTING_interpreter_lookup_command (is, + pls->order_reference); + if (NULL == order_cmd) + TALER_TESTING_FAIL (is); + if (GNUNET_OK != + GET_TRAIT_NONCE (order_cmd, + &nonce)) + { + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &dummy_nonce, + sizeof (dummy_nonce)); + nonce = &dummy_nonce; + } + + if (GNUNET_OK != + TALER_TESTING_get_trait_order_id (order_cmd, + 0, + &order_id)) + TALER_TESTING_FAIL (is); + } + pls->och = TALER_MERCHANT_order_claim (is->ctx, + pls->merchant_url, + order_id, + &nonce->eddsa_pub, + &order_claim_cb, + pls); + GNUNET_assert (NULL != pls->och); +} + + +/** + * Offer internal data to other commands. + * + * @param cls closure + * @param ret[out] result (could be anything) + * @param trait name of the trait + * @param index index number of the object to extract. + * @return #GNUNET_OK on success + */ +static int +order_claim_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct OrderClaimState *pls = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_contract_terms (0, + pls->contract_terms), + TALER_TESTING_make_trait_h_contract_terms (0, + &pls->contract_terms_hash), + TALER_TESTING_make_trait_merchant_sig (0, + &pls->merchant_sig), + TALER_TESTING_make_trait_merchant_pub (0, + &pls->merchant_pub), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Make a "order claim" command. + * + * @param label command label. + * @param merchant_url base URL of the merchant backend + * serving the order claim request. + * @param http_status expected HTTP response code. + * @param order_reference reference to a POST order CMD, can be NULL if @a order_id given + * @param order_id order id to lookup, can be NULL (then we use @a order_reference) + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_merchant_claim_order ( + const char *label, + const char *merchant_url, + unsigned int http_status, + const char *order_reference, + const char *order_id) +{ + struct OrderClaimState *pls; + + pls = GNUNET_new (struct OrderClaimState); + pls->http_status = http_status; + pls->order_reference = order_reference; + pls->merchant_url = merchant_url; + pls->order_id = order_id; + { + struct TALER_TESTING_Command cmd = { + .cls = pls, + .label = label, + .run = &order_claim_run, + .cleanup = &order_claim_cleanup, + .traits = &order_claim_traits + }; + + return cmd; + } +} diff --git a/src/testing/testing_api_cmd_post_orders.c b/src/testing/testing_api_cmd_post_orders.c index c7635e31..d8bf23f0 100644 --- a/src/testing/testing_api_cmd_post_orders.c +++ b/src/testing/testing_api_cmd_post_orders.c @@ -66,12 +66,11 @@ struct OrdersState struct TALER_MERCHANT_PostOrdersOperation *po; /** - * The (initial) GET /orders/$ID operation handle. - * The logic is such that after a orders creation, - * it soon makes a orders lookup in order to check - * if the merchant backend is actually aware. + * The (initial) POST /orders/$ID/claim operation handle. + * The logic is such that after an order creation, + * we immediately claim the order. */ - struct TALER_MERCHANT_ProposalLookupOperation *plo; + struct TALER_MERCHANT_OrderClaimHandle *och; /** * The nonce. @@ -139,7 +138,7 @@ orders_traits (void *cls, /** * Used to fill the "orders" CMD state with backend-provided - * values. Also double-checks that the orders was correctly + * values. Also double-checks that the order was correctly * created. * * @param cls closure @@ -148,11 +147,11 @@ orders_traits (void *cls, * @param hash hash over the contract */ static void -orders_lookup_initial_cb (void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, - const json_t *contract_terms, - const struct TALER_MerchantSignatureP *sig, - const struct GNUNET_HashCode *hash) +orders_claim_cb (void *cls, + const struct TALER_MERCHANT_HttpResponse *hr, + const json_t *contract_terms, + const struct TALER_MerchantSignatureP *sig, + const struct GNUNET_HashCode *hash) { struct OrdersState *ps = cls; struct TALER_MerchantPublicKeyP merchant_pub; @@ -164,7 +163,7 @@ orders_lookup_initial_cb (void *cls, GNUNET_JSON_spec_end () }; - ps->plo = NULL; + ps->och = NULL; if (ps->http_status != hr->http_status) TALER_TESTING_FAIL (ps->is); @@ -254,12 +253,12 @@ order_cb (void *cls, } if (NULL == - (ps->plo = TALER_MERCHANT_proposal_lookup (ps->is->ctx, - ps->merchant_url, - ps->order_id, - &ps->nonce, - &orders_lookup_initial_cb, - ps))) + (ps->och = TALER_MERCHANT_order_claim (ps->is->ctx, + ps->merchant_url, + ps->order_id, + &ps->nonce, + &orders_claim_cb, + ps))) TALER_TESTING_FAIL (ps->is); } @@ -344,14 +343,14 @@ orders_cleanup (void *cls, ps->po = NULL; } - if (NULL != ps->plo) + if (NULL != ps->och) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Command '%s' did not complete" " (orders lookup)\n", cmd->label); - TALER_MERCHANT_proposal_lookup_cancel (ps->plo); - ps->plo = NULL; + TALER_MERCHANT_order_claim_cancel (ps->och); + ps->och = NULL; } json_decref (ps->contract_terms); diff --git a/src/testing/testing_api_cmd_proposal_lookup.c b/src/testing/testing_api_cmd_proposal_lookup.c deleted file mode 100644 index 78f2fb8b..00000000 --- a/src/testing/testing_api_cmd_proposal_lookup.c +++ /dev/null @@ -1,311 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2018 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 - -*/ - -/** - * @file exchange/testing_api_cmd_proposal_lookup.c - * @brief command to execute a proposal lookup - * @author Marcello Stanisci - */ -#include "platform.h" -#include -#include -#include "taler_merchant_service.h" -#include "taler_merchant_testing_lib.h" - -/** - * State for a "proposal lookup" CMD. Not used by - * the initial lookup operation. - */ -struct ProposalLookupState -{ - /** - * The interpreter state. - */ - struct TALER_TESTING_Interpreter *is; - - /** - * URL of the merchant backend. - */ - const char *merchant_url; - - /** - * Contract terms we downloaded. Only set if we got #MHD_HTTP_OK. - */ - json_t *contract_terms; - - /** - * Hash over the contract terms. Only set if we got #MHD_HTTP_OK. - */ - struct GNUNET_HashCode contract_terms_hash; - - /** - * Signature of the merchant. Only set if we got #MHD_HTTP_OK. - */ - struct TALER_MerchantSignatureP merchant_sig; - - /** - * Public key of the merchant. Only set if we got #MHD_HTTP_OK. - */ - struct TALER_MerchantPublicKeyP merchant_pub; - - /** - * Expected status code. - */ - unsigned int http_status; - - /** - * /proposal/lookup operation handle. - */ - struct TALER_MERCHANT_ProposalLookupOperation *plo; - - /** - * Reference to a proposal operation. Will offer the - * nonce for the operation. - */ - const char *proposal_reference; - - /** - * Order id to lookup upon. If null, the @a proposal_reference - * will offer this value. - */ - const char *order_id; -}; - - -/** - * Free the state of a "proposal lookup" CMD, and possibly - * cancel it if it did not complete. - * - * @param cls closure. - * @param cmd command being freed. - */ -static void -proposal_lookup_cleanup (void *cls, - const struct TALER_TESTING_Command *cmd) -{ - struct ProposalLookupState *pls = cls; - - if (NULL != pls->plo) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Command '%s' did not complete\n", - cmd->label); - TALER_MERCHANT_proposal_lookup_cancel (pls->plo); - pls->plo = NULL; - } - if (NULL != pls->contract_terms) - { - json_decref (pls->contract_terms); - pls->contract_terms = NULL; - } - GNUNET_free (pls); -} - - -/** - * Callback for "proposal lookup" operation, to check the - * response code is as expected. - * - * @param cls closure - * @param hr HTTP response we got - * @param contract_terms the contract terms; they are the - * backend-filled up proposal minus cryptographic - * information. - * @param sig merchant signature over the contract terms. - * @param hash hash code of the contract terms. - */ -static void -proposal_lookup_cb (void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, - const json_t *contract_terms, - const struct TALER_MerchantSignatureP *sig, - const struct GNUNET_HashCode *hash) -{ - struct ProposalLookupState *pls = cls; - - pls->plo = NULL; - if (pls->http_status != hr->http_status) - TALER_TESTING_FAIL (pls->is); - if (MHD_HTTP_OK == hr->http_status) - { - pls->contract_terms = json_object_get (hr->reply, - "contract_terms"); - if (NULL == pls->contract_terms) - TALER_TESTING_FAIL (pls->is); - json_incref (pls->contract_terms); - pls->contract_terms_hash = *hash; - pls->merchant_sig = *sig; - { - const char *error_name; - unsigned int error_line; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("merchant_pub", - &pls->merchant_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (contract_terms, - spec, - &error_name, - &error_line)) - TALER_TESTING_FAIL (pls->is); - } - } - TALER_TESTING_interpreter_next (pls->is); -} - - -/** - * Run the "proposal lookup" CMD. - * - * @param cls closure. - * @param cmd command currently being run. - * @param is interpreter state. - */ -static void -proposal_lookup_run (void *cls, - const struct TALER_TESTING_Command *cmd, - struct TALER_TESTING_Interpreter *is) -{ - struct ProposalLookupState *pls = cls; - const char *order_id; - const struct TALER_MerchantPublicKeyP *nonce; - /* Only used if we do NOT use the nonce from traits. */ - struct TALER_MerchantPublicKeyP dummy_nonce; - #define GET_TRAIT_NONCE(cmd,ptr) \ - TALER_TESTING_get_trait_merchant_pub (cmd, 1, ptr) - - pls->is = is; - - if (NULL != pls->order_id) - { - order_id = pls->order_id; - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, - &dummy_nonce, - sizeof (dummy_nonce)); - nonce = &dummy_nonce; - } - else - { - const struct TALER_TESTING_Command *proposal_cmd; - - proposal_cmd = TALER_TESTING_interpreter_lookup_command - (is, pls->proposal_reference); - - if (NULL == proposal_cmd) - TALER_TESTING_FAIL (is); - - if (GNUNET_OK != GET_TRAIT_NONCE (proposal_cmd, - &nonce)) - { - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, - &dummy_nonce, - sizeof (dummy_nonce)); - nonce = &dummy_nonce; - } - - if (GNUNET_OK != TALER_TESTING_get_trait_order_id - (proposal_cmd, 0, &order_id)) - TALER_TESTING_FAIL (is); - } - pls->plo = TALER_MERCHANT_proposal_lookup (is->ctx, - pls->merchant_url, - order_id, - &nonce->eddsa_pub, - &proposal_lookup_cb, - pls); - GNUNET_assert (NULL != pls->plo); -} - - -/** - * Offer internal data to other commands. - * - * @param cls closure - * @param ret[out] result (could be anything) - * @param trait name of the trait - * @param index index number of the object to extract. - * @return #GNUNET_OK on success - */ -static int -proposal_lookup_traits (void *cls, - const void **ret, - const char *trait, - unsigned int index) -{ - struct ProposalLookupState *pls = cls; - struct TALER_TESTING_Trait traits[] = { - TALER_TESTING_make_trait_contract_terms (0, - pls->contract_terms), - TALER_TESTING_make_trait_h_contract_terms (0, - &pls->contract_terms_hash), - TALER_TESTING_make_trait_merchant_sig (0, - &pls->merchant_sig), - TALER_TESTING_make_trait_merchant_pub (0, - &pls->merchant_pub), - TALER_TESTING_trait_end () - }; - - return TALER_TESTING_get_trait (traits, - ret, - trait, - index); -} - - -/** - * Make a "proposal lookup" command. - * - * @param label command label. - * @param merchant_url base URL of the merchant backend - * serving the proposal lookup request. - * @param http_status expected HTTP response code. - * @param proposal_reference reference to a "proposal" CMD. - * @param order_id order id to lookup, can be NULL. - * - * @return the command. - */ -struct TALER_TESTING_Command -TALER_TESTING_cmd_merchant_post_orders_lookup - (const char *label, - const char *merchant_url, - unsigned int http_status, - const char *proposal_reference, - const char *order_id) -{ - struct ProposalLookupState *pls; - - pls = GNUNET_new (struct ProposalLookupState); - pls->http_status = http_status; - pls->proposal_reference = proposal_reference; - pls->merchant_url = merchant_url; - pls->order_id = order_id; - { - struct TALER_TESTING_Command cmd = { - .cls = pls, - .label = label, - .run = &proposal_lookup_run, - .cleanup = &proposal_lookup_cleanup, - .traits = &proposal_lookup_traits - }; - - return cmd; - } -} -- cgit v1.2.3