merchant

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

commit c0228ee670fc971658abda363fc2c2090fa94546
parent fd8e8a5f730cdd8b13a14d28a3d8341e989986f6
Author: Bohdan Potuzhnyi <potub1@bfh.ch>
Date:   Mon, 14 Oct 2024 10:28:07 +0000

adding test for donau

Diffstat:
Msrc/include/taler_merchant_donau.h | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/taler_merchant_testing_lib.h | 19+++++++++++++++++++
Msrc/lib/Makefile.am | 8++++++++
Asrc/lib/merchant_api_get_donau_instance.c | 281+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/testing/Makefile.am | 1+
Msrc/testing/test_merchant_api.c | 19+++++++++++++++++++
Msrc/testing/test_merchant_api.conf | 38++++++++++++++++++++++++++++++++++++++
Asrc/testing/testing_api_cmd_get_donau_instances.c | 209+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 709 insertions(+), 0 deletions(-)

diff --git a/src/include/taler_merchant_donau.h b/src/include/taler_merchant_donau.h @@ -25,6 +25,8 @@ #include <taler/taler_util.h> #include <taler/taler_error_codes.h> #include <taler/taler_exchange_service.h> +#include "taler_merchant_service.h" +#include <donau/donau_service.h> #include <donau/donau_util.h> #include <donau/donaudb_plugin.h> @@ -145,4 +147,136 @@ typedef void const json_t *donau_keys_json ); +/** + * SPECIFICATION FOR REQUESTS /donau + */ + +/** + * Handle for a GET /donau operation. + */ +struct TALER_MERCHANT_DonauInstanceGetHandle; + +/** + * Handle for a GET /donau operation. + */ +struct TALER_MERCHANT_DonauInstanceGetHandle; + +/** + * Individual Donau instance details. + */ +struct TALER_MERCHANT_DonauInstanceEntry +{ + /** + * The URL of the Donau service. + */ + const char *donau_url; + + /** + * Name of the charity associated with the Donau instance. + */ + const char *charity_name; + + /** + * Public key of the charity. + */ + struct DONAU_CharityPublicKeyP charity_pub_key; + + /** + * ID of the charity on Donau. + */ + uint64_t charity_id; + + /** + * Maximum donation amount per year allowed for the charity. + */ + struct TALER_Amount charity_max_per_year; + + /** + * Total donations received by the charity in the current year. + */ + struct TALER_Amount charity_receipts_to_date; + + /** + * The current year being tracked for donations. + */ + int64_t current_year; + + /** + * Additional key information related to the Donau instance. + */ + struct DONAU_Keys donau_keys; +}; + +/** + * Response for a GET /donau request. + */ +struct TALER_MERCHANT_DonauInstanceGetResponse +{ + /** + * HTTP response details. + */ + struct TALER_MERCHANT_HttpResponse hr; + + /** + * Details depending on the HTTP status. + */ + union + { + /** + * Details for #MHD_HTTP_OK. + */ + struct + { + /** + * Length of the @e donau_instances array. + */ + unsigned int donau_instances_length; + + /** + * Details about the Donau instance. + */ + struct TALER_MERCHANT_DonauInstanceEntry *donau_instances; + } ok; + } details; +}; + +/** + * Function called with the result of the GET /donau operation. + * + * @param cls closure + * @param dir response details + */ +typedef void +(*TALER_MERCHANT_DonauInstanceGetCallback)( + void *cls, + const struct TALER_MERCHANT_DonauInstanceGetResponse *dir); + +/** + * Get the details on the Donau instance. This will connect to the + * Donau service backend and retrieve information about the instance. + * The respective information will be passed to the @a cb once available. + * + * @param ctx the context + * @param cb function to call with the backend's instance information + * @param cb_cls closure for @a cb + * @return the instance handle; NULL upon error + */ +struct TALER_MERCHANT_DonauInstanceGetHandle * +TALER_MERCHANT_donau_instances_get( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + TALER_MERCHANT_DonauInstanceGetCallback cb, + void *cb_cls); + + +/** + * Cancel the GET /donau instances operation. Must not be called by clients + * after the callback was invoked. + * + * @param dgh request to cancel. + */ +void +TALER_MERCHANT_donau_instances_get_cancel( + struct TALER_MERCHANT_DonauInstanceGetHandle *dgh); + #endif \ No newline at end of file diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h @@ -1790,6 +1790,25 @@ TALER_TESTING_cmd_checkserver2 (const char *label, const char *expected_header, const char *expected_body); +/** + * Define a "GET /donau" CMD. + * + * @param label command label. + * @param url base URL of the Donau service serving the + * GET /donau_instances request. + * @param expected_http_status expected HTTP response code. + * @param ... NULL-terminated list of labels (const char *) of + * donau instances (commands) we expect to be returned in the list + * (assuming @a expected_http_status is #MHD_HTTP_OK) + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_merchant_get_donau_instances(const char *label, + const char *url, + unsigned int expected_http_status, + ...); + + /* ****** Specific traits supported by this component ******* */ diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am @@ -82,6 +82,14 @@ libtalermerchant_la_LIBADD = \ -lcurl \ $(XLIB) +if HAVE_DONAU + libtalermerchant_la_SOURCES += \ + merchant_api_get_donau_instance.c + + libtalermerchant_la_LIBADD += \ + -ldonau +endif + check_PROGRAMS = \ test_merchant_api_common diff --git a/src/lib/merchant_api_get_donau_instance.c b/src/lib/merchant_api_get_donau_instance.c @@ -0,0 +1,281 @@ +/* + This file is part of TALER + Copyright (C) 2024 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 <http://www.gnu.org/licenses/> + */ +/** + * @file merchant_api_get_donau_instance.c + * @brief Implementation of the GET /donau request of the merchant's HTTP API + * @author Bohdan Potuzhnyi + * @author Vlada Svirsh + */ + +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> +/* DONAU RELATED IMPORTS */ +#include "taler_merchant_donau.h" +#include <donau/donau_service.h> + +/** + * Handle for a GET /donau operation. + */ +struct TALER_MERCHANT_DonauInstanceGetHandle +{ + /** + * The URL for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_DonauInstanceGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + +/** + * Parse Donau instance information from @a ia. + * + * @param ia JSON array (or NULL!) with Donau instance data + * @param igr response to fill + * @param dgh operation handle + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_donau_instances (const json_t *ia, + struct TALER_MERCHANT_DonauInstanceGetResponse *igr, + struct TALER_MERCHANT_DonauInstanceGetHandle *dgh) +{ + unsigned int instances_len = (unsigned int) json_array_size(ia); + + if ((json_array_size(ia) != (size_t)instances_len)) + { + GNUNET_break(0); + return GNUNET_SYSERR; + } + + struct TALER_MERCHANT_DonauInstanceEntry instances[GNUNET_NZL (instances_len)]; + size_t index; + json_t *value; + + json_array_foreach(ia, index, value) + { + struct TALER_MERCHANT_DonauInstanceEntry *instance = &instances[index]; + + /* Specification for parsing each instance */ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string("donau_url", + &instance->donau_url), + GNUNET_JSON_spec_string("charity_name", + &instance->charity_name), + GNUNET_JSON_spec_fixed_auto("charity_pub_key", + &instance->charity_pub_key), + GNUNET_JSON_spec_uint64("charity_id", + &instance->charity_id), + TALER_JSON_spec_amount_any("charity_max_per_year", + &instance->charity_max_per_year), + TALER_JSON_spec_amount_any("charity_receipts_to_date", + &instance->charity_receipts_to_date), + GNUNET_JSON_spec_int64("current_year", + &instance->current_year), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != GNUNET_JSON_parse(value, spec, NULL, NULL)) + { + GNUNET_break_op(0); + return GNUNET_SYSERR; + } + + /* Parse the Donau keys */ + const json_t *donau_keys_json = json_object_get(value, "donau_keys"); + if (NULL == donau_keys_json) + { + GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Failed to parse donau keys JSON\n"); + return GNUNET_SYSERR; + } + + /* Convert donau_keys_json to donau_keys struct */ + struct DONAU_Keys *donau_keys_ptr = DONAU_keys_from_json(donau_keys_json); + if (NULL == donau_keys_ptr) + { + GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Failed to convert donau keys from JSON\n"); + return GNUNET_SYSERR; + } + + /* Assign donau_keys to the instance details */ + instance->donau_keys = *donau_keys_ptr; + } + + igr->details.ok.donau_instances_length = instances_len; + igr->details.ok.donau_instances = instances; + dgh->cb(dgh->cb_cls, igr); + dgh->cb = NULL; /* just to be sure */ + + return GNUNET_OK; +} + +/** + * Function called when we're done processing the + * HTTP /donau request. + * + * @param cls the `struct TALER_MERCHANT_DonauInstanceGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_get_donau_instances_finished(void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_DonauInstanceGetHandle *dgh = cls; + const json_t *json = response; + struct TALER_MERCHANT_DonauInstanceGetResponse igr = { + .hr.http_status = (unsigned int)response_code, + .hr.reply = json + }; + + dgh->job = NULL; + GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, + "Got /donau response with status code %u\n", + (unsigned int)response_code); + + switch (response_code) + { + case MHD_HTTP_OK: + { + const json_t *donau_instances; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const("donau_instances", &donau_instances), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != GNUNET_JSON_parse(json, spec, NULL, NULL)) + { + igr.hr.http_status = 0; + igr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + + if (GNUNET_OK == parse_donau_instances(donau_instances, &igr, dgh)) + { + TALER_MERCHANT_donau_instances_get_cancel(dgh); + return; + } + + igr.hr.http_status = 0; + igr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + + case MHD_HTTP_UNAUTHORIZED: + igr.hr.ec = TALER_JSON_get_error_code(json); + igr.hr.hint = TALER_JSON_get_error_hint(json); + break; + + case MHD_HTTP_NOT_FOUND: + igr.hr.ec = TALER_JSON_get_error_code(json); + igr.hr.hint = TALER_JSON_get_error_hint(json); + break; + + default: + igr.hr.ec = TALER_JSON_get_error_code(json); + igr.hr.hint = TALER_JSON_get_error_hint(json); + GNUNET_log(GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int)response_code, + (int)igr.hr.ec); + break; + } + + dgh->cb(dgh->cb_cls, &igr); + TALER_MERCHANT_donau_instances_get_cancel(dgh); +} + +/** + * Initiate the GET /donau request. + * + * @param ctx CURL context + * @param backend_url base URL for the backend + * @param cb callback function to handle the response + * @param cb_cls closure for the callback function + * @return the handle for the operation, or NULL on error + */ +struct TALER_MERCHANT_DonauInstanceGetHandle * +TALER_MERCHANT_donau_instances_get (struct GNUNET_CURL_Context *ctx, + const char *backend_url, + TALER_MERCHANT_DonauInstanceGetCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_DonauInstanceGetHandle *dgh; + CURL *eh; + + dgh = GNUNET_new(struct TALER_MERCHANT_DonauInstanceGetHandle); + dgh->ctx = ctx; + dgh->cb = cb; + dgh->cb_cls = cb_cls; + + dgh->url = TALER_url_join(backend_url, "private/donau", NULL); + if (NULL == dgh->url) + { + GNUNET_log(GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free(dgh); + return NULL; + } + + GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", dgh->url); + + eh = TALER_MERCHANT_curl_easy_get_(dgh->url); + dgh->job = GNUNET_CURL_job_add(ctx, eh, &handle_get_donau_instances_finished, dgh); + + return dgh; +} + +/** + * Cancel the GET /donau operation. + * + * @param dgh the handle for the operation to be canceled + */ +void +TALER_MERCHANT_donau_instances_get_cancel (struct TALER_MERCHANT_DonauInstanceGetHandle *dgh) +{ + if (NULL != dgh->job) + GNUNET_CURL_job_cancel(dgh->job); + + GNUNET_free(dgh->url); + GNUNET_free(dgh); +} diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am @@ -32,6 +32,7 @@ libtalermerchanttesting_la_SOURCES = \ testing_api_cmd_abort_order.c \ testing_api_cmd_claim_order.c \ testing_api_cmd_depositcheck.c \ + testing_api_cmd_get_donau_instances.c \ testing_api_cmd_get_instance.c \ testing_api_cmd_get_instances.c \ testing_api_cmd_get_orders.c \ diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c @@ -1817,6 +1817,13 @@ run (void *cls, TALER_TESTING_cmd_end () }; + struct TALER_TESTING_Command donau[] = { + TALER_TESTING_cmd_merchant_get_donau_instances( + "get-donau-instance", + "http://localhost:8080/donau", + MHD_HTTP_OK), + }; + struct TALER_TESTING_Command commands[] = { /* general setup */ TALER_TESTING_cmd_run_fakebank ( @@ -2141,6 +2148,18 @@ run (void *cls, repurchase), TALER_TESTING_cmd_batch ("tokens", tokens), + #ifdef HAVE_DONAU_DONAU_SERVICE_H + TALER_TESTING_cmd_system_start ( + "start-donau-service", + config_file, + "-D", + "-u", "exchange-account-exchange", + NULL), + + TALER_TESTING_cmd_batch ("donau", + donau), + + #endif /** * End the suite. */ diff --git a/src/testing/test_merchant_api.conf b/src/testing/test_merchant_api.conf @@ -2,6 +2,7 @@ # [PATHS] TALER_TEST_HOME = test_merchant_api_home/ +DONAU_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/donau-system-runtime/ [taler] CURRENCY = EUR @@ -71,3 +72,39 @@ WIRE_GATEWAY_AUTH_METHOD = NONE [admin-accountcredentials-exchange] WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/" WIRE_GATEWAY_AUTH_METHOD = NONE + +[donau] +TERMS_ETAG = tos +PRIVACY_ETAG = 0 +PORT = 8079 +DB = postgres +DOMAIN = "Bern" +BASE_URL = "http://localhost:8079/" +SERVE = tcp +EXPIRE_IDLE_SLEEP_INTERVAL ="1 s" +EXPIRE_LEGAL = 5 + +[donaudb-postgres] +CONFIG = "postgres:///talercheck" + +[donau-secmod-cs] +LOOKAHEAD_SIGN = "24 days" +KEY_DIR = ${TALER_TEST_HOME}taler/exchange-secmod-cs/keys +OVERLAP_DURATION = 0 +SM_PRIV_KEY = ${DONAU_RUNTIME_DIR}donau-secmod-cs/secmod-private-key +UNIXPATH = ${DONAU_RUNTIME_DIR}donau-secmod-cs/server.sock + +[donau-secmod-rsa] +LOOKAHEAD_SIGN = "24 days" +KEY_DIR = ${TALER_TEST_HOME}taler/exchange-secmod-rsa/keys +OVERLAP_DURATION = 0 +SM_PRIV_KEY = ${DONAU_RUNTIME_DIR}donau-secmod-rsa/secmod-private-key +UNIXPATH = ${DONAU_RUNTIME_DIR}donau-secmod-rsa/server.sock + +[donau-secmod-eddsa] +LOOKAHEAD_SIGN = "24 days" +DURATION = "14 days" +KEY_DIR = ${TALER_TEST_HOME}taler/exchange-secmod-eddsa/keys +OVERLAP_DURATION = 0 +SM_PRIV_KEY = ${DONAU_RUNTIME_DIR}donau-secmod-eddsa/secmod-private-key +UNIXPATH = ${DONAU_RUNTIME_DIR}donau-secmod-eddsa/server.sock +\ No newline at end of file diff --git a/src/testing/testing_api_cmd_get_donau_instances.c b/src/testing/testing_api_cmd_get_donau_instances.c @@ -0,0 +1,209 @@ +/* + This file is part of TALER + Copyright (C) 2021 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file testing_api_cmd_kyc_get.c + * @brief command to test kyc_get request + * @author Bohdan Potuzhnyi + * @author Vlada Svirsh + */ +#include "platform.h" +#include <taler/taler_exchange_service.h> +#include <taler/taler_testing_lib.h> +#include "taler_merchant_service.h" +#include "taler_merchant_testing_lib.h" +#include "taler_merchant_donau.h" + +/** + * State of a "GET donau instances" CMD. + */ +struct GetDonauInstancesState +{ + /** + * Handle for a "GET donau instances" request. + */ + struct TALER_MERCHANT_DonauInstanceGetHandle *dgh; + + /** + * The interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Base URL of the merchant/donau service. + */ + const char *url; + + /** + * Expected HTTP response code. + */ + unsigned int expected_http_status; + + /** + * A NULL-terminated array of Donau instance names. + */ + const char **instances; + + /** + * The number of instances in the `instances` array. + */ + unsigned int instances_length; +}; + +/** + * Callback for a /get/donau operation. + * + * @param cls closure for this function + * @param response response details from the Donau service + */ +static void +get_donau_instances_cb (void *cls, + const struct TALER_MERCHANT_DonauInstanceGetResponse *response) +{ + struct GetDonauInstancesState *gis = cls; + + gis->dgh = NULL; + + if (gis->expected_http_status != response->hr.http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u (%d) to command %s\n", + response->hr.http_status, + (int) response->hr.ec, + TALER_TESTING_interpreter_get_current_label (gis->is)); + TALER_TESTING_interpreter_fail (gis->is); + return; + } + + // Process successful response if the status is 200 OK + if (MHD_HTTP_OK == response->hr.http_status) + { + unsigned int instance_count = response->details.ok.donau_instances_length; + if (instance_count != gis->instances_length) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Expected %u instances, but got %u\n", + gis->instances_length, instance_count); + TALER_TESTING_interpreter_fail(gis->is); + return; + } + + for (unsigned int i = 0; i < instance_count; ++i) + { + const struct TALER_MERCHANT_DonauInstanceEntry *instance = &response->details.ok.donau_instances[i]; + + if (0 != strcmp(instance->charity_name, gis->instances[i])) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Instance charity name mismatch: expected %s, got %s\n", + gis->instances[i], instance->charity_name); + TALER_TESTING_interpreter_fail(gis->is); + return; + } + } + } + + // Continue to the next test command + TALER_TESTING_interpreter_next (gis->is); +} + +/** + * Run the "GET donau instance" CMD. + * + * @param cls closure. + * @param cmd command being run now. + * @param is interpreter state. + */ +static void +get_donau_instances_run(void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct GetDonauInstancesState *gis = cls; + + gis->is = is; + gis->dgh = TALER_MERCHANT_donau_instances_get( + TALER_TESTING_interpreter_get_context(is), + gis->url, + &get_donau_instances_cb, + gis); + + GNUNET_assert(NULL != gis->dgh); // Ensure the request handle is valid +} + +/** + * Free the state of a "GET donau instance" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd command being run. + */ +static void +get_donau_instances_cleanup(void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct GetDonauInstancesState *gis = cls; + + if (NULL != gis->dgh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "GET /donau instances operation did not complete\n"); + TALER_MERCHANT_donau_instances_get_cancel(gis->dgh); + } + + GNUNET_free(gis); // Free the state +} + +/** + * Create a new command to GET Donau instances. + * + * @param label label for the command + * @param url the base URL of the Donau service + * @param expected_http_status expected HTTP status code + * @return a new testing command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_merchant_get_donau_instances(const char *label, + const char *url, + unsigned int expected_http_status, + ...) +{ + struct GetDonauInstancesState *gis; + + gis = GNUNET_new(struct GetDonauInstancesState); + gis->url = url; + gis->expected_http_status = expected_http_status; + gis->instances_length = 0; + + const char *instance_label; + va_list ap; + va_start(ap, expected_http_status); + while (NULL != (instance_label = va_arg(ap, const char *))) + { + GNUNET_array_append(gis->instances, gis->instances_length, instance_label); + } + va_end(ap); + + struct TALER_TESTING_Command cmd = { + .cls = gis, + .label = label, + .run = &get_donau_instances_run, + .cleanup = &get_donau_instances_cleanup + }; + + return cmd; +} +