merchant

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

commit 0dc132ded682646339917a6a39455cbdf7e9630c
parent afb5d95964bfb6a662c3b1c6a6098cfdb2104dc7
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Fri, 11 Jul 2025 09:58:25 +0200

some progress for the donau pay test

Diffstat:
Msrc/backend/taler-merchant-httpd_post-orders-ID-pay.c | 15+++++++++++++--
Msrc/backenddb/pg_lookup_order_charity.c | 7++-----
Msrc/include/taler_merchant_testing_lib.h | 33+++++++++++++++++++++++++++++++++
Msrc/lib/taler_merchant_pay_service.c | 89+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/testing/Makefile.am | 1+
Msrc/testing/test_merchant_api.c | 6++++--
Asrc/testing/testing_api_cmd_post_donau_charity_merchant.c | 229+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/testing/testing_api_cmd_post_donau_instances.c | 25+++++++++++++++++++++++++
8 files changed, 360 insertions(+), 45 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -2682,11 +2682,20 @@ phase_execute_pay_transaction (struct PayContext *pc) /* Store signed output tokens in database. */ for (size_t i = 0; i<pc->validate_tokens.output_tokens_len; i++) { + const struct TALER_MERCHANT_ContractChoice *choice = + &pc->check_contract.contract_terms->details.v1 + .choices[pc->parse_wallet_data.choice_index]; + + /* If the matching contract-output is a donation-receipt, we skip it. */ + if (i < choice->outputs_len && + TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT == + choice->outputs[i].type) + continue; + struct SignedOutputToken *output = &pc->validate_tokens.output_tokens[i]; enum GNUNET_DB_QueryStatus qs; - //FIXME: Fails for donau output... qs = TMH_db->insert_issued_token (TMH_db->cls, &pc->check_contract.h_contract_terms, &output->h_issue, @@ -2735,6 +2744,8 @@ phase_execute_pay_transaction (struct PayContext *pc) } } + pc->charity.charity_priv = GNUNET_new (struct DONAU_CharityPrivateKeyP); + /* Part where we fetch info about the charity*/ qs = TMH_db->lookup_order_charity( TMH_db->cls, @@ -2742,7 +2753,7 @@ phase_execute_pay_transaction (struct PayContext *pc) &pc->charity.charity_id, pc->charity.charity_priv); - if (0 >= qs) + if (0 > qs) { TMH_db->rollback (TMH_db->cls); diff --git a/src/backenddb/pg_lookup_order_charity.c b/src/backenddb/pg_lookup_order_charity.c @@ -50,17 +50,14 @@ TMH_PG_lookup_order_charity ( { struct PostgresClosure *pg = cls; - uint64_t cid; - struct DONAU_CharityPrivateKeyP cpriv; - struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (donau_url), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("charity_id", &cid), - GNUNET_PQ_result_spec_auto_from_type ("merchant_priv", &cpriv), + GNUNET_PQ_result_spec_uint64 ("charity_id", charity_id), + GNUNET_PQ_result_spec_auto_from_type ("merchant_priv", charity_priv), GNUNET_PQ_result_spec_end }; diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h @@ -30,6 +30,9 @@ #include <gnunet/gnunet_time_lib.h> #include <taler/taler_testing_lib.h> #include "taler_merchant_service.h" +#ifdef HAVE_DONAU_DONAU_SERVICE_H +#include "donau/donau_service.h" +#endif // HAVE_DONAU_DONAU_SERVICE_H /* ********************* Helper functions ********************* */ @@ -1904,6 +1907,35 @@ TALER_TESTING_cmd_checkserver2 (const char *label, const char *expected_header, const char *expected_body); + +#ifdef HAVE_DONAU_DONAU_SERVICE_H +/** + * This function is the similar to the TALER_TESSTING_cmd_charity_post from DONAU + * with only difference that it re-uses the merchant public key, from the merchant test-suite. + * + * @param label command label + * @param name name of the charity + * @param url url of the charity + * @param max_per_year maximum amount of donations per year + * @param receipts_to_date amount of receipts to date + * @param current_year year + * @param bearer + * @param merchant_reference reference to fetch the merchant public key + * @param expected_response_code expected HTTP response code + * @return + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_charity_post_merchant (const char *label, + const char *name, + const char *url, + const char *max_per_year, + const char *receipts_to_date, + uint64_t current_year, + const struct DONAU_BearerToken *bearer, + const char *merchant_reference, + unsigned int expected_response_code); +#endif /* HAVE_DONAU_DONAU_SERVICE_H */ + /** * Define a "GET /donau" CMD. * @@ -1939,6 +1971,7 @@ TALER_TESTING_cmd_merchant_get_donau_instances(const char *label, struct TALER_TESTING_Command TALER_TESTING_cmd_merchant_post_donau_instance(const char *label, const char *url, + const char *merchant_reference, unsigned int expected_http_status, ...); diff --git a/src/lib/taler_merchant_pay_service.c b/src/lib/taler_merchant_pay_service.c @@ -113,6 +113,18 @@ struct TALER_MERCHANT_OrderPayHandle bool field_seen[TALER_MERCHANT_OrderPayOptionType_LENGTH]; bool am_wallet; + + /* --- Donau (optional) ------------------------------------------------- */ + #ifdef HAVE_DONAU_DONAU_SERVICE_H + bool has_donau_url; + char *donau_url; + + bool has_donau_year; + uint64_t donau_year; + + bool has_donau_budis; + json_t *donau_budikeypairs; /* array we’ll own */ + #endif }; /** @@ -446,6 +458,12 @@ TALER_MERCHANT_order_pay_cancel1 (struct TALER_MERCHANT_OrderPayHandle *ph) json_decref (ph->tokens_evs); ph->tokens_evs = NULL; + #ifdef HAVE_DONAU_DONAU_SERVICE_H + GNUNET_free (ph->donau_url); + if (ph->donau_budikeypairs) + json_decref (ph->donau_budikeypairs); + #endif + GNUNET_free (ph->url); GNUNET_free (ph->merchant_url); @@ -512,13 +530,7 @@ TALER_MERCHANT_order_pay_set_options (struct TALER_MERCHANT_OrderPayHandle *ph, case TALER_MERCHANT_OrderPayOptionType_CHOICE_INDEX: ph->choice_index = o->details.choice_index; ph->has_choice_index = true; - { - json_t *js = GNUNET_JSON_PACK(GNUNET_JSON_pack_int64("choice_index", o->details.choice_index)); - enum TALER_MERCHANT_OrderPayOptionErrorCode ec = store_json_option(ph, o->ot, js); - if (TALER_MERCHANT_OPOEC_OK != ec) - return ec; - break; - } + break; case TALER_MERCHANT_OrderPayOptionType_AMOUNT: { @@ -605,40 +617,23 @@ TALER_MERCHANT_order_pay_set_options (struct TALER_MERCHANT_OrderPayHandle *ph, /* ---------- Donau ------------ */ case TALER_MERCHANT_OrderPayOptionType_DONAU_URL: { - json_t *js = GNUNET_JSON_PACK( - GNUNET_JSON_pack_string("donau_url", - o->details.donau_url) - ); - enum TALER_MERCHANT_OrderPayOptionErrorCode ec = - store_json_option (ph, o->ot, js); - if (ec != TALER_MERCHANT_OPOEC_OK) - return ec; + ph->has_donau_url = true; + ph->donau_url = GNUNET_strdup (o->details.donau_url); break; } case TALER_MERCHANT_OrderPayOptionType_DONAU_YEAR: { - json_t *js = GNUNET_JSON_PACK( - GNUNET_JSON_pack_uint64("donau_year", - o->details.donau_year) - ); - enum TALER_MERCHANT_OrderPayOptionErrorCode ec = - store_json_option(ph, o->ot, js); - if (ec != TALER_MERCHANT_OPOEC_OK) - return ec; + ph->has_donau_year = true; + ph->donau_year = o->details.donau_year; break; } case TALER_MERCHANT_OrderPayOptionType_DONAU_BUDIS: { - json_t *js = GNUNET_JSON_PACK( - GNUNET_JSON_pack_array_incref("donau_budikeypairs", - o->details.donau_budis_json) - ); - enum TALER_MERCHANT_OrderPayOptionErrorCode ec = - store_json_option(ph, o->ot, js); - if (ec != TALER_MERCHANT_OPOEC_OK) - return ec; + ph->has_donau_budis = true; + ph->donau_budikeypairs = o->details.donau_budis_json; + json_incref (ph->donau_budikeypairs); break; } #endif /* HAVE_DONAU_DONAU_SERVICE_H */ @@ -668,11 +663,33 @@ TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph) /* --- build wallet_data hash for signing coins & tokens --- */ if (ph->has_choice_index) { - ph->wallet_data = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_int64("choice_index", ph->choice_index), - GNUNET_JSON_pack_allow_null( - GNUNET_JSON_pack_array_incref("tokens_evs", ph->tokens_evs)) - ); + /* base fields */ + json_t *wd = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_int64 ("choice_index", ph->choice_index), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_incref ("tokens_evs", ph->tokens_evs)) + ); + + #ifdef HAVE_DONAU_DONAU_SERVICE_H + /* Donau extras (optional) */ + if (ph->has_donau_url) + GNUNET_assert (0 == json_object_set_new (wd, + "donau_url", + json_string (ph->donau_url))); + + if (ph->has_donau_year) + GNUNET_assert (0 == json_object_set_new (wd, + "donau_year", + json_integer ((json_int_t) ph->donau_year))); + + if (ph->has_donau_budis) + GNUNET_assert (0 == json_object_set_new (wd, + "donau_budikeypairs", + json_incref (ph->donau_budikeypairs))); + #endif + + ph->wallet_data = wd; + TALER_json_hash (ph->wallet_data, &ph->wallet_data_hash); diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am @@ -89,6 +89,7 @@ libtalermerchanttesting_la_SOURCES = \ if HAVE_DONAU libtalermerchanttesting_la_SOURCES += \ + testing_api_cmd_post_donau_charity_merchant.c \ testing_api_cmd_post_donau_instances.c \ testing_api_cmd_get_donau_instances.c \ testing_api_cmd_delete_donau_instances.c diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c @@ -1860,17 +1860,19 @@ run (void *cls, TALER_TESTING_cmd_get_donau ("get-donau", cred.cfg, true)), - TALER_TESTING_cmd_charity_post ("post-charity", + TALER_TESTING_cmd_charity_post_merchant ("post-charity", "example", "example.com", "EUR:50", // max_per_year "EUR:0", // receipts_to_date 2025, // current year &bearer, + "create-another-order-with-input-and-output", //reusing the merhcant_reference for merchant_pub MHD_HTTP_CREATED), TALER_TESTING_cmd_merchant_post_donau_instance ( "post-donau-instance", merchant_url, + "create-another-order-with-input-and-output", //reusing the merhcant_reference MHD_HTTP_NO_CONTENT), TALER_TESTING_cmd_sleep ( "In this time donaukeyupdate must fetch the keys from the donau", 1), @@ -2254,7 +2256,7 @@ run (void *cls, tokens), #ifdef HAVE_DONAU_DONAU_SERVICE_H - TALER_TESTING_cmd_sleep("dream", 30), + //TALER_TESTING_cmd_sleep("dream", 30), TALER_TESTING_cmd_batch ("donau", donau), #endif diff --git a/src/testing/testing_api_cmd_post_donau_charity_merchant.c b/src/testing/testing_api_cmd_post_donau_charity_merchant.c @@ -0,0 +1,229 @@ +/* + This file is part of TALER + Copyright (C) 2020-2024 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_post_donau_charity_merchant.c + * @brief command to test POST /donau charity with merchant_pub + * @author Bohdan Potuzhnyi + * @author Vlada Svirsh + */ +#include "platform.h" +#include <taler/taler_exchange_service.h> +#include <taler/taler_testing_lib.h> +#include <taler/taler_signatures.h> +#include "taler_merchant_service.h" +#include "taler_merchant_testing_lib.h" +#include "taler_merchant_donau.h" +#include <donau/donau_service.h> +#include <donau/donau_testing_lib.h> + + +/** + * State for a "status" CMD. + */ +struct StatusState +{ + /** + * Handle to the "charity status" operation. + */ + struct DONAU_CharityPostHandle *cph; + + /** + * The charity POST request. + */ + struct DONAU_CharityRequest charity_req; + + /** + * The bearer token for authorization. + */ + const struct DONAU_BearerToken *bearer; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * charity id + */ + unsigned long long charity_id; + + /** + * Merchant reference to fetch public key. + */ + const char *merchant_reference; +}; + + +/** + * Check that the reserve balance and HTTP response code are + * both acceptable. + * + * @param cls closure. + * @param gcr HTTP response details + */ +static void +charity_status_cb (void *cls, + const struct DONAU_PostCharityResponse *gcr) +{ + struct StatusState *ss = cls; + + ss->cph = NULL; + if (ss->expected_response_code != gcr->hr.http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected HTTP response code: %d in %s:%u\n", + gcr->hr.http_status, + __FILE__, + __LINE__); + json_dumpf (gcr->hr.reply, + stderr, + 0); + TALER_TESTING_interpreter_fail (ss->is); + return; + } + if (ss->expected_response_code == gcr->hr.http_status) + ss->charity_id = (unsigned long long) gcr->details.ok.charity_id; + TALER_TESTING_interpreter_next (ss->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command being executed. + * @param is the interpreter state. + */ +static void +charity_status_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct StatusState *ss = cls; + + ss->is = is; + + if (NULL != ss->merchant_reference) + { + const struct TALER_TESTING_Command *mc; + const struct TALER_MerchantPublicKeyP *mpub; + + mc = TALER_TESTING_interpreter_lookup_command (is, + ss->merchant_reference); + GNUNET_assert (NULL != mc); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_merchant_pub (mc, + &mpub)); + + ss->charity_req.charity_pub.eddsa_pub = mpub->eddsa_pub; + } + + ss->cph = DONAU_charity_post ( + TALER_TESTING_interpreter_get_context (is), + TALER_TESTING_get_donau_url (is), + &ss->charity_req, + ss->bearer, + &charity_status_cb, + ss); +} + + +/** + * Cleanup the state from a "reserve status" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct StatusState *ss = cls; + + if (NULL != ss->cph) + { + // log incomplete command + TALER_TESTING_command_incomplete (ss->is, + cmd->label); + DONAU_charity_post_cancel (ss->cph); + ss->cph = NULL; + } + GNUNET_free (ss); +} + +struct TALER_TESTING_Command +TALER_TESTING_cmd_charity_post_merchant (const char *label, + const char *name, + const char *url, + const char *max_per_year, + const char *receipts_to_date, + uint64_t current_year, + const struct DONAU_BearerToken *bearer, + const char *merchant_reference, + unsigned int expected_response_code) +{ + struct StatusState *ss; + + ss = GNUNET_new (struct StatusState); + + ss->merchant_reference = merchant_reference; + ss->charity_req.name = name; + ss->charity_req.charity_url = url; + // parse string max_per_year to amount + if (GNUNET_OK != + TALER_string_to_amount (max_per_year, + &ss->charity_req.max_per_year)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %s\n", + max_per_year, + label); + GNUNET_assert (0); + } + // parse string receipts_to_date to amount + if (GNUNET_OK != + TALER_string_to_amount (receipts_to_date, + &ss->charity_req.receipts_to_date)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %s\n", + receipts_to_date, + label); + GNUNET_assert (0); + } + ss->charity_req.current_year = current_year; + ss->expected_response_code = expected_response_code; + ss->bearer = bearer; + { + struct TALER_TESTING_Command cmd = { + .cls = ss, + .label = label, + .run = &charity_status_run, + .cleanup = &cleanup + }; + + return cmd; + } +} diff --git a/src/testing/testing_api_cmd_post_donau_instances.c b/src/testing/testing_api_cmd_post_donau_instances.c @@ -57,6 +57,11 @@ struct PostDonauState struct TALER_MERCHANT_DONAU_Charity charity; /** + * Merchant reference to fetch public key. + */ + const char *merchant_reference; + + /** * Authentication token for the request. */ const char *auth_token; @@ -135,6 +140,23 @@ post_donau_run (void *cls, //Replacing the url of the charity pds->charity.charity_url = TALER_TESTING_get_donau_url(is); + //Fetching the publick key of the merchant + if (NULL != pds->merchant_reference) + { + const struct TALER_TESTING_Command *mc; + const struct TALER_MerchantPublicKeyP *merchant_pub; + + mc = TALER_TESTING_interpreter_lookup_command (is, + pds->merchant_reference); + GNUNET_assert (NULL != mc); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_merchant_pub (mc, + &merchant_pub)); + + /* shallow copy of the actual EdDSA key */ + pds->charity.charity_pub.eddsa_pub = merchant_pub->eddsa_pub; + } + pds->dph = TALER_MERCHANT_donau_instances_post( TALER_TESTING_interpreter_get_context(is), pds->merchant_url, @@ -184,11 +206,13 @@ post_donau_cleanup (void *cls, struct TALER_TESTING_Command TALER_TESTING_cmd_merchant_post_donau_instance(const char *label, const char *merchant_url, + const char *merchant_reference, unsigned int http_status, ...) { struct PostDonauState *pds = GNUNET_new(struct PostDonauState); + //FIXME: I need to steal the charity_pub->eddsa_pub from the merchant_pub->eddsa_pub struct DONAU_CharityPublicKeyP *charity_pub = GNUNET_new(struct DONAU_CharityPublicKeyP); struct TALER_Amount max_amount; @@ -215,6 +239,7 @@ TALER_TESTING_cmd_merchant_post_donau_instance(const char *label, .current_year = 2025 }; + pds->merchant_reference = merchant_reference; pds->merchant_url = merchant_url; pds->charity = charity; pds->http_status = http_status;