merchant

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

commit 1ea6a4f5a191a721ca76ae42e9a0e9ec1b3ad807
parent 44a32b527ec1570bd1a6fd1b0b059dbef818e652
Author: Christian Grothoff <christian@grothoff.org>
Date:   Tue, 30 May 2023 23:09:34 +0200

add new commands for merchant instance account CRUD API

Diffstat:
Msrc/include/taler_merchant_testing_lib.h | 47++++++++++++++++++++++++++++++++++++++++++++---
Msrc/testing/Makefile.am | 2++
Asrc/testing/testing_api_cmd_delete_account.c | 242+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/testing/testing_api_cmd_get_instance.c | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/testing/testing_api_cmd_pay_order.c | 8++++----
Asrc/testing/testing_api_cmd_post_account.c | 220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 635 insertions(+), 9 deletions(-)

diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2018-2022 Taler Systems SA + (C) 2018-2023 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 @@ -147,7 +147,7 @@ TALER_TESTING_cmd_merchant_post_instance_auth (const char *label, * @param merchant_url base URL of the merchant serving the * POST /instances request. * @param instance_id the ID of the instance to query - * @param accounts_length length of the @a accounts array + * @param accounts_length length of the @a payto_uris array * @param payto_uris URIs of the bank accounts of the merchant instance * @param name name of the merchant instance * @param address physical address of the merchant instance @@ -181,6 +181,45 @@ TALER_TESTING_cmd_merchant_post_instances2 ( /** + * Define a "POST /account" CMD. + * + * @param label command label. + * @param merchant_url base URL of the merchant serving the + * POST /instances request. + * @param payto_uri URIs of the bank account to add to the merchant instance + * @param credit_facade_url credit facade URL to configure, can be NULL + * @param credit_facade_credentials credit facade credentials to use, can be NULL + * @param http_status expected HTTP response code. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_merchant_post_account ( + const char *label, + const char *merchant_url, + const char *payto_uri, + const char *credit_facade_url, + const json_t *credit_facade_credentials, + unsigned int http_status); + + +/** + * Define a "DELETE /account" CMD. + * + * @param label command label. + * @param get_instance_ref reference to a GET instance command + * @param payto_uri payto URI of the account to delete, must be in the response of the GET instance command + * @param http_status expected HTTP response code. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_merchant_delete_account ( + const char *label, + const char *get_instance_ref, + const char *payto_uri, + unsigned int http_status); + + +/** * Define a "PATCH /instances/$ID" CMD. * * @param label command label. @@ -1897,6 +1936,7 @@ TALER_TESTING_cmd_checkserver2 (const char *label, op (template_contract, const json_t) \ op (event_type, const char *) \ op (webhook_id, const char *) \ + op (merchant_base_url, const char *) \ op (url, const char *) \ op (http_method, const char *) \ op (header_template, const char *) \ @@ -1913,7 +1953,8 @@ TALER_TESTING_cmd_checkserver2 (const char *label, op (coin_reference, const char *) \ op (paths, const char *) \ op (payto_uris, const char *) \ - op (amounts, const struct TALER_Amount) \ + op (h_wires, const struct TALER_MerchantWireHashP) \ + op (amounts, const struct TALER_Amount) \ op (urls, const char *) \ op (http_methods, const char *) \ op (http_header, const char *) \ diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am @@ -42,6 +42,7 @@ libtalermerchanttesting_la_SOURCES = \ testing_api_cmd_get_template.c \ testing_api_cmd_get_webhooks.c \ testing_api_cmd_get_webhook.c \ + testing_api_cmd_delete_account.c \ testing_api_cmd_delete_instance.c \ testing_api_cmd_delete_order.c \ testing_api_cmd_delete_product.c \ @@ -56,6 +57,7 @@ libtalermerchanttesting_la_SOURCES = \ testing_api_cmd_merchant_get_order.c \ testing_api_cmd_merchant_get_tip.c \ testing_api_cmd_pay_order.c \ + testing_api_cmd_post_account.c \ testing_api_cmd_post_instances.c \ testing_api_cmd_post_orders_paid.c \ testing_api_cmd_post_orders.c \ diff --git a/src/testing/testing_api_cmd_delete_account.c b/src/testing/testing_api_cmd_delete_account.c @@ -0,0 +1,242 @@ +/* + This file is part of TALER + Copyright (C) 2023 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_delete_account.c + * @brief command to test DELETE /account/$H_WIRE + * @author Christian Grothoff + */ +#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" + + +/** + * State of a "DELETE /accounts/$ID" CMD. + */ +struct DeleteAccountState +{ + + /** + * Handle for a "DELETE account" request. + */ + struct TALER_MERCHANT_AccountDeleteHandle *adh; + + /** + * The interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Base URL of the merchant serving the request. + */ + const char *merchant_url; + + /** + * ID of the command to get account details from. + */ + const char *get_instance_ref; + + /** + * Payto URI to extract h_wire from. + */ + const char *payto_uri; + + /** + * Expected HTTP response code. + */ + unsigned int http_status; + +}; + + +/** + * Callback for a DELETE /account/$H_WIRE operation. + * + * @param cls closure for this function + * @param adr response being processed + */ +static void +delete_account_cb (void *cls, + const struct TALER_MERCHANT_AccountDeleteResponse *adr) +{ + struct DeleteAccountState *das = cls; + + das->adh = NULL; + if (das->http_status != adr->hr.http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u (%d) to command %s\n", + adr->hr.http_status, + (int) adr->hr.ec, + TALER_TESTING_interpreter_get_current_label (das->is)); + TALER_TESTING_interpreter_fail (das->is); + return; + } + switch (adr->hr.http_status) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_UNAUTHORIZED: + break; + case MHD_HTTP_NOT_FOUND: + break; + case MHD_HTTP_CONFLICT: + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unhandled HTTP status %u for DELETE account.\n", + adr->hr.http_status); + } + TALER_TESTING_interpreter_next (das->is); +} + + +/** + * Run the "DELETE account" CMD. + * + * @param cls closure. + * @param cmd command being run now. + * @param is interpreter state. + */ +static void +delete_account_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct DeleteAccountState *das = cls; + const struct TALER_TESTING_Command *ref; + const struct TALER_MerchantWireHashP *h_wire; + const char **merchant_url; + + das->is = is; + ref = TALER_TESTING_interpreter_lookup_command (is, + das->get_instance_ref); + if (NULL == ref) + { + GNUNET_break (0); + TALER_TESTING_FAIL (is); + return; + } + if (GNUNET_OK != + TALER_TESTING_get_trait_merchant_base_url (ref, + &merchant_url)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Command %s lacked merchant base URL\n", + das->get_instance_ref); + GNUNET_break (0); + TALER_TESTING_FAIL (is); + return; + } + for (unsigned int i=0;i<UINT_MAX;i++) + { + const char **payto_uri; + + if (GNUNET_OK != + TALER_TESTING_get_trait_payto_uris (ref, + i, + &payto_uri)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Command %s did not return payto URI %s\n", + das->get_instance_ref, + das->payto_uri); + GNUNET_break (0); + TALER_TESTING_FAIL (is); + return; + } + if (0 != strcmp (*payto_uri, + das->payto_uri)) + continue; /* different account */ + if (GNUNET_OK != + TALER_TESTING_get_trait_h_wires (ref, + i, + &h_wire)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Command %s had payto URI %s but lacked h_wire!?\n", + das->get_instance_ref, + das->payto_uri); + GNUNET_break (0); + TALER_TESTING_FAIL (is); + return; + } + break; + } + GNUNET_assert (NULL != h_wire); + das->adh = TALER_MERCHANT_account_delete (is->ctx, + *merchant_url, + h_wire, + &delete_account_cb, + das); + GNUNET_assert (NULL != das->adh); +} + + +/** + * Free the state of a "DELETE account" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd command being run. + */ +static void +delete_account_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct DeleteAccountState *das = cls; + + if (NULL != das->adh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "DELETE /accounts/$ID operation did not complete\n"); + TALER_MERCHANT_account_delete_cancel (das->adh); + } + GNUNET_free (das); +} + + +struct TALER_TESTING_Command +TALER_TESTING_cmd_merchant_delete_account (const char *label, + const char *get_instance_ref, + const char *payto_uri, + unsigned int http_status) +{ + struct DeleteAccountState *das; + + das = GNUNET_new (struct DeleteAccountState); + das->get_instance_ref = get_instance_ref; + das->payto_uri = payto_uri; + das->http_status = http_status; + { + struct TALER_TESTING_Command cmd = { + .cls = das, + .label = label, + .run = &delete_account_run, + .cleanup = &delete_account_cleanup + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_delete_account.c */ diff --git a/src/testing/testing_api_cmd_get_instance.c b/src/testing/testing_api_cmd_get_instance.c @@ -29,6 +29,39 @@ /** + * Details about a merchant's bank account. + */ +struct MERCHANT_Account +{ + /** + * salt used to compute h_wire + */ + struct TALER_WireSaltP salt; + + /** + * payto:// URI of the account. + */ + char *payto_uri; + + /** + * Credit facade URL of the account. + */ + char *credit_facade_url; + + /** + * Hash of @e payto_uri and @e salt. + */ + struct TALER_MerchantWireHashP h_wire; + + /** + * true if the account is active, + * false if it is historic. + */ + bool active; +}; + + +/** * State of a "GET instance" CMD. */ struct GetInstanceState @@ -80,6 +113,17 @@ struct GetInstanceState const char **inactive_accounts; /** + * Length of the @e accounts array. + */ + unsigned int accounts_length; + + /** + * Array of @e accounts_length bank accounts of the merchant instance as + * returned by the request. + */ + struct MERCHANT_Account *accounts; + + /** * The length of @e inactive_accounts. */ unsigned int inactive_accounts_length; @@ -126,6 +170,22 @@ get_instance_cb (void *cls, { const struct TALER_MERCHANT_InstanceDetails *details = &igr->details.ok.details; + + gis->accounts_length = igr->details.ok.accounts_length; + gis->accounts = GNUNET_new_array (gis->accounts_length, + struct MERCHANT_Account); + for (unsigned int i = 0; i<gis->accounts_length; i++) + { + const struct TALER_MERCHANT_Account *src = &igr->details.ok.accounts[i]; + struct MERCHANT_Account *dst = &gis->accounts[i]; + + dst->salt = src->salt; + dst->payto_uri = GNUNET_strdup (src->payto_uri); + if (NULL != src->credit_facade_url) + dst->credit_facade_url = GNUNET_strdup (src->credit_facade_url); + dst->h_wire = src->h_wire; + dst->active = src->active; + } { const char **name; @@ -383,10 +443,69 @@ get_instance_cleanup (void *cls, "GET /instances/$ID operation did not complete\n"); TALER_MERCHANT_instance_get_cancel (gis->igh); } + for (unsigned int i = 0; i<gis->accounts_length; i++) + { + struct MERCHANT_Account *acc = &gis->accounts[i]; + + GNUNET_free (acc->payto_uri); + GNUNET_free (acc->credit_facade_url); + } + GNUNET_free (gis->accounts); GNUNET_free (gis); } +/** + * Offers information from the GET /instance/$ID CMD state to other + * commands. + * + * @param cls closure + * @param[out] ret 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 enum GNUNET_GenericReturnValue +get_instance_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct GetInstanceState *pps = cls; + + if (index < pps->accounts_length) + { + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_merchant_base_url (&pps->merchant_url), + TALER_TESTING_make_trait_h_wires ( + index, + &pps->accounts[index].h_wire), + TALER_TESTING_make_trait_payto_uris ( + index, + (const char **) &pps->accounts[index].payto_uri), + TALER_TESTING_trait_end (), + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); + } + else + { + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_merchant_base_url (&pps->merchant_url), + TALER_TESTING_trait_end (), + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); + } +} + + struct TALER_TESTING_Command TALER_TESTING_cmd_merchant_get_instance (const char *label, const char *merchant_url, @@ -407,7 +526,8 @@ TALER_TESTING_cmd_merchant_get_instance (const char *label, .cls = gis, .label = label, .run = &get_instance_run, - .cleanup = &get_instance_cleanup + .cleanup = &get_instance_cleanup, + .traits = &get_instance_traits }; return cmd; @@ -443,7 +563,8 @@ TALER_TESTING_cmd_merchant_get_instance2 (const char *label, .cls = gis, .label = label, .run = &get_instance_run, - .cleanup = &get_instance_cleanup + .cleanup = &get_instance_cleanup, + .traits = &get_instance_traits }; return cmd; diff --git a/src/testing/testing_api_cmd_pay_order.c b/src/testing/testing_api_cmd_pay_order.c @@ -71,7 +71,7 @@ struct PayState * Total amount to be paid. */ struct TALER_Amount total_amount; - + /** * Amount to be paid, plus the deposit fee. */ @@ -259,7 +259,7 @@ pay_cb (void *cls, { char *pc; bool found = false; - + if (NULL == pr->details.ok.pos_confirmation) { GNUNET_break (0); @@ -269,7 +269,7 @@ pay_cb (void *cls, pc = TALER_build_pos_confirmation (ps->pos_key, ps->pos_alg, &ps->total_amount, - GNUNET_TIME_timestamp_get()); + GNUNET_TIME_timestamp_get ()); /* Check if *any* of our TOTP codes overlaps with any of the returned TOTP codes. */ for (const char *tok = strtok (pc, "\n"); @@ -283,7 +283,7 @@ pay_cb (void *cls, break; } } - GNUNET_free (pc); + GNUNET_free (pc); if (! found) { GNUNET_break (0); diff --git a/src/testing/testing_api_cmd_post_account.c b/src/testing/testing_api_cmd_post_account.c @@ -0,0 +1,220 @@ +/* + This file is part of TALER + Copyright (C) 2023 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_account.c + * @brief command to test POST /account + * @author Christian Grothoff + */ +#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" + + +/** + * State of a "POST /account" CMD. + */ +struct PostAccountState +{ + + /** + * Handle for a "GET product" request. + */ + struct TALER_MERCHANT_AccountPostHandle *aph; + + /** + * The interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Base URL of the merchant serving the request. + */ + const char *merchant_url; + + /** + * Account configuration for the account to create. + */ + struct TALER_MERCHANT_AccountConfig ac; + + /** + * Expected HTTP response code. + */ + unsigned int http_status; + +}; + + +/** + * Callback for a POST /account operation. + * + * @param cls closure for this function + * @param hr response being processed + */ +static void +post_account_cb (void *cls, + const struct TALER_MERCHANT_AccountPostResponse *apr) +{ + struct PostAccountState *pas = cls; + + pas->aph = NULL; + if (pas->http_status != apr->hr.http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u (%d) to command %s\n", + apr->hr.http_status, + (int) apr->hr.ec, + TALER_TESTING_interpreter_get_current_label (pas->is)); + TALER_TESTING_interpreter_fail (pas->is); + return; + } + switch (apr->hr.http_status) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_UNAUTHORIZED: + break; + case MHD_HTTP_FORBIDDEN: + break; + case MHD_HTTP_NOT_FOUND: + break; + case MHD_HTTP_CONFLICT: + break; + default: + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unhandled HTTP status %u for POST /account.\n", + apr->hr.http_status); + } + TALER_TESTING_interpreter_next (pas->is); +} + + +/** + * Run the "POST /account" CMD. + * + * + * @param cls closure. + * @param cmd command being run now. + * @param is interpreter state. + */ +static void +post_account_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct PostAccountState *pas = cls; + + pas->is = is; + pas->aph = TALER_MERCHANT_account_post (is->ctx, + pas->merchant_url, + &pas->ac, + &post_account_cb, + pas); + GNUNET_assert (NULL != pas->aph); +} + + +/** + * Offers information from the POST /account CMD state to other + * commands. + * + * @param cls closure + * @param[out] ret 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 enum GNUNET_GenericReturnValue +post_account_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + /* struct PostAccountState *pps = cls; */ + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_trait_end (), + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Free the state of a "POST product" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd command being run. + */ +static void +post_account_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct PostAccountState *pas = cls; + + if (NULL != pas->aph) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "POST /account operation did not complete\n"); + TALER_MERCHANT_account_post_cancel (pas->aph); + } + json_decref (pas->ac.credit_facade_credentials); + GNUNET_free (pas); +} + + +struct TALER_TESTING_Command +TALER_TESTING_cmd_merchant_post_account ( + const char *label, + const char *merchant_url, + const char *payto_uri, + const char *credit_facade_url, + const json_t *credit_facade_credentials, + unsigned int http_status) +{ + struct PostAccountState *pas; + + pas = GNUNET_new (struct PostAccountState); + pas->merchant_url = merchant_url; + pas->ac.payto_uri = payto_uri; + pas->ac.credit_facade_url = credit_facade_url; + if (NULL != credit_facade_credentials) + pas->ac.credit_facade_credentials + = json_incref ((json_t *) credit_facade_credentials); + pas->http_status = http_status; + { + struct TALER_TESTING_Command cmd = { + .cls = pas, + .label = label, + .run = &post_account_run, + .cleanup = &post_account_cleanup, + .traits = &post_account_traits + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_post_account.c */